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 | |
65 | enum |
66 | { |
67 | OBJECT_SELECTED, |
68 | OBJECT_ACTIVATED, |
69 | LAST_SIGNAL |
70 | }; |
71 | |
72 | struct _GtkInspectorObjectTreePrivate |
73 | { |
74 | GtkColumnView *list; |
75 | GtkTreeListModel *tree_model; |
76 | GtkSingleSelection *selection; |
77 | GtkWidget *search_bar; |
78 | GtkWidget *search_entry; |
79 | }; |
80 | |
81 | typedef struct _ObjectTreeClassFuncs ObjectTreeClassFuncs; |
82 | typedef void (* ObjectTreeForallFunc) (GObject *object, |
83 | const char *name, |
84 | gpointer data); |
85 | |
86 | struct _ObjectTreeClassFuncs { |
87 | GType (* get_type) (void); |
88 | GObject * (* get_parent) (GObject *object); |
89 | GListModel * (* get_children) (GObject *object); |
90 | }; |
91 | |
92 | static guint signals[LAST_SIGNAL] = { 0 }; |
93 | |
94 | G_DEFINE_TYPE_WITH_PRIVATE (GtkInspectorObjectTree, gtk_inspector_object_tree, GTK_TYPE_BOX) |
95 | |
96 | static GObject * |
97 | object_tree_get_parent_default (GObject *object) |
98 | { |
99 | return g_object_get_data (object, key: "inspector-object-tree-parent" ); |
100 | } |
101 | |
102 | static GListModel * |
103 | object_tree_get_children_default (GObject *object) |
104 | { |
105 | return NULL; |
106 | } |
107 | |
108 | static GObject * |
109 | object_tree_widget_get_parent (GObject *object) |
110 | { |
111 | return G_OBJECT (gtk_widget_get_parent (GTK_WIDGET (object))); |
112 | } |
113 | |
114 | static GListModel * |
115 | object_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 | |
134 | static GListModel * |
135 | object_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 | |
145 | static GListModel * |
146 | object_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 | |
156 | static void |
157 | update_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 | |
179 | static void |
180 | list_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 | |
187 | static GListModel * |
188 | list_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 | |
204 | static GListModel * |
205 | list_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 | |
225 | static GListModel * |
226 | object_tree_combo_box_get_children (GObject *object) |
227 | { |
228 | return list_model_for_properties (object, props: (const char *[2]) { "model" , NULL }); |
229 | } |
230 | |
231 | static void |
232 | treeview_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 | |
299 | static GListModel * |
300 | object_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 | |
328 | static GListModel * |
329 | object_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 | |
347 | static GListModel * |
348 | object_tree_icon_view_get_children (GObject *object) |
349 | { |
350 | return list_model_for_properties (object, props: (const char *[2]) { "model" , NULL }); |
351 | } |
352 | |
353 | static gboolean |
354 | object_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 | |
367 | static GListModel * |
368 | object_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 | |
382 | static GListModel * |
383 | object_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 | |
403 | static GListModel * |
404 | object_tree_text_view_get_children (GObject *object) |
405 | { |
406 | return list_model_for_properties (object, props: (const char *[2]) { "buffer" , NULL }); |
407 | } |
408 | |
409 | static GListModel * |
410 | object_tree_text_buffer_get_children (GObject *object) |
411 | { |
412 | return list_model_for_properties (object, props: (const char *[2]) { "tag-table" , NULL }); |
413 | } |
414 | |
415 | static void |
416 | text_tag_added (GtkTextTagTable *table, |
417 | GtkTextTag *tag, |
418 | GListStore *store) |
419 | { |
420 | g_list_store_append (store, item: tag); |
421 | } |
422 | |
423 | static void |
424 | text_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 | |
443 | static void |
444 | text_tag_foreach (GtkTextTag *tag, |
445 | gpointer store) |
446 | { |
447 | g_list_store_append (store, item: tag); |
448 | } |
449 | |
450 | static GListModel * |
451 | object_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 | |
462 | static GListModel * |
463 | object_tree_application_get_children (GObject *object) |
464 | { |
465 | return list_model_for_properties (object, props: (const char *[2]) { "menubar" , NULL }); |
466 | } |
467 | |
468 | static GObject * |
469 | object_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 | */ |
479 | static 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 | |
557 | static const ObjectTreeClassFuncs * |
558 | find_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 | |
576 | static GObject * |
577 | object_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 | |
586 | static GListModel * |
587 | object_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 | |
619 | static const char * |
620 | gtk_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 | |
648 | char * |
649 | gtk_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 | |
659 | void |
660 | gtk_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 | |
667 | static void |
668 | on_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 | |
684 | GObject * |
685 | gtk_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 | |
700 | static void |
701 | widget_mapped (GtkWidget *widget, |
702 | GtkWidget *label) |
703 | { |
704 | gtk_widget_remove_css_class (widget: label, css_class: "dim-label" ); |
705 | } |
706 | |
707 | static void |
708 | widget_unmapped (GtkWidget *widget, |
709 | GtkWidget *label) |
710 | { |
711 | gtk_widget_add_css_class (widget: label, css_class: "dim-label" ); |
712 | } |
713 | |
714 | static gboolean |
715 | search (GtkInspectorObjectTree *wt, |
716 | gboolean forward, |
717 | gboolean force_progress); |
718 | |
719 | static gboolean |
720 | key_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 | |
769 | static void |
770 | destroy_controller (GtkEventController *controller) |
771 | { |
772 | gtk_widget_remove_controller (widget: gtk_event_controller_get_widget (controller), controller); |
773 | } |
774 | |
775 | static gboolean toplevel_filter_func (gpointer item, |
776 | gpointer data); |
777 | |
778 | static void |
779 | map (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 | |
797 | static void |
798 | unmap (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 | |
808 | static gboolean |
809 | match_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 | |
825 | static gboolean |
826 | match_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 | |
852 | static GObject * |
853 | search_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 | |
881 | static gboolean |
882 | search (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 | |
934 | static void |
935 | on_search_changed (GtkSearchEntry *entry, |
936 | GtkInspectorObjectTree *wt) |
937 | { |
938 | if (!search (wt, TRUE, FALSE)) |
939 | gtk_widget_error_bell (GTK_WIDGET (wt)); |
940 | } |
941 | |
942 | static void |
943 | next_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 | |
953 | static void |
954 | previous_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 | |
964 | static void |
965 | stop_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 | |
972 | static void |
973 | setup_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 | |
989 | static void |
990 | bind_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 | |
1018 | static void |
1019 | unbind_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 | |
1037 | static void |
1038 | setup_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 | |
1050 | static void |
1051 | bind_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 | |
1065 | static void |
1066 | setup_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 | |
1078 | static void |
1079 | bind_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 | |
1106 | static void |
1107 | unbind_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 | |
1119 | static GListModel * |
1120 | create_model_for_object (gpointer object, |
1121 | gpointer user_data) |
1122 | { |
1123 | return object_get_children (object); |
1124 | } |
1125 | |
1126 | static gboolean |
1127 | toplevel_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 | |
1141 | static GListModel * |
1142 | create_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 | |
1169 | static void |
1170 | gtk_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 | |
1179 | static void |
1180 | gtk_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 | |
1190 | static void |
1191 | gtk_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 | |
1238 | static guint |
1239 | model_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 | |
1258 | static GtkTreeListRow * |
1259 | find_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 | |
1287 | void |
1288 | gtk_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 | |
1303 | void |
1304 | gtk_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 | |