1/*
2 * Copyright (c) 2008-2009 Christian Hammond
3 * Copyright (c) 2008-2009 David Trowbridge
4 * Copyright (c) 2013 Intel Corporation
5 *
6 * Permission is hereby granted, free of charge, to any person obtaining a
7 * copy of this software and associated documentation files (the "Software"),
8 * to deal in the Software without restriction, including without limitation
9 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
10 * and/or sell copies of the Software, and to permit persons to whom the
11 * Software is furnished to do so, subject to the following conditions:
12 *
13 * The above copyright notice and this permission notice shall be included
14 * in all copies or substantial portions of the Software.
15 *
16 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22 * THE SOFTWARE.
23 */
24
25#include "config.h"
26#include <glib/gi18n-lib.h>
27
28#include <string.h>
29
30#include "object-tree.h"
31#include "prop-list.h"
32#include "window.h"
33
34#include "gtkbuildable.h"
35#include "gtkbutton.h"
36#include "gtkcelllayout.h"
37#include "gtkcolumnview.h"
38#include "gtkcomboboxprivate.h"
39#include "gtkfilterlistmodel.h"
40#include "gtkcustomfilter.h"
41#include "gtkflattenlistmodel.h"
42#include "gtkbuiltiniconprivate.h"
43#include "gtkiconview.h"
44#include "gtklabel.h"
45#include "gtklistitem.h"
46#include "gtkpopover.h"
47#include "gtksettings.h"
48#include "gtksingleselection.h"
49#include "gtksignallistitemfactory.h"
50#include "gtktextview.h"
51#include "gtktogglebutton.h"
52#include "gtktreeexpander.h"
53#include "gtktreelistmodel.h"
54#include "gtktreeview.h"
55#include "gtktreeselection.h"
56#include "gtktreestore.h"
57#include "gtktreemodelsort.h"
58#include "gtktreemodelfilter.h"
59#include "gtkwidgetprivate.h"
60#include "gtksearchbar.h"
61#include "gtksearchentry.h"
62#include "gtkeventcontrollerkey.h"
63
64
65enum
66{
67 OBJECT_SELECTED,
68 OBJECT_ACTIVATED,
69 LAST_SIGNAL
70};
71
72struct _GtkInspectorObjectTreePrivate
73{
74 GtkColumnView *list;
75 GtkTreeListModel *tree_model;
76 GtkSingleSelection *selection;
77 GtkWidget *search_bar;
78 GtkWidget *search_entry;
79};
80
81typedef struct _ObjectTreeClassFuncs ObjectTreeClassFuncs;
82typedef void (* ObjectTreeForallFunc) (GObject *object,
83 const char *name,
84 gpointer data);
85
86struct _ObjectTreeClassFuncs {
87 GType (* get_type) (void);
88 GObject * (* get_parent) (GObject *object);
89 GListModel * (* get_children) (GObject *object);
90};
91
92static guint signals[LAST_SIGNAL] = { 0 };
93
94G_DEFINE_TYPE_WITH_PRIVATE (GtkInspectorObjectTree, gtk_inspector_object_tree, GTK_TYPE_BOX)
95
96static GObject *
97object_tree_get_parent_default (GObject *object)
98{
99 return g_object_get_data (object, key: "inspector-object-tree-parent");
100}
101
102static GListModel *
103object_tree_get_children_default (GObject *object)
104{
105 return NULL;
106}
107
108static GObject *
109object_tree_widget_get_parent (GObject *object)
110{
111 return G_OBJECT (gtk_widget_get_parent (GTK_WIDGET (object)));
112}
113
114static GListModel *
115object_tree_widget_get_children (GObject *object)
116{
117 GtkWidget *widget = GTK_WIDGET (object);
118 GListStore *list;
119 GListModel *sublist;
120
121 list = g_list_store_new (G_TYPE_LIST_MODEL);
122
123 sublist = gtk_widget_observe_children (widget);
124 g_list_store_append (store: list, item: sublist);
125 g_object_unref (object: sublist);
126
127 sublist = gtk_widget_observe_controllers (widget);
128 g_list_store_append (store: list, item: sublist);
129 g_object_unref (object: sublist);
130
131 return G_LIST_MODEL (ptr: gtk_flatten_list_model_new (model: G_LIST_MODEL (ptr: list)));
132}
133
134static GListModel *
135object_tree_tree_model_sort_get_children (GObject *object)
136{
137 GListStore *store;
138
139 store = g_list_store_new (G_TYPE_OBJECT);
140 g_list_store_append (store, item: gtk_tree_model_sort_get_model (GTK_TREE_MODEL_SORT (object)));
141
142 return G_LIST_MODEL (ptr: store);
143}
144
145static GListModel *
146object_tree_tree_model_filter_get_children (GObject *object)
147{
148 GListStore *store;
149
150 store = g_list_store_new (G_TYPE_OBJECT);
151 g_list_store_append (store, item: gtk_tree_model_filter_get_model (GTK_TREE_MODEL_FILTER (object)));
152
153 return G_LIST_MODEL (ptr: store);
154}
155
156static void
157update_list_store (GListStore *store,
158 GObject *object,
159 const char *property)
160{
161 gpointer value;
162
163 g_object_get (object, first_property_name: property, &value, NULL);
164 if (value)
165 {
166 g_list_store_splice (store,
167 position: 0,
168 n_removals: g_list_model_get_n_items (list: G_LIST_MODEL (ptr: store)),
169 additions: &value,
170 n_additions: 1);
171 g_object_unref (object: value);
172 }
173 else
174 {
175 g_list_store_remove_all (store);
176 }
177}
178
179static void
180list_model_for_property_notify_cb (GObject *object,
181 GParamSpec *pspec,
182 GListStore *store)
183{
184 update_list_store (store, object, property: pspec->name);
185}
186
187static GListModel *
188list_model_for_property (GObject *object,
189 const char *property)
190{
191 GListStore *store = g_list_store_new (G_TYPE_OBJECT);
192
193 /* g_signal_connect_object ("notify::property") */
194 g_signal_connect_closure_by_id (instance: object,
195 signal_id: g_signal_lookup (name: "notify", G_OBJECT_TYPE (object)),
196 detail: g_quark_from_static_string (string: property),
197 closure: g_cclosure_new_object (G_CALLBACK (list_model_for_property_notify_cb), G_OBJECT (store)),
198 FALSE);
199 update_list_store (store, object, property);
200
201 return G_LIST_MODEL (ptr: store);
202}
203
204static GListModel *
205list_model_for_properties (GObject *object,
206 const char **props)
207{
208 GListStore *concat;
209 guint i;
210
211 if (props[1] == NULL)
212 return list_model_for_property (object, property: props[0]);
213
214 concat = g_list_store_new (G_TYPE_LIST_MODEL);
215 for (i = 0; props[i]; i++)
216 {
217 GListModel *tmp = list_model_for_property (object, property: props[i]);
218 g_list_store_append (store: concat, item: tmp);
219 g_object_unref (object: tmp);
220 }
221
222 return G_LIST_MODEL (ptr: gtk_flatten_list_model_new (model: G_LIST_MODEL (ptr: concat)));
223}
224
225static GListModel *
226object_tree_combo_box_get_children (GObject *object)
227{
228 return list_model_for_properties (object, props: (const char *[2]) { "model", NULL });
229}
230
231static void
232treeview_columns_changed (GtkTreeView *treeview,
233 GListModel *store)
234{
235 GtkTreeViewColumn *column, *item;
236 guint i, n_columns, n_items;
237
238 n_columns = gtk_tree_view_get_n_columns (tree_view: treeview);
239 n_items = g_list_model_get_n_items (list: store);
240
241 for (i = 0; i < MAX (n_columns, n_items); i++)
242 {
243 column = gtk_tree_view_get_column (tree_view: treeview, n: i);
244 item = g_list_model_get_item (list: store, position: i);
245 g_object_unref (object: item);
246
247 if (column == item)
248 continue;
249
250 if (n_columns < n_items)
251 {
252 /* column removed */
253 g_assert (n_columns + 1 == n_items);
254 g_list_store_remove (store: G_LIST_STORE (ptr: store), position: i);
255 return;
256 }
257 else if (n_columns > n_items)
258 {
259 /* column added */
260 g_assert (n_columns - 1 == n_items);
261 g_list_store_insert (store: G_LIST_STORE (ptr: store), position: i, item: column);
262 return;
263 }
264 else
265 {
266 guint j;
267 /* column reordered */
268 for (j = n_columns - 1; j > i; j--)
269 {
270 column = gtk_tree_view_get_column (tree_view: treeview, n: j);
271 item = g_list_model_get_item (list: store, position: j);
272 g_object_unref (object: item);
273
274 if (column != item)
275 break;
276 }
277 g_assert (j > i);
278 column = gtk_tree_view_get_column (tree_view: treeview, n: i);
279 item = g_list_model_get_item (list: store, position: j);
280 g_object_unref (object: item);
281
282 if (item == column)
283 {
284 /* column was removed from position j and put into position i */
285 g_list_store_remove (store: G_LIST_STORE (ptr: store), position: j);
286 g_list_store_insert (store: G_LIST_STORE (ptr: store), position: i, item: column);
287 }
288 else
289 {
290 /* column was removed from position i and put into position j */
291 column = gtk_tree_view_get_column (tree_view: treeview, n: j);
292 g_list_store_remove (store: G_LIST_STORE (ptr: store), position: i);
293 g_list_store_insert (store: G_LIST_STORE (ptr: store), position: j, item: column);
294 }
295 }
296 }
297}
298
299static GListModel *
300object_tree_tree_view_get_children (GObject *object)
301{
302 GtkTreeView *treeview = GTK_TREE_VIEW (object);
303 GListStore *columns, *selection, *result_list;
304 GListModel *props;
305 guint i;
306
307 props = list_model_for_properties (object, props: (const char *[2]) { "model", NULL });
308
309 columns = g_list_store_new (GTK_TYPE_TREE_VIEW_COLUMN);
310 g_signal_connect_object (instance: treeview, detailed_signal: "columns-changed", G_CALLBACK (treeview_columns_changed), gobject: columns, connect_flags: 0);
311 for (i = 0; i < gtk_tree_view_get_n_columns (tree_view: treeview); i++)
312 g_list_store_append (store: columns, item: gtk_tree_view_get_column (tree_view: treeview, n: i));
313
314 selection = g_list_store_new (GTK_TYPE_TREE_SELECTION);
315 g_list_store_append (store: selection, item: gtk_tree_view_get_selection (tree_view: treeview));
316
317 result_list = g_list_store_new (G_TYPE_LIST_MODEL);
318 g_list_store_append (store: result_list, item: props);
319 g_object_unref (object: props);
320 g_list_store_append (store: result_list, item: selection);
321 g_object_unref (object: selection);
322 g_list_store_append (store: result_list, item: columns);
323 g_object_unref (object: columns);
324
325 return G_LIST_MODEL (ptr: gtk_flatten_list_model_new (model: G_LIST_MODEL (ptr: result_list)));
326}
327
328static GListModel *
329object_tree_column_view_get_children (GObject *object)
330{
331 GtkColumnView *view = GTK_COLUMN_VIEW (object);
332 GListStore *result_list;
333 GListModel *columns, *sublist;
334
335 result_list = g_list_store_new (G_TYPE_LIST_MODEL);
336
337 columns = gtk_column_view_get_columns (self: view);
338 g_list_store_append (store: result_list, item: columns);
339
340 sublist = object_tree_widget_get_children (object);
341 g_list_store_append (store: result_list, item: sublist);
342 g_object_unref (object: sublist);
343
344 return G_LIST_MODEL (ptr: gtk_flatten_list_model_new (model: G_LIST_MODEL (ptr: result_list)));
345}
346
347static GListModel *
348object_tree_icon_view_get_children (GObject *object)
349{
350 return list_model_for_properties (object, props: (const char *[2]) { "model", NULL });
351}
352
353static gboolean
354object_tree_cell_area_add_child (GtkCellRenderer *renderer,
355 gpointer store)
356{
357 gpointer cell_layout;
358
359 cell_layout = g_object_get_data (object: store, key: "gtk-inspector-cell-layout");
360 g_object_set_data (G_OBJECT (renderer), key: "gtk-inspector-cell-layout", data: cell_layout);
361
362 g_list_store_append (store, item: renderer);
363
364 return FALSE;
365}
366
367static GListModel *
368object_tree_cell_area_get_children (GObject *object)
369{
370 GListStore *store;
371 gpointer cell_layout;
372
373 cell_layout = g_object_get_data (object, key: "gtk-inspector-cell-layout");
374 store = g_list_store_new (GTK_TYPE_CELL_RENDERER);
375 g_object_set_data (G_OBJECT (store), key: "gtk-inspector-cell-layout", data: cell_layout);
376 /* XXX: no change notification for cell areas */
377 gtk_cell_area_foreach (GTK_CELL_AREA (object), callback: object_tree_cell_area_add_child, callback_data: store);
378
379 return G_LIST_MODEL (ptr: store);
380}
381
382static GListModel *
383object_tree_cell_layout_get_children (GObject *object)
384{
385 GListStore *store;
386 GtkCellArea *area;
387
388 /* cell areas handle their own stuff */
389 if (GTK_IS_CELL_AREA (object))
390 return NULL;
391
392 area = gtk_cell_layout_get_area (GTK_CELL_LAYOUT (object));
393 if (!area)
394 return NULL;
395
396 g_object_set_data (G_OBJECT (area), key: "gtk-inspector-cell-layout", data: object);
397 /* XXX: are cell areas immutable? */
398 store = g_list_store_new (G_TYPE_OBJECT);
399 g_list_store_append (store, item: area);
400 return G_LIST_MODEL (ptr: store);
401}
402
403static GListModel *
404object_tree_text_view_get_children (GObject *object)
405{
406 return list_model_for_properties (object, props: (const char *[2]) { "buffer", NULL });
407}
408
409static GListModel *
410object_tree_text_buffer_get_children (GObject *object)
411{
412 return list_model_for_properties (object, props: (const char *[2]) { "tag-table", NULL });
413}
414
415static void
416text_tag_added (GtkTextTagTable *table,
417 GtkTextTag *tag,
418 GListStore *store)
419{
420 g_list_store_append (store, item: tag);
421}
422
423static void
424text_tag_removed (GtkTextTagTable *table,
425 GtkTextTag *tag,
426 GListStore *store)
427{
428 guint i;
429
430 for (i = 0; i < g_list_model_get_n_items (list: G_LIST_MODEL (ptr: store)); i++)
431 {
432 gpointer item = g_list_model_get_item (list: G_LIST_MODEL (ptr: store), position: i);
433 g_object_unref (object: item);
434
435 if (tag == item)
436 {
437 g_list_store_remove (store, position: i);
438 return;
439 }
440 }
441}
442
443static void
444text_tag_foreach (GtkTextTag *tag,
445 gpointer store)
446{
447 g_list_store_append (store, item: tag);
448}
449
450static GListModel *
451object_tree_text_tag_table_get_children (GObject *object)
452{
453 GListStore *store = g_list_store_new (GTK_TYPE_TEXT_TAG);
454
455 g_signal_connect_object (instance: object, detailed_signal: "tag-added", G_CALLBACK (text_tag_added), gobject: store, connect_flags: 0);
456 g_signal_connect_object (instance: object, detailed_signal: "tag-removed", G_CALLBACK (text_tag_removed), gobject: store, connect_flags: 0);
457 gtk_text_tag_table_foreach (GTK_TEXT_TAG_TABLE (object), func: text_tag_foreach, data: store);
458
459 return NULL;
460}
461
462static GListModel *
463object_tree_application_get_children (GObject *object)
464{
465 return list_model_for_properties (object, props: (const char *[2]) { "menubar", NULL });
466}
467
468static GObject *
469object_tree_event_controller_get_parent (GObject *object)
470{
471 return G_OBJECT (gtk_event_controller_get_widget (GTK_EVENT_CONTROLLER (object)));
472}
473
474/* Note:
475 * This tree must be sorted with the most specific types first.
476 * We iterate over it top to bottom and return the first match
477 * using g_type_is_a ()
478 */
479static const ObjectTreeClassFuncs object_tree_class_funcs[] = {
480 {
481 gtk_application_get_type,
482 object_tree_get_parent_default,
483 object_tree_application_get_children
484 },
485 {
486 gtk_text_tag_table_get_type,
487 object_tree_get_parent_default,
488 object_tree_text_tag_table_get_children
489 },
490 {
491 gtk_text_buffer_get_type,
492 object_tree_get_parent_default,
493 object_tree_text_buffer_get_children
494 },
495 {
496 gtk_text_view_get_type,
497 object_tree_widget_get_parent,
498 object_tree_text_view_get_children
499 },
500 {
501 gtk_icon_view_get_type,
502 object_tree_widget_get_parent,
503 object_tree_icon_view_get_children
504 },
505 {
506 gtk_tree_view_get_type,
507 object_tree_widget_get_parent,
508 object_tree_tree_view_get_children
509 },
510 {
511 gtk_column_view_get_type,
512 object_tree_widget_get_parent,
513 object_tree_column_view_get_children
514 },
515 {
516 gtk_combo_box_get_type,
517 object_tree_widget_get_parent,
518 object_tree_combo_box_get_children
519 },
520 {
521 gtk_widget_get_type,
522 object_tree_widget_get_parent,
523 object_tree_widget_get_children
524 },
525 {
526 gtk_tree_model_filter_get_type,
527 object_tree_get_parent_default,
528 object_tree_tree_model_filter_get_children
529 },
530 {
531 gtk_tree_model_sort_get_type,
532 object_tree_get_parent_default,
533 object_tree_tree_model_sort_get_children
534 },
535 {
536 gtk_cell_area_get_type,
537 object_tree_get_parent_default,
538 object_tree_cell_area_get_children
539 },
540 {
541 gtk_cell_layout_get_type,
542 object_tree_get_parent_default,
543 object_tree_cell_layout_get_children
544 },
545 {
546 gtk_event_controller_get_type,
547 object_tree_event_controller_get_parent,
548 object_tree_get_children_default
549 },
550 {
551 g_object_get_type,
552 object_tree_get_parent_default,
553 object_tree_get_children_default
554 },
555};
556
557static const ObjectTreeClassFuncs *
558find_class_funcs (GObject *object)
559{
560 GType object_type;
561 guint i;
562
563 object_type = G_OBJECT_TYPE (object);
564
565 for (i = 0; i < G_N_ELEMENTS (object_tree_class_funcs); i++)
566 {
567 if (g_type_is_a (type: object_type, is_a_type: object_tree_class_funcs[i].get_type ()))
568 return &object_tree_class_funcs[i];
569 }
570
571 g_assert_not_reached ();
572
573 return NULL;
574}
575
576static GObject *
577object_get_parent (GObject *object)
578{
579 const ObjectTreeClassFuncs *funcs;
580
581 funcs = find_class_funcs (object);
582
583 return funcs->get_parent (object);
584}
585
586static GListModel *
587object_get_children (GObject *object)
588{
589 GType object_type;
590 GListModel *children;
591 GListStore *result_list;
592 guint i;
593
594 object_type = G_OBJECT_TYPE (object);
595 result_list = NULL;
596
597 for (i = 0; i < G_N_ELEMENTS (object_tree_class_funcs); i++)
598 {
599 if (!g_type_is_a (type: object_type, is_a_type: object_tree_class_funcs[i].get_type ()))
600 continue;
601
602 children = object_tree_class_funcs[i].get_children (object);
603 if (children == NULL)
604 continue;
605
606 if (!result_list)
607 result_list = g_list_store_new (G_TYPE_LIST_MODEL);
608
609 g_list_store_append (store: result_list, item: children);
610 g_object_unref (object: children);
611 }
612
613 if (result_list)
614 return G_LIST_MODEL (ptr: gtk_flatten_list_model_new (model: G_LIST_MODEL (ptr: result_list)));
615 else
616 return NULL;
617}
618
619static const char *
620gtk_inspector_get_object_name (GObject *object)
621{
622 if (GTK_IS_WIDGET (object))
623 {
624 const char *id;
625
626 id = gtk_widget_get_name (GTK_WIDGET (object));
627 if (id != NULL && g_strcmp0 (str1: id, G_OBJECT_TYPE_NAME (object)) != 0)
628 return id;
629 }
630
631 if (GTK_IS_BUILDABLE (object))
632 {
633 const char *id;
634
635 id = gtk_buildable_get_buildable_id (GTK_BUILDABLE (object));
636 if (id != NULL && !g_str_has_prefix (str: id, prefix: "___object_"))
637 return id;
638 }
639
640 if (GTK_IS_EVENT_CONTROLLER (object))
641 {
642 return gtk_event_controller_get_name (GTK_EVENT_CONTROLLER (object));
643 }
644
645 return NULL;
646}
647
648char *
649gtk_inspector_get_object_title (GObject *object)
650{
651 const char *name = gtk_inspector_get_object_name (object);
652
653 if (name == NULL)
654 return g_strdup (G_OBJECT_TYPE_NAME (object));
655 else
656 return g_strconcat (G_OBJECT_TYPE_NAME (object), " — ", name, NULL);
657}
658
659void
660gtk_inspector_object_tree_activate_object (GtkInspectorObjectTree *wt,
661 GObject *object)
662{
663 gtk_inspector_object_tree_select_object (wt, object);
664 g_signal_emit (instance: wt, signal_id: signals[OBJECT_ACTIVATED], detail: 0, object);
665}
666
667static void
668on_row_activated (GtkColumnView *view,
669 guint pos,
670 GtkInspectorObjectTree *wt)
671{
672 GtkTreeListRow *item;
673 GObject *object;
674
675 item = g_list_model_get_item (list: G_LIST_MODEL (ptr: wt->priv->tree_model), position: pos);
676 object = gtk_tree_list_row_get_item (self: item);
677
678 gtk_inspector_object_tree_activate_object (wt, object);
679
680 g_object_unref (object: item);
681 g_object_unref (object);
682}
683
684GObject *
685gtk_inspector_object_tree_get_selected (GtkInspectorObjectTree *wt)
686{
687 GtkTreeListRow *selected_item;
688 GObject *object;
689
690 selected_item = gtk_single_selection_get_selected_item (self: wt->priv->selection);
691 if (selected_item == NULL)
692 return NULL;
693
694 object = gtk_tree_list_row_get_item (self: selected_item);
695
696 g_object_unref (object); /* ahem */
697 return object;
698}
699
700static void
701widget_mapped (GtkWidget *widget,
702 GtkWidget *label)
703{
704 gtk_widget_remove_css_class (widget: label, css_class: "dim-label");
705}
706
707static void
708widget_unmapped (GtkWidget *widget,
709 GtkWidget *label)
710{
711 gtk_widget_add_css_class (widget: label, css_class: "dim-label");
712}
713
714static gboolean
715search (GtkInspectorObjectTree *wt,
716 gboolean forward,
717 gboolean force_progress);
718
719static gboolean
720key_pressed (GtkEventController *controller,
721 guint keyval,
722 guint keycode,
723 GdkModifierType state,
724 GtkInspectorObjectTree *wt)
725{
726 if (gtk_widget_get_mapped (GTK_WIDGET (wt)))
727 {
728 GdkModifierType default_accel;
729 gboolean search_started;
730
731 search_started = gtk_search_bar_get_search_mode (GTK_SEARCH_BAR (wt->priv->search_bar));
732 default_accel = GDK_CONTROL_MASK;
733
734 if (search_started &&
735 (keyval == GDK_KEY_Return ||
736 keyval == GDK_KEY_ISO_Enter ||
737 keyval == GDK_KEY_KP_Enter))
738 {
739 gtk_widget_activate (GTK_WIDGET (wt->priv->list));
740 return GDK_EVENT_PROPAGATE;
741 }
742 else if (search_started &&
743 (keyval == GDK_KEY_Escape))
744 {
745 gtk_search_bar_set_search_mode (GTK_SEARCH_BAR (wt->priv->search_bar), FALSE);
746 return GDK_EVENT_STOP;
747 }
748 else if (search_started &&
749 ((state & (default_accel | GDK_SHIFT_MASK)) == (default_accel | GDK_SHIFT_MASK)) &&
750 (keyval == GDK_KEY_g || keyval == GDK_KEY_G))
751 {
752 if (!search (wt, TRUE, TRUE))
753 gtk_widget_error_bell (GTK_WIDGET (wt));
754 return GDK_EVENT_STOP;
755 }
756 else if (search_started &&
757 ((state & (default_accel | GDK_SHIFT_MASK)) == default_accel) &&
758 (keyval == GDK_KEY_g || keyval == GDK_KEY_G))
759 {
760 if (!search (wt, TRUE, TRUE))
761 gtk_widget_error_bell (GTK_WIDGET (wt));
762 return GDK_EVENT_STOP;
763 }
764 }
765
766 return GDK_EVENT_PROPAGATE;
767}
768
769static void
770destroy_controller (GtkEventController *controller)
771{
772 gtk_widget_remove_controller (widget: gtk_event_controller_get_widget (controller), controller);
773}
774
775static gboolean toplevel_filter_func (gpointer item,
776 gpointer data);
777
778static void
779map (GtkWidget *widget)
780{
781 GtkInspectorObjectTree *wt = GTK_INSPECTOR_OBJECT_TREE (widget);
782 GtkEventController *controller;
783 GtkWidget *toplevel;
784
785 GTK_WIDGET_CLASS (gtk_inspector_object_tree_parent_class)->map (widget);
786
787 toplevel = GTK_WIDGET (gtk_widget_get_root (widget));
788
789 controller = gtk_event_controller_key_new ();
790 g_object_set_data_full (G_OBJECT (toplevel), key: "object-controller", data: controller, destroy: (GDestroyNotify)destroy_controller);
791 g_signal_connect (controller, "key-pressed", G_CALLBACK (key_pressed), widget);
792 gtk_widget_add_controller (widget: toplevel, controller);
793
794 gtk_search_bar_set_key_capture_widget (GTK_SEARCH_BAR (wt->priv->search_bar), widget: toplevel);
795}
796
797static void
798unmap (GtkWidget *widget)
799{
800 GtkWidget *toplevel;
801
802 toplevel = GTK_WIDGET (gtk_widget_get_root (widget));
803 g_object_set_data (G_OBJECT (toplevel), key: "object-controller", NULL);
804
805 GTK_WIDGET_CLASS (gtk_inspector_object_tree_parent_class)->unmap (widget);
806}
807
808static gboolean
809match_string (const char *string,
810 const char *text)
811{
812 char *lower;
813 gboolean match = FALSE;
814
815 if (string)
816 {
817 lower = g_ascii_strdown (str: string, len: -1);
818 match = g_str_has_prefix (str: lower, prefix: text);
819 g_free (mem: lower);
820 }
821
822 return match;
823}
824
825static gboolean
826match_object (GObject *object,
827 const char *text)
828{
829 char *address;
830 gboolean ret = FALSE;
831
832 if (match_string (G_OBJECT_TYPE_NAME (object), text) ||
833 match_string (string: gtk_inspector_get_object_name (object), text))
834 return TRUE;
835
836 if (GTK_IS_LABEL (object))
837 return match_string (string: gtk_label_get_label (GTK_LABEL (object)), text);
838 else if (GTK_IS_BUTTON (object))
839 return match_string (string: gtk_button_get_label (GTK_BUTTON (object)), text);
840 else if (GTK_IS_WINDOW (object))
841 return match_string (string: gtk_window_get_title (GTK_WINDOW (object)), text);
842 else if (GTK_IS_TREE_VIEW_COLUMN (object))
843 return match_string (string: gtk_tree_view_column_get_title (GTK_TREE_VIEW_COLUMN (object)), text);
844
845 address = g_strdup_printf (format: "%p", object);
846 ret = match_string (string: address, text);
847 g_free (mem: address);
848
849 return ret;
850}
851
852static GObject *
853search_children (GObject *object,
854 const char *text,
855 gboolean forward)
856{
857 GListModel *children;
858 GObject *child, *result;
859 guint i, n;
860
861 children = object_get_children (object);
862 if (children == NULL)
863 return NULL;
864
865 n = g_list_model_get_n_items (list: children);
866 for (i = 0; i < n; i++)
867 {
868 child = g_list_model_get_item (list: children, position: forward ? i : n - i - 1);
869 if (match_object (object: child, text))
870 return child;
871
872 result = search_children (object: child, text, forward);
873 g_object_unref (object: child);
874 if (result)
875 return result;
876 }
877
878 return NULL;
879}
880
881static gboolean
882search (GtkInspectorObjectTree *wt,
883 gboolean forward,
884 gboolean force_progress)
885{
886 GtkInspectorObjectTreePrivate *priv = wt->priv;
887 GListModel *model = G_LIST_MODEL (ptr: priv->tree_model);
888 GtkTreeListRow *row_item;
889 GObject *child, *result;
890 guint i, selected, n, row;
891 const char *text;
892
893 text = gtk_editable_get_text (GTK_EDITABLE (priv->search_entry));
894 selected = gtk_single_selection_get_selected (self: priv->selection);
895 n = g_list_model_get_n_items (list: model);
896 if (selected >= n)
897 selected = 0;
898
899 for (i = 0; i < n; i++)
900 {
901 row = (selected + (forward ? i : n - i - 1)) % n;
902 row_item = g_list_model_get_item (list: model, position: row);
903 child = gtk_tree_list_row_get_item (self: row_item);
904 if (i > 0 || !force_progress)
905 {
906 if (match_object (object: child, text))
907 {
908 gtk_single_selection_set_selected (self: priv->selection, position: row);
909 g_object_unref (object: child);
910 g_object_unref (object: row_item);
911 return TRUE;
912 }
913 }
914
915 if (!gtk_tree_list_row_get_expanded (self: row_item))
916 {
917 result = search_children (object: child, text, forward);
918 if (result)
919 {
920 gtk_inspector_object_tree_select_object (wt, object: result);
921 g_object_unref (object: result);
922 g_object_unref (object: child);
923 g_object_unref (object: row_item);
924 return TRUE;
925 }
926 }
927 g_object_unref (object: child);
928 g_object_unref (object: row_item);
929 }
930
931 return FALSE;
932}
933
934static void
935on_search_changed (GtkSearchEntry *entry,
936 GtkInspectorObjectTree *wt)
937{
938 if (!search (wt, TRUE, FALSE))
939 gtk_widget_error_bell (GTK_WIDGET (wt));
940}
941
942static void
943next_match (GtkButton *button,
944 GtkInspectorObjectTree *wt)
945{
946 if (gtk_search_bar_get_search_mode (GTK_SEARCH_BAR (wt->priv->search_bar)))
947 {
948 if (!search (wt, TRUE, TRUE))
949 gtk_widget_error_bell (GTK_WIDGET (wt));
950 }
951}
952
953static void
954previous_match (GtkButton *button,
955 GtkInspectorObjectTree *wt)
956{
957 if (gtk_search_bar_get_search_mode (GTK_SEARCH_BAR (wt->priv->search_bar)))
958 {
959 if (!search (wt, FALSE, TRUE))
960 gtk_widget_error_bell (GTK_WIDGET (wt));
961 }
962}
963
964static void
965stop_search (GtkWidget *entry,
966 GtkInspectorObjectTree *wt)
967{
968 gtk_editable_set_text (GTK_EDITABLE (wt->priv->search_entry), text: "");
969 gtk_search_bar_set_search_mode (GTK_SEARCH_BAR (wt->priv->search_bar), FALSE);
970}
971
972static void
973setup_type_cb (GtkSignalListItemFactory *factory,
974 GtkListItem *list_item)
975{
976 GtkWidget *expander, *label;
977
978 /* expander */
979 expander = gtk_tree_expander_new ();
980 gtk_list_item_set_child (self: list_item, child: expander);
981
982 /* label */
983 label = gtk_label_new (NULL);
984 gtk_label_set_width_chars (GTK_LABEL (label), n_chars: 30);
985 gtk_label_set_xalign (GTK_LABEL (label), xalign: 0.0);
986 gtk_tree_expander_set_child (self: GTK_TREE_EXPANDER (ptr: expander), child: label);
987}
988
989static void
990bind_type_cb (GtkSignalListItemFactory *factory,
991 GtkListItem *list_item)
992{
993 GtkWidget *expander, *label;
994 GtkTreeListRow *list_row;
995 gpointer item;
996
997 list_row = gtk_list_item_get_item (self: list_item);
998 expander = gtk_list_item_get_child (self: list_item);
999 gtk_tree_expander_set_list_row (self: GTK_TREE_EXPANDER (ptr: expander), list_row);
1000 item = gtk_tree_list_row_get_item (self: list_row);
1001 expander = gtk_list_item_get_child (self: list_item);
1002 label = gtk_tree_expander_get_child (self: GTK_TREE_EXPANDER (ptr: expander));
1003
1004 gtk_label_set_label (GTK_LABEL (label), G_OBJECT_TYPE_NAME (item));
1005
1006 if (GTK_IS_WIDGET (item))
1007 {
1008 g_signal_connect (item, "map", G_CALLBACK (widget_mapped), label);
1009 g_signal_connect (item, "unmap", G_CALLBACK (widget_unmapped), label);
1010 if (!gtk_widget_get_mapped (widget: item))
1011 widget_unmapped (widget: item, label);
1012 g_object_set_data (G_OBJECT (label), key: "binding", g_object_ref (item));
1013 }
1014
1015 g_object_unref (object: item);
1016}
1017
1018static void
1019unbind_type_cb (GtkSignalListItemFactory *factory,
1020 GtkListItem *list_item)
1021{
1022 GtkWidget *expander, *label;
1023 gpointer item;
1024
1025 expander = gtk_list_item_get_child (self: list_item);
1026 label = gtk_tree_expander_get_child (self: GTK_TREE_EXPANDER (ptr: expander));
1027 item = g_object_steal_data (G_OBJECT (label), key: "binding");
1028 if (item)
1029 {
1030 g_signal_handlers_disconnect_by_func (item, widget_mapped, label);
1031 g_signal_handlers_disconnect_by_func (item, widget_unmapped, label);
1032
1033 g_object_unref (object: item);
1034 }
1035}
1036
1037static void
1038setup_name_cb (GtkSignalListItemFactory *factory,
1039 GtkListItem *list_item)
1040{
1041 GtkWidget *label;
1042
1043 label = gtk_label_new (NULL);
1044 gtk_label_set_width_chars (GTK_LABEL (label), n_chars: 15);
1045 gtk_label_set_ellipsize (GTK_LABEL (label), mode: PANGO_ELLIPSIZE_END);
1046 gtk_label_set_xalign (GTK_LABEL (label), xalign: 0.0);
1047 gtk_list_item_set_child (self: list_item, child: label);
1048}
1049
1050static void
1051bind_name_cb (GtkSignalListItemFactory *factory,
1052 GtkListItem *list_item)
1053{
1054 GtkWidget *label;
1055 gpointer item;
1056
1057 item = gtk_tree_list_row_get_item (self: gtk_list_item_get_item (self: list_item));
1058 label = gtk_list_item_get_child (self: list_item);
1059
1060 gtk_label_set_label (GTK_LABEL (label), str: gtk_inspector_get_object_name (object: item));
1061
1062 g_object_unref (object: item);
1063}
1064
1065static void
1066setup_label_cb (GtkSignalListItemFactory *factory,
1067 GtkListItem *list_item)
1068{
1069 GtkWidget *label;
1070
1071 label = gtk_label_new (NULL);
1072 gtk_label_set_width_chars (GTK_LABEL (label), n_chars: 25);
1073 gtk_label_set_ellipsize (GTK_LABEL (label), mode: PANGO_ELLIPSIZE_END);
1074 gtk_label_set_xalign (GTK_LABEL (label), xalign: 0.0);
1075 gtk_list_item_set_child (self: list_item, child: label);
1076}
1077
1078static void
1079bind_label_cb (GtkSignalListItemFactory *factory,
1080 GtkListItem *list_item)
1081{
1082 GtkWidget *label;
1083 gpointer item;
1084 GBinding *binding = NULL;
1085
1086 item = gtk_tree_list_row_get_item (self: gtk_list_item_get_item (self: list_item));
1087 label = gtk_list_item_get_child (self: list_item);
1088
1089 if (GTK_IS_LABEL (item))
1090 binding = g_object_bind_property (source: item, source_property: "label", target: label, target_property: "label", flags: G_BINDING_SYNC_CREATE);
1091 else if (GTK_IS_BUTTON (item))
1092 binding = g_object_bind_property (source: item, source_property: "label", target: label, target_property: "label", flags: G_BINDING_SYNC_CREATE);
1093 else if (GTK_IS_WINDOW (item))
1094 binding = g_object_bind_property (source: item, source_property: "title", target: label, target_property: "label", flags: G_BINDING_SYNC_CREATE);
1095 else if (GTK_IS_TREE_VIEW_COLUMN (item))
1096 binding = g_object_bind_property (source: item, source_property: "title", target: label, target_property: "label", flags: G_BINDING_SYNC_CREATE);
1097 else
1098 gtk_label_set_label (GTK_LABEL (label), NULL);
1099
1100 g_object_unref (object: item);
1101
1102 if (binding)
1103 g_object_set_data (G_OBJECT (label), key: "binding", data: binding);
1104}
1105
1106static void
1107unbind_label_cb (GtkSignalListItemFactory *factory,
1108 GtkListItem *list_item)
1109{
1110 GtkWidget *label;
1111 GBinding *binding;
1112
1113 label = gtk_list_item_get_child (self: list_item);
1114 binding = g_object_steal_data (G_OBJECT (label), key: "binding");
1115 if (binding)
1116 g_binding_unbind (binding);
1117}
1118
1119static GListModel *
1120create_model_for_object (gpointer object,
1121 gpointer user_data)
1122{
1123 return object_get_children (object);
1124}
1125
1126static gboolean
1127toplevel_filter_func (gpointer item,
1128 gpointer data)
1129{
1130 GdkDisplay *display = data;
1131
1132 if (!GTK_IS_WINDOW (item))
1133 return FALSE;
1134
1135 if (g_str_equal (G_OBJECT_TYPE_NAME (item), v2: "GtkInspectorWindow"))
1136 return FALSE;
1137
1138 return gtk_widget_get_display (widget: item) == display;
1139}
1140
1141static GListModel *
1142create_root_model (GdkDisplay *display)
1143{
1144 GtkFilter *filter;
1145 GtkFilterListModel *filter_model;
1146 GListModel *model;
1147 GListStore *list, *special;
1148 gpointer item;
1149
1150 list = g_list_store_new (G_TYPE_LIST_MODEL);
1151
1152 special = g_list_store_new (G_TYPE_OBJECT);
1153 item = g_application_get_default ();
1154 if (item)
1155 g_list_store_append (store: special, item);
1156 g_list_store_append (store: special, item: gtk_settings_get_for_display (display));
1157 g_list_store_append (store: list, item: special);
1158 g_object_unref (object: special);
1159
1160 filter = GTK_FILTER (ptr: gtk_custom_filter_new (match_func: toplevel_filter_func, user_data: display, NULL));
1161 model = gtk_window_get_toplevels ();
1162 filter_model = gtk_filter_list_model_new (g_object_ref (model), filter);
1163 g_list_store_append (store: list, item: filter_model);
1164 g_object_unref (object: filter_model);
1165
1166 return G_LIST_MODEL (ptr: gtk_flatten_list_model_new (model: G_LIST_MODEL (ptr: list)));
1167}
1168
1169static void
1170gtk_inspector_object_tree_init (GtkInspectorObjectTree *wt)
1171{
1172 wt->priv = gtk_inspector_object_tree_get_instance_private (self: wt);
1173 gtk_widget_init_template (GTK_WIDGET (wt));
1174
1175 gtk_search_bar_connect_entry (GTK_SEARCH_BAR (wt->priv->search_bar),
1176 GTK_EDITABLE (wt->priv->search_entry));
1177}
1178
1179static void
1180gtk_inspector_object_tree_dispose (GObject *object)
1181{
1182 GtkInspectorObjectTree *wt = GTK_INSPECTOR_OBJECT_TREE (object);
1183
1184 g_clear_object (&wt->priv->tree_model);
1185 g_clear_object (&wt->priv->selection);
1186
1187 G_OBJECT_CLASS (gtk_inspector_object_tree_parent_class)->dispose (object);
1188}
1189
1190static void
1191gtk_inspector_object_tree_class_init (GtkInspectorObjectTreeClass *klass)
1192{
1193 GObjectClass *object_class = G_OBJECT_CLASS (klass);
1194 GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
1195
1196 object_class->dispose = gtk_inspector_object_tree_dispose;
1197
1198 widget_class->map = map;
1199 widget_class->unmap = unmap;
1200
1201 signals[OBJECT_ACTIVATED] =
1202 g_signal_new (signal_name: "object-activated",
1203 G_OBJECT_CLASS_TYPE (klass),
1204 signal_flags: G_SIGNAL_RUN_FIRST | G_SIGNAL_NO_RECURSE,
1205 G_STRUCT_OFFSET (GtkInspectorObjectTreeClass, object_activated),
1206 NULL, NULL,
1207 NULL,
1208 G_TYPE_NONE, n_params: 1, G_TYPE_OBJECT);
1209
1210 signals[OBJECT_SELECTED] =
1211 g_signal_new (signal_name: "object-selected",
1212 G_OBJECT_CLASS_TYPE (klass),
1213 signal_flags: G_SIGNAL_RUN_FIRST | G_SIGNAL_NO_RECURSE,
1214 G_STRUCT_OFFSET (GtkInspectorObjectTreeClass, object_selected),
1215 NULL, NULL,
1216 NULL,
1217 G_TYPE_NONE, n_params: 1, G_TYPE_OBJECT);
1218
1219 gtk_widget_class_set_template_from_resource (widget_class, resource_name: "/org/gtk/libgtk/inspector/object-tree.ui");
1220 gtk_widget_class_bind_template_child_private (widget_class, GtkInspectorObjectTree, list);
1221 gtk_widget_class_bind_template_child_private (widget_class, GtkInspectorObjectTree, search_bar);
1222 gtk_widget_class_bind_template_child_private (widget_class, GtkInspectorObjectTree, search_entry);
1223 gtk_widget_class_bind_template_callback (widget_class, on_search_changed);
1224 gtk_widget_class_bind_template_callback (widget_class, on_row_activated);
1225 gtk_widget_class_bind_template_callback (widget_class, next_match);
1226 gtk_widget_class_bind_template_callback (widget_class, previous_match);
1227 gtk_widget_class_bind_template_callback (widget_class, stop_search);
1228 gtk_widget_class_bind_template_callback (widget_class, setup_type_cb);
1229 gtk_widget_class_bind_template_callback (widget_class, bind_type_cb);
1230 gtk_widget_class_bind_template_callback (widget_class, unbind_type_cb);
1231 gtk_widget_class_bind_template_callback (widget_class, setup_name_cb);
1232 gtk_widget_class_bind_template_callback (widget_class, bind_name_cb);
1233 gtk_widget_class_bind_template_callback (widget_class, setup_label_cb);
1234 gtk_widget_class_bind_template_callback (widget_class, bind_label_cb);
1235 gtk_widget_class_bind_template_callback (widget_class, unbind_label_cb);
1236}
1237
1238static guint
1239model_get_item_index (GListModel *model,
1240 gpointer item)
1241{
1242 gpointer cmp;
1243 guint i;
1244
1245 for (i = 0; (cmp = g_list_model_get_item (list: model, position: i)); i++)
1246 {
1247 if (cmp == item)
1248 {
1249 g_object_unref (object: cmp);
1250 return i;
1251 }
1252 g_object_unref (object: cmp);
1253 }
1254
1255 return G_MAXUINT;
1256}
1257
1258static GtkTreeListRow *
1259find_and_expand_object (GtkTreeListModel *model,
1260 GObject *object)
1261{
1262 GtkTreeListRow *result;
1263 GObject *parent;
1264 guint pos;
1265
1266 parent = object_get_parent (object);
1267 if (parent)
1268 {
1269 GtkTreeListRow *parent_row = find_and_expand_object (model, object: parent);
1270 if (parent_row == NULL)
1271 return NULL;
1272
1273 gtk_tree_list_row_set_expanded (self: parent_row, TRUE);
1274 pos = model_get_item_index (model: gtk_tree_list_row_get_children (self: parent_row), item: object);
1275 result = gtk_tree_list_row_get_child_row (self: parent_row, position: pos);
1276 g_object_unref (object: parent_row);
1277 }
1278 else
1279 {
1280 pos = model_get_item_index (model: gtk_tree_list_model_get_model (self: model), item: object);
1281 result = gtk_tree_list_model_get_child_row (self: model, position: pos);
1282 }
1283
1284 return result;
1285}
1286
1287void
1288gtk_inspector_object_tree_select_object (GtkInspectorObjectTree *wt,
1289 GObject *object)
1290{
1291 GtkTreeListRow *row_item;
1292
1293 row_item = find_and_expand_object (model: wt->priv->tree_model, object);
1294 if (row_item == NULL)
1295 return;
1296
1297 gtk_single_selection_set_selected (self: wt->priv->selection,
1298 position: gtk_tree_list_row_get_position (self: row_item));
1299 g_signal_emit (instance: wt, signal_id: signals[OBJECT_SELECTED], detail: 0, object); // FIXME
1300 g_object_unref (object: row_item);
1301}
1302
1303void
1304gtk_inspector_object_tree_set_display (GtkInspectorObjectTree *wt,
1305 GdkDisplay *display)
1306{
1307 wt->priv->tree_model = gtk_tree_list_model_new (root: create_root_model (display),
1308 FALSE,
1309 FALSE,
1310 create_func: create_model_for_object,
1311 NULL,
1312 NULL);
1313 wt->priv->selection = gtk_single_selection_new (g_object_ref (G_LIST_MODEL (wt->priv->tree_model)));
1314 gtk_column_view_set_model (GTK_COLUMN_VIEW (wt->priv->list),
1315 model: GTK_SELECTION_MODEL (ptr: wt->priv->selection));
1316}
1317

source code of gtk/gtk/inspector/object-tree.c