1 | /* |
2 | * Copyright (c) 2014 Benjamin Otte <otte@gnome.org> |
3 | * |
4 | * Permission is hereby granted, free of charge, to any person obtaining a |
5 | * copy of this software and associated documentation files (the "Software"), |
6 | * to deal in the Software without restriction, including without limitation |
7 | * the rights to use, copy, modify, merge, publish, distribute, sublicntnse, |
8 | * and/or sell copies of the Software, and to permit persons to whom the |
9 | * Software is furnished to do so, subject to the following conditions: |
10 | * |
11 | * The above copyright noticnt and this permission noticnt shall be included |
12 | * in all copies or substantial portions of the Software. |
13 | * |
14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN |
20 | * THE SOFTWARE. |
21 | */ |
22 | |
23 | #include "config.h" |
24 | |
25 | #include "css-node-tree.h" |
26 | #include "prop-editor.h" |
27 | #include "window.h" |
28 | |
29 | #include "gtktreemodelcssnode.h" |
30 | #include "gtktreeview.h" |
31 | #include "gtklabel.h" |
32 | #include "gtkpopover.h" |
33 | #include "gtk/gtkwidgetprivate.h" |
34 | #include "gtkcssproviderprivate.h" |
35 | #include "gtkcssstylepropertyprivate.h" |
36 | #include "gtkcssstyleprivate.h" |
37 | #include "gtkcssvalueprivate.h" |
38 | #include "gtkcssselectorprivate.h" |
39 | #include "gtkliststore.h" |
40 | #include "gtksettings.h" |
41 | #include "gtktreeview.h" |
42 | #include "gtktreeselection.h" |
43 | #include "gtktypebuiltins.h" |
44 | #include "gtkstack.h" |
45 | |
46 | #include <glib/gi18n-lib.h> |
47 | #include <gtk/css/gtkcss.h> |
48 | |
49 | enum { |
50 | COLUMN_NODE_NAME, |
51 | COLUMN_NODE_VISIBLE, |
52 | COLUMN_NODE_CLASSES, |
53 | COLUMN_NODE_ID, |
54 | COLUMN_NODE_STATE, |
55 | /* add more */ |
56 | N_NODE_COLUMNS |
57 | }; |
58 | |
59 | enum |
60 | { |
61 | COLUMN_PROP_NAME, |
62 | COLUMN_PROP_VALUE, |
63 | COLUMN_PROP_LOCATION |
64 | }; |
65 | |
66 | enum |
67 | { |
68 | PROP_0, |
69 | PROP_NODE, |
70 | |
71 | N_PROPS |
72 | }; |
73 | |
74 | struct _GtkInspectorCssNodeTreePrivate |
75 | { |
76 | GtkWidget *node_tree; |
77 | GtkTreeModel *node_model; |
78 | GtkTreeViewColumn *node_name_column; |
79 | GtkTreeViewColumn *node_id_column; |
80 | GtkTreeViewColumn *node_classes_column; |
81 | GtkListStore *prop_model; |
82 | GtkWidget *prop_tree; |
83 | GtkTreeViewColumn *prop_name_column; |
84 | GHashTable *prop_iters; |
85 | GtkCssNode *node; |
86 | }; |
87 | |
88 | static GParamSpec *properties[N_PROPS] = { NULL, }; |
89 | |
90 | G_DEFINE_TYPE_WITH_PRIVATE (GtkInspectorCssNodeTree, gtk_inspector_css_node_tree, GTK_TYPE_BOX) |
91 | |
92 | typedef struct { |
93 | GtkCssNode *node; |
94 | const char *prop_name; |
95 | GdkRectangle rect; |
96 | GtkInspectorCssNodeTree *cnt; |
97 | } NodePropEditor; |
98 | |
99 | static void |
100 | show_node_prop_editor (NodePropEditor *npe) |
101 | { |
102 | GtkWidget *popover; |
103 | GtkWidget *editor; |
104 | |
105 | popover = gtk_popover_new (); |
106 | gtk_widget_set_parent (widget: popover, GTK_WIDGET (npe->cnt)); |
107 | gtk_popover_set_pointing_to (GTK_POPOVER (popover), rect: &npe->rect); |
108 | |
109 | editor = gtk_inspector_prop_editor_new (G_OBJECT (npe->node), name: npe->prop_name, NULL); |
110 | |
111 | gtk_popover_set_child (GTK_POPOVER (popover), child: editor); |
112 | |
113 | gtk_popover_popup (GTK_POPOVER (popover)); |
114 | |
115 | g_signal_connect (popover, "unmap" , G_CALLBACK (gtk_widget_unparent), NULL); |
116 | } |
117 | |
118 | static void |
119 | row_activated (GtkTreeView *tv, |
120 | GtkTreePath *path, |
121 | GtkTreeViewColumn *col, |
122 | GtkInspectorCssNodeTree *cnt) |
123 | { |
124 | GtkTreeIter iter; |
125 | NodePropEditor npe; |
126 | |
127 | npe.cnt = cnt; |
128 | |
129 | if (col == cnt->priv->node_name_column) |
130 | npe.prop_name = "name" ; |
131 | else if (col == cnt->priv->node_id_column) |
132 | npe.prop_name = "id" ; |
133 | else if (col == cnt->priv->node_classes_column) |
134 | npe.prop_name = "classes" ; |
135 | else |
136 | return; |
137 | |
138 | gtk_tree_model_get_iter (tree_model: cnt->priv->node_model, iter: &iter, path); |
139 | npe.node = gtk_tree_model_css_node_get_node_from_iter (GTK_TREE_MODEL_CSS_NODE (cnt->priv->node_model), iter: &iter); |
140 | gtk_tree_view_get_cell_area (tree_view: tv, path, column: col, rect: &npe.rect); |
141 | gtk_tree_view_convert_bin_window_to_widget_coords (tree_view: tv, bx: npe.rect.x, by: npe.rect.y, wx: &npe.rect.x, wy: &npe.rect.y); |
142 | |
143 | show_node_prop_editor (npe: &npe); |
144 | } |
145 | |
146 | static void |
147 | gtk_inspector_css_node_tree_set_node (GtkInspectorCssNodeTree *cnt, |
148 | GtkCssNode *node); |
149 | |
150 | static void |
151 | selection_changed (GtkTreeSelection *selection, GtkInspectorCssNodeTree *cnt) |
152 | { |
153 | GtkTreeIter iter; |
154 | GtkCssNode *node; |
155 | |
156 | if (!gtk_tree_selection_get_selected (selection, NULL, iter: &iter)) |
157 | return; |
158 | |
159 | node = gtk_tree_model_css_node_get_node_from_iter (GTK_TREE_MODEL_CSS_NODE (cnt->priv->node_model), iter: &iter); |
160 | gtk_inspector_css_node_tree_set_node (cnt, node); |
161 | } |
162 | |
163 | static void |
164 | gtk_inspector_css_node_tree_unset_node (GtkInspectorCssNodeTree *cnt) |
165 | { |
166 | GtkInspectorCssNodeTreePrivate *priv = cnt->priv; |
167 | |
168 | if (priv->node) |
169 | { |
170 | g_signal_handlers_disconnect_matched (instance: priv->node, |
171 | mask: G_SIGNAL_MATCH_DATA, |
172 | signal_id: 0, detail: 0, NULL, NULL, |
173 | data: cnt); |
174 | g_object_unref (object: priv->node); |
175 | priv->node = NULL; |
176 | } |
177 | } |
178 | |
179 | static void |
180 | gtk_inspector_css_node_tree_get_property (GObject *object, |
181 | guint property_id, |
182 | GValue *value, |
183 | GParamSpec *pspec) |
184 | { |
185 | GtkInspectorCssNodeTree *cnt = GTK_INSPECTOR_CSS_NODE_TREE (object); |
186 | GtkInspectorCssNodeTreePrivate *priv = cnt->priv; |
187 | |
188 | switch (property_id) |
189 | { |
190 | case PROP_NODE: |
191 | g_value_set_object (value, v_object: priv->node); |
192 | break; |
193 | |
194 | default: |
195 | G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); |
196 | break; |
197 | } |
198 | } |
199 | |
200 | static void |
201 | gtk_inspector_css_node_tree_set_property (GObject *object, |
202 | guint property_id, |
203 | const GValue *value, |
204 | GParamSpec *pspec) |
205 | { |
206 | switch (property_id) |
207 | { |
208 | default: |
209 | G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); |
210 | break; |
211 | } |
212 | } |
213 | |
214 | static void |
215 | gtk_inspector_css_node_tree_finalize (GObject *object) |
216 | { |
217 | GtkInspectorCssNodeTree *cnt = GTK_INSPECTOR_CSS_NODE_TREE (object); |
218 | |
219 | gtk_inspector_css_node_tree_unset_node (cnt); |
220 | |
221 | g_hash_table_unref (hash_table: cnt->priv->prop_iters); |
222 | |
223 | G_OBJECT_CLASS (gtk_inspector_css_node_tree_parent_class)->finalize (object); |
224 | } |
225 | |
226 | static void |
227 | gtk_inspector_css_node_tree_class_init (GtkInspectorCssNodeTreeClass *klass) |
228 | { |
229 | GObjectClass *object_class = G_OBJECT_CLASS (klass); |
230 | GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); |
231 | |
232 | object_class->set_property = gtk_inspector_css_node_tree_set_property; |
233 | object_class->get_property = gtk_inspector_css_node_tree_get_property; |
234 | object_class->finalize = gtk_inspector_css_node_tree_finalize; |
235 | |
236 | properties[PROP_NODE] = |
237 | g_param_spec_object (name: "node" , |
238 | nick: "Node" , |
239 | blurb: "Currently inspected CSS node" , |
240 | GTK_TYPE_CSS_NODE, |
241 | flags: G_PARAM_READABLE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS); |
242 | |
243 | g_object_class_install_properties (oclass: object_class, n_pspecs: N_PROPS, pspecs: properties); |
244 | |
245 | gtk_widget_class_set_template_from_resource (widget_class, resource_name: "/org/gtk/libgtk/inspector/css-node-tree.ui" ); |
246 | gtk_widget_class_bind_template_child_private (widget_class, GtkInspectorCssNodeTree, node_tree); |
247 | gtk_widget_class_bind_template_child_private (widget_class, GtkInspectorCssNodeTree, node_name_column); |
248 | gtk_widget_class_bind_template_child_private (widget_class, GtkInspectorCssNodeTree, node_id_column); |
249 | gtk_widget_class_bind_template_child_private (widget_class, GtkInspectorCssNodeTree, node_classes_column); |
250 | gtk_widget_class_bind_template_child_private (widget_class, GtkInspectorCssNodeTree, prop_name_column); |
251 | gtk_widget_class_bind_template_child_private (widget_class, GtkInspectorCssNodeTree, prop_model); |
252 | gtk_widget_class_bind_template_child_private (widget_class, GtkInspectorCssNodeTree, prop_name_column); |
253 | |
254 | gtk_widget_class_bind_template_callback (widget_class, row_activated); |
255 | gtk_widget_class_bind_template_callback (widget_class, selection_changed); |
256 | } |
257 | |
258 | static int |
259 | sort_strv (gconstpointer a, |
260 | gconstpointer b, |
261 | gpointer data) |
262 | { |
263 | char **ap = (char **) a; |
264 | char **bp = (char **) b; |
265 | |
266 | return g_ascii_strcasecmp (s1: *ap, s2: *bp); |
267 | } |
268 | |
269 | static void |
270 | strv_sort (char **strv) |
271 | { |
272 | g_qsort_with_data (pbase: strv, |
273 | total_elems: g_strv_length (str_array: strv), |
274 | size: sizeof (char *), |
275 | compare_func: sort_strv, |
276 | NULL); |
277 | } |
278 | |
279 | static char * |
280 | format_state_flags (GtkStateFlags state) |
281 | { |
282 | if (state) |
283 | { |
284 | GString *str; |
285 | int i; |
286 | gboolean first = TRUE; |
287 | |
288 | str = g_string_new (init: "" ); |
289 | |
290 | for (i = 0; i < 31; i++) |
291 | { |
292 | if (state & (1 << i)) |
293 | { |
294 | if (!first) |
295 | g_string_append (string: str, val: " | " ); |
296 | first = FALSE; |
297 | g_string_append (string: str, val: gtk_css_pseudoclass_name (flags: 1 << i)); |
298 | } |
299 | } |
300 | return g_string_free (string: str, FALSE); |
301 | } |
302 | |
303 | return g_strdup (str: "" ); |
304 | } |
305 | |
306 | static void |
307 | gtk_inspector_css_node_tree_get_node_value (GtkTreeModelCssNode *model, |
308 | GtkCssNode *node, |
309 | int column, |
310 | GValue *value) |
311 | { |
312 | char **strv; |
313 | char *s; |
314 | |
315 | switch (column) |
316 | { |
317 | case COLUMN_NODE_NAME: |
318 | g_value_set_string (value, v_string: g_quark_to_string (quark: gtk_css_node_get_name (cssnode: node))); |
319 | break; |
320 | |
321 | case COLUMN_NODE_VISIBLE: |
322 | g_value_set_boolean (value, v_boolean: gtk_css_node_get_visible (cssnode: node)); |
323 | break; |
324 | |
325 | case COLUMN_NODE_CLASSES: |
326 | strv = gtk_css_node_get_classes (cssnode: node); |
327 | strv_sort (strv); |
328 | s = g_strjoinv (separator: " " , str_array: strv); |
329 | g_value_take_string (value, v_string: s); |
330 | g_strfreev (str_array: strv); |
331 | break; |
332 | |
333 | case COLUMN_NODE_ID: |
334 | g_value_set_string (value, v_string: g_quark_to_string (quark: gtk_css_node_get_id (cssnode: node))); |
335 | break; |
336 | |
337 | case COLUMN_NODE_STATE: |
338 | g_value_take_string (value, v_string: format_state_flags (state: gtk_css_node_get_state (cssnode: node))); |
339 | break; |
340 | |
341 | default: |
342 | g_assert_not_reached (); |
343 | break; |
344 | } |
345 | } |
346 | |
347 | static void |
348 | gtk_inspector_css_node_tree_init (GtkInspectorCssNodeTree *cnt) |
349 | { |
350 | GtkInspectorCssNodeTreePrivate *priv; |
351 | int i; |
352 | |
353 | cnt->priv = gtk_inspector_css_node_tree_get_instance_private (self: cnt); |
354 | gtk_widget_init_template (GTK_WIDGET (cnt)); |
355 | priv = cnt->priv; |
356 | |
357 | priv->node_model = gtk_tree_model_css_node_new (get_func: gtk_inspector_css_node_tree_get_node_value, |
358 | n_columns: N_NODE_COLUMNS, |
359 | G_TYPE_STRING, |
360 | G_TYPE_BOOLEAN, |
361 | G_TYPE_STRING, |
362 | G_TYPE_STRING, |
363 | G_TYPE_STRING); |
364 | gtk_tree_view_set_model (GTK_TREE_VIEW (priv->node_tree), model: priv->node_model); |
365 | g_object_unref (object: priv->node_model); |
366 | |
367 | gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (cnt->priv->prop_model), |
368 | sort_column_id: COLUMN_PROP_NAME, |
369 | order: GTK_SORT_ASCENDING); |
370 | |
371 | priv->prop_iters = g_hash_table_new_full (hash_func: g_str_hash, key_equal_func: g_str_equal, |
372 | NULL, value_destroy_func: (GDestroyNotify) gtk_tree_iter_free); |
373 | |
374 | for (i = 0; i < _gtk_css_style_property_get_n_properties (); i++) |
375 | { |
376 | GtkCssStyleProperty *prop; |
377 | GtkTreeIter iter; |
378 | const char *name; |
379 | |
380 | prop = _gtk_css_style_property_lookup_by_id (id: i); |
381 | name = _gtk_style_property_get_name (GTK_STYLE_PROPERTY (prop)); |
382 | |
383 | gtk_list_store_append (list_store: cnt->priv->prop_model, iter: &iter); |
384 | gtk_list_store_set (list_store: cnt->priv->prop_model, iter: &iter, COLUMN_PROP_NAME, name, -1); |
385 | g_hash_table_insert (hash_table: cnt->priv->prop_iters, key: (gpointer)name, value: gtk_tree_iter_copy (iter: &iter)); |
386 | } |
387 | } |
388 | |
389 | void |
390 | gtk_inspector_css_node_tree_set_object (GtkInspectorCssNodeTree *cnt, |
391 | GObject *object) |
392 | { |
393 | GtkWidget *stack; |
394 | GtkStackPage *page; |
395 | GtkInspectorCssNodeTreePrivate *priv; |
396 | GtkCssNode *node, *root; |
397 | GtkTreePath *path; |
398 | GtkTreeIter iter; |
399 | |
400 | g_return_if_fail (GTK_INSPECTOR_IS_CSS_NODE_TREE (cnt)); |
401 | |
402 | priv = cnt->priv; |
403 | |
404 | stack = gtk_widget_get_parent (GTK_WIDGET (cnt)); |
405 | page = gtk_stack_get_page (GTK_STACK (stack), GTK_WIDGET (cnt)); |
406 | |
407 | if (!GTK_IS_WIDGET (object)) |
408 | { |
409 | g_object_set (object: page, first_property_name: "visible" , FALSE, NULL); |
410 | return; |
411 | } |
412 | |
413 | g_object_set (object: page, first_property_name: "visible" , TRUE, NULL); |
414 | |
415 | root = node = gtk_widget_get_css_node (GTK_WIDGET (object)); |
416 | while (gtk_css_node_get_parent (cssnode: root)) |
417 | root = gtk_css_node_get_parent (cssnode: root); |
418 | |
419 | gtk_tree_model_css_node_set_root_node (GTK_TREE_MODEL_CSS_NODE (priv->node_model), node: root); |
420 | |
421 | gtk_tree_model_css_node_get_iter_from_node (GTK_TREE_MODEL_CSS_NODE (priv->node_model), iter: &iter, node); |
422 | path = gtk_tree_model_get_path (tree_model: priv->node_model, iter: &iter); |
423 | |
424 | gtk_tree_view_expand_to_path (GTK_TREE_VIEW (priv->node_tree), path); |
425 | gtk_tree_view_set_cursor (GTK_TREE_VIEW (priv->node_tree), path, NULL, FALSE); |
426 | gtk_tree_view_scroll_to_cell (GTK_TREE_VIEW (priv->node_tree), path, NULL, TRUE, row_align: 0.5, col_align: 0.0); |
427 | |
428 | gtk_tree_path_free (path); |
429 | } |
430 | |
431 | static void |
432 | gtk_inspector_css_node_tree_update_style (GtkInspectorCssNodeTree *cnt, |
433 | GtkCssStyle *new_style) |
434 | { |
435 | GtkInspectorCssNodeTreePrivate *priv = cnt->priv; |
436 | int i; |
437 | |
438 | for (i = 0; i < _gtk_css_style_property_get_n_properties (); i++) |
439 | { |
440 | GtkCssStyleProperty *prop; |
441 | const char *name; |
442 | GtkTreeIter *iter; |
443 | GtkCssSection *section; |
444 | char *location; |
445 | char *value; |
446 | |
447 | prop = _gtk_css_style_property_lookup_by_id (id: i); |
448 | name = _gtk_style_property_get_name (GTK_STYLE_PROPERTY (prop)); |
449 | |
450 | iter = (GtkTreeIter *)g_hash_table_lookup (hash_table: priv->prop_iters, key: name); |
451 | |
452 | if (new_style) |
453 | { |
454 | value = _gtk_css_value_to_string (value: gtk_css_style_get_value (style: new_style, id: i)); |
455 | |
456 | section = gtk_css_style_get_section (style: new_style, id: i); |
457 | if (section) |
458 | location = gtk_css_section_to_string (section); |
459 | else |
460 | location = NULL; |
461 | } |
462 | else |
463 | { |
464 | value = NULL; |
465 | location = NULL; |
466 | } |
467 | |
468 | gtk_list_store_set (list_store: priv->prop_model, |
469 | iter, |
470 | COLUMN_PROP_VALUE, value, |
471 | COLUMN_PROP_LOCATION, location, |
472 | -1); |
473 | |
474 | g_free (mem: location); |
475 | g_free (mem: value); |
476 | } |
477 | } |
478 | |
479 | static void |
480 | gtk_inspector_css_node_tree_update_style_cb (GtkCssNode *node, |
481 | GtkCssStyleChange *change, |
482 | GtkInspectorCssNodeTree *cnt) |
483 | { |
484 | gtk_inspector_css_node_tree_update_style (cnt, new_style: gtk_css_style_change_get_new_style (change)); |
485 | } |
486 | |
487 | static void |
488 | gtk_inspector_css_node_tree_set_node (GtkInspectorCssNodeTree *cnt, |
489 | GtkCssNode *node) |
490 | { |
491 | GtkInspectorCssNodeTreePrivate *priv = cnt->priv; |
492 | |
493 | if (priv->node == node) |
494 | return; |
495 | |
496 | if (node) |
497 | g_object_ref (node); |
498 | |
499 | gtk_inspector_css_node_tree_update_style (cnt, new_style: node ? gtk_css_node_get_style (cssnode: node) : NULL); |
500 | |
501 | gtk_inspector_css_node_tree_unset_node (cnt); |
502 | |
503 | priv->node = node; |
504 | if (node) |
505 | { |
506 | g_signal_connect (node, "style-changed" , G_CALLBACK (gtk_inspector_css_node_tree_update_style_cb), cnt); |
507 | } |
508 | |
509 | g_object_notify_by_pspec (G_OBJECT (cnt), pspec: properties[PROP_NODE]); |
510 | } |
511 | |
512 | GtkCssNode * |
513 | gtk_inspector_css_node_tree_get_node (GtkInspectorCssNodeTree *cnt) |
514 | { |
515 | GtkInspectorCssNodeTreePrivate *priv = cnt->priv; |
516 | |
517 | return priv->node; |
518 | } |
519 | |
520 | void |
521 | gtk_inspector_css_node_tree_set_display (GtkInspectorCssNodeTree *cnt, |
522 | GdkDisplay *display) |
523 | { |
524 | GtkSettings *settings; |
525 | char *theme_name; |
526 | |
527 | settings = gtk_settings_get_for_display (display); |
528 | g_object_get (object: settings, first_property_name: "gtk-theme-name" , &theme_name, NULL); |
529 | g_object_set (object: settings, first_property_name: "gtk-theme-name" , theme_name, NULL); |
530 | g_free (mem: theme_name); |
531 | } |
532 | |
533 | // vim: set et sw=2 ts=2: |
534 | |