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
49enum {
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
59enum
60{
61 COLUMN_PROP_NAME,
62 COLUMN_PROP_VALUE,
63 COLUMN_PROP_LOCATION
64};
65
66enum
67{
68 PROP_0,
69 PROP_NODE,
70
71 N_PROPS
72};
73
74struct _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
88static GParamSpec *properties[N_PROPS] = { NULL, };
89
90G_DEFINE_TYPE_WITH_PRIVATE (GtkInspectorCssNodeTree, gtk_inspector_css_node_tree, GTK_TYPE_BOX)
91
92typedef struct {
93 GtkCssNode *node;
94 const char *prop_name;
95 GdkRectangle rect;
96 GtkInspectorCssNodeTree *cnt;
97} NodePropEditor;
98
99static void
100show_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
118static void
119row_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
146static void
147gtk_inspector_css_node_tree_set_node (GtkInspectorCssNodeTree *cnt,
148 GtkCssNode *node);
149
150static void
151selection_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
163static void
164gtk_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
179static void
180gtk_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
200static void
201gtk_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
214static void
215gtk_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
226static void
227gtk_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
258static int
259sort_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
269static void
270strv_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
279static char *
280format_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
306static void
307gtk_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
347static void
348gtk_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
389void
390gtk_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
431static void
432gtk_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
479static void
480gtk_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
487static void
488gtk_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
512GtkCssNode *
513gtk_inspector_css_node_tree_get_node (GtkInspectorCssNodeTree *cnt)
514{
515 GtkInspectorCssNodeTreePrivate *priv = cnt->priv;
516
517 return priv->node;
518}
519
520void
521gtk_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

source code of gtk/gtk/inspector/css-node-tree.c