1 | /* |
2 | * Copyright (c) 2014 Red Hat, Inc. |
3 | * |
4 | * This library is free software; you can redistribute it and/or |
5 | * modify it under the terms of the GNU Lesser General Public |
6 | * License as published by the Free Software Foundation; either |
7 | * version 2 of the License, or (at your option) any later version. |
8 | * |
9 | * This library is distributed in the hope that it will be useful, |
10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
12 | * Lesser General Public License for more details. |
13 | * |
14 | * You should have received a copy of the GNU Lesser General Public |
15 | * License along with this library. If not, see <http://www.gnu.org/licenses/>. |
16 | */ |
17 | |
18 | #include "config.h" |
19 | |
20 | #include "resource-list.h" |
21 | |
22 | #include "gtkbutton.h" |
23 | #include "gtklabel.h" |
24 | #include "gtksearchbar.h" |
25 | #include "gtksearchentry.h" |
26 | #include "gtkstack.h" |
27 | #include "gtktextbuffer.h" |
28 | #include "gtktreeselection.h" |
29 | #include "gtktreestore.h" |
30 | #include "gtkeventcontrollerkey.h" |
31 | #include "gtkpicture.h" |
32 | #include "gtkmediafile.h" |
33 | #include "gtkbinlayout.h" |
34 | #include "resource-holder.h" |
35 | |
36 | #include <glib/gi18n-lib.h> |
37 | |
38 | enum |
39 | { |
40 | PROP_0, |
41 | PROP_BUTTONS |
42 | }; |
43 | |
44 | struct _GtkInspectorResourceList |
45 | { |
46 | GtkWidget parent_instance; |
47 | |
48 | GtkTextBuffer *buffer; |
49 | GtkWidget *video; |
50 | GtkWidget *image; |
51 | GtkWidget *content; |
52 | GtkWidget *name_label; |
53 | GtkWidget *type; |
54 | GtkWidget *type_label; |
55 | GtkWidget *size_label; |
56 | GtkWidget *info_grid; |
57 | GtkWidget *stack; |
58 | GtkWidget *buttons; |
59 | GtkWidget *open_details_button; |
60 | GtkWidget *close_details_button; |
61 | GtkWidget *search_bar; |
62 | GtkWidget *search_entry; |
63 | |
64 | GtkWidget *list; |
65 | GtkColumnViewColumn *path; |
66 | GtkColumnViewColumn *count; |
67 | GtkColumnViewColumn *size; |
68 | |
69 | GtkTreeListModel *tree_model; |
70 | GtkSingleSelection *selection; |
71 | }; |
72 | |
73 | typedef struct _GtkInspectorResourceListClass |
74 | { |
75 | GtkWidgetClass parent; |
76 | } GtkInspectorResourceListClass; |
77 | |
78 | |
79 | G_DEFINE_TYPE (GtkInspectorResourceList, gtk_inspector_resource_list, GTK_TYPE_WIDGET) |
80 | |
81 | static GListModel * |
82 | load_resources_recurse (const char *path, |
83 | int *count_out, |
84 | gsize *size_out) |
85 | { |
86 | char **names; |
87 | int i; |
88 | GListStore *result; |
89 | |
90 | result = g_list_store_new (RESOURCE_TYPE_HOLDER); |
91 | |
92 | names = g_resources_enumerate_children (path, lookup_flags: 0, NULL); |
93 | for (i = 0; names[i]; i++) |
94 | { |
95 | int len; |
96 | char *p; |
97 | gboolean has_slash; |
98 | int count; |
99 | gsize size; |
100 | GListModel *children; |
101 | ResourceHolder *holder; |
102 | |
103 | p = g_strconcat (string1: path, names[i], NULL); |
104 | |
105 | len = strlen (s: names[i]); |
106 | has_slash = names[i][len - 1] == '/'; |
107 | |
108 | if (has_slash) |
109 | names[i][len - 1] = '\0'; |
110 | |
111 | count = 0; |
112 | size = 0; |
113 | |
114 | if (has_slash) |
115 | { |
116 | children = load_resources_recurse (path: p, count_out: &count, size_out: &size); |
117 | |
118 | *count_out += count; |
119 | *size_out += size; |
120 | } |
121 | else |
122 | { |
123 | count = 0; |
124 | if (g_resources_get_info (path: p, lookup_flags: 0, size: &size, NULL, NULL)) |
125 | { |
126 | *count_out += 1; |
127 | *size_out += size; |
128 | } |
129 | children = NULL; |
130 | } |
131 | |
132 | holder = resource_holder_new (name: names[i], path: p, count, size, children); |
133 | g_clear_object (&children); |
134 | g_list_store_append (store: result, item: holder); |
135 | g_object_unref (object: holder); |
136 | |
137 | g_free (mem: p); |
138 | } |
139 | |
140 | g_strfreev (str_array: names); |
141 | |
142 | return G_LIST_MODEL (ptr: result); |
143 | } |
144 | |
145 | static gboolean |
146 | populate_details (GtkInspectorResourceList *rl, |
147 | ResourceHolder *holder) |
148 | { |
149 | const char *path; |
150 | const char *name; |
151 | GBytes *bytes; |
152 | char *type; |
153 | gconstpointer data; |
154 | gsize size; |
155 | GError *error = NULL; |
156 | char *markup; |
157 | |
158 | path = resource_holder_get_path (holder); |
159 | name = resource_holder_get_name (holder); |
160 | size = resource_holder_get_size (holder); |
161 | |
162 | if (g_str_has_suffix (str: path, suffix: "/" )) |
163 | return FALSE; |
164 | |
165 | markup = g_strconcat (string1: "<span face='Monospace' size='small'>" , path, "</span>" , NULL); |
166 | gtk_label_set_markup (GTK_LABEL (rl->name_label), str: markup); |
167 | g_free (mem: markup); |
168 | |
169 | bytes = g_resources_lookup_data (path, lookup_flags: 0, error: &error); |
170 | if (bytes == NULL) |
171 | { |
172 | gtk_text_buffer_set_text (buffer: rl->buffer, text: error->message, len: -1); |
173 | g_error_free (error); |
174 | gtk_stack_set_visible_child_name (GTK_STACK (rl->content), name: "text" ); |
175 | } |
176 | else |
177 | { |
178 | char *text; |
179 | char *content_image; |
180 | char *content_text; |
181 | char *content_video; |
182 | |
183 | content_image = g_content_type_from_mime_type (mime_type: "image/*" ); |
184 | content_text = g_content_type_from_mime_type (mime_type: "text/*" ); |
185 | content_video = g_content_type_from_mime_type (mime_type: "video/*" ); |
186 | |
187 | data = g_bytes_get_data (bytes, size: &size); |
188 | type = g_content_type_guess (filename: name, data, data_size: size, NULL); |
189 | |
190 | text = g_content_type_get_description (type); |
191 | gtk_label_set_text (GTK_LABEL (rl->type_label), str: text); |
192 | g_free (mem: text); |
193 | |
194 | text = g_format_size (size); |
195 | gtk_label_set_text (GTK_LABEL (rl->size_label), str: text); |
196 | g_free (mem: text); |
197 | |
198 | if (g_content_type_is_a (type, supertype: content_text)) |
199 | { |
200 | gtk_text_buffer_set_text (buffer: rl->buffer, text: data, len: -1); |
201 | gtk_stack_set_visible_child_name (GTK_STACK (rl->content), name: "text" ); |
202 | } |
203 | else if (g_content_type_is_a (type, supertype: content_image)) |
204 | { |
205 | gtk_picture_set_resource (self: GTK_PICTURE (ptr: rl->image), resource_path: path); |
206 | gtk_stack_set_visible_child_name (GTK_STACK (rl->content), name: "image" ); |
207 | } |
208 | else if (g_content_type_is_a (type, supertype: content_video)) |
209 | { |
210 | GtkMediaStream *stream; |
211 | |
212 | stream = gtk_media_file_new_for_resource (resource_path: path); |
213 | gtk_media_stream_set_loop (self: GTK_MEDIA_STREAM (ptr: stream), TRUE); |
214 | gtk_picture_set_paintable (self: GTK_PICTURE (ptr: rl->image), paintable: GDK_PAINTABLE (ptr: stream)); |
215 | gtk_stack_set_visible_child_name (GTK_STACK (rl->content), name: "image" ); |
216 | gtk_media_stream_play (self: GTK_MEDIA_STREAM (ptr: stream)); |
217 | g_object_unref (object: stream); |
218 | } |
219 | else |
220 | { |
221 | gtk_text_buffer_set_text (buffer: rl->buffer, text: "" , len: 0); |
222 | gtk_stack_set_visible_child_name (GTK_STACK (rl->content), name: "text" ); |
223 | } |
224 | |
225 | g_free (mem: type); |
226 | g_bytes_unref (bytes); |
227 | |
228 | g_free (mem: content_image); |
229 | g_free (mem: content_text); |
230 | } |
231 | |
232 | return TRUE; |
233 | } |
234 | |
235 | static void |
236 | on_row_activated (GtkColumnView *view, |
237 | guint position, |
238 | GtkInspectorResourceList *rl) |
239 | { |
240 | gpointer item; |
241 | ResourceHolder *holder; |
242 | |
243 | item = g_list_model_get_item (list: G_LIST_MODEL (ptr: rl->selection), position); |
244 | holder = gtk_tree_list_row_get_item (self: item); |
245 | g_object_unref (object: item); |
246 | if (populate_details (rl, holder)) |
247 | { |
248 | gtk_stack_set_visible_child_name (GTK_STACK (rl->stack), name: "details" ); |
249 | gtk_stack_set_visible_child_name (GTK_STACK (rl->buttons), name: "details" ); |
250 | } |
251 | g_object_unref (object: holder); |
252 | } |
253 | |
254 | static gboolean |
255 | can_show_details (GtkInspectorResourceList *rl) |
256 | { |
257 | gpointer item; |
258 | ResourceHolder *holder; |
259 | const char *path; |
260 | |
261 | item = gtk_single_selection_get_selected_item (self: rl->selection); |
262 | holder = gtk_tree_list_row_get_item (self: item); |
263 | if (holder == NULL) |
264 | return FALSE; |
265 | path = resource_holder_get_path (holder); |
266 | g_object_unref (object: holder); |
267 | return !g_str_has_suffix (str: path, suffix: "/" ); |
268 | } |
269 | |
270 | static void |
271 | on_selection_changed (GtkSelectionModel *selection, |
272 | guint position, |
273 | guint n_items, |
274 | GtkInspectorResourceList *rl) |
275 | { |
276 | gtk_widget_set_sensitive (widget: rl->open_details_button, sensitive: can_show_details (rl)); |
277 | } |
278 | |
279 | static void |
280 | open_details (GtkWidget *button, |
281 | GtkInspectorResourceList *rl) |
282 | { |
283 | gpointer item; |
284 | ResourceHolder *holder; |
285 | |
286 | item = gtk_single_selection_get_selected_item (self: rl->selection); |
287 | holder = gtk_tree_list_row_get_item (self: item); |
288 | if (holder == NULL) |
289 | return; |
290 | if (populate_details (rl, holder)) |
291 | { |
292 | gtk_stack_set_visible_child_name (GTK_STACK (rl->stack), name: "details" ); |
293 | gtk_stack_set_visible_child_name (GTK_STACK (rl->buttons), name: "details" ); |
294 | } |
295 | g_object_unref (object: holder); |
296 | } |
297 | |
298 | static void |
299 | close_details (GtkWidget *button, |
300 | GtkInspectorResourceList *rl) |
301 | { |
302 | gtk_stack_set_visible_child_name (GTK_STACK (rl->stack), name: "list" ); |
303 | gtk_stack_set_visible_child_name (GTK_STACK (rl->buttons), name: "list" ); |
304 | } |
305 | |
306 | static GListModel * |
307 | load_resources (void) |
308 | { |
309 | int count = 0; |
310 | gsize size = 0; |
311 | |
312 | return load_resources_recurse (path: "/" , count_out: &count, size_out: &size); |
313 | } |
314 | |
315 | static void |
316 | on_map (GtkWidget *widget) |
317 | { |
318 | GtkInspectorResourceList *rl = GTK_INSPECTOR_RESOURCE_LIST (widget); |
319 | |
320 | gtk_stack_set_visible_child_name (GTK_STACK (rl->stack), name: "list" ); |
321 | gtk_widget_set_sensitive (widget: rl->open_details_button, sensitive: can_show_details (rl)); |
322 | } |
323 | |
324 | static gboolean search (GtkInspectorResourceList *rl, |
325 | gboolean forward, |
326 | gboolean force_progress); |
327 | |
328 | static gboolean |
329 | key_pressed (GtkEventController *controller, |
330 | guint keyval, |
331 | guint keycode, |
332 | GdkModifierType state, |
333 | GtkInspectorResourceList *rl) |
334 | { |
335 | if (gtk_widget_get_mapped (GTK_WIDGET (rl))) |
336 | { |
337 | GdkModifierType default_accel; |
338 | gboolean search_started; |
339 | |
340 | search_started = gtk_search_bar_get_search_mode (GTK_SEARCH_BAR (rl->search_bar)); |
341 | default_accel = GDK_CONTROL_MASK; |
342 | |
343 | if (search_started && |
344 | (keyval == GDK_KEY_Return || |
345 | keyval == GDK_KEY_ISO_Enter || |
346 | keyval == GDK_KEY_KP_Enter)) |
347 | { |
348 | gtk_widget_activate (GTK_WIDGET (rl->list)); |
349 | return GDK_EVENT_PROPAGATE; |
350 | } |
351 | else if (search_started && |
352 | (keyval == GDK_KEY_Escape)) |
353 | { |
354 | gtk_search_bar_set_search_mode (GTK_SEARCH_BAR (rl->search_bar), FALSE); |
355 | return GDK_EVENT_STOP; |
356 | } |
357 | else if (search_started && |
358 | ((state & (default_accel | GDK_SHIFT_MASK)) == (default_accel | GDK_SHIFT_MASK)) && |
359 | (keyval == GDK_KEY_g || keyval == GDK_KEY_G)) |
360 | { |
361 | if (!search (rl, FALSE, TRUE)) |
362 | gtk_widget_error_bell (GTK_WIDGET (rl)); |
363 | return GDK_EVENT_STOP; |
364 | } |
365 | else if (search_started && |
366 | ((state & (default_accel | GDK_SHIFT_MASK)) == default_accel) && |
367 | (keyval == GDK_KEY_g || keyval == GDK_KEY_G)) |
368 | { |
369 | if (!search (rl, TRUE, TRUE)) |
370 | gtk_widget_error_bell (GTK_WIDGET (rl)); |
371 | return GDK_EVENT_STOP; |
372 | } |
373 | } |
374 | |
375 | return GDK_EVENT_PROPAGATE; |
376 | } |
377 | |
378 | static void |
379 | destroy_controller (GtkEventController *controller) |
380 | { |
381 | gtk_widget_remove_controller (widget: gtk_event_controller_get_widget (controller), controller); |
382 | } |
383 | |
384 | static void |
385 | root (GtkWidget *widget) |
386 | { |
387 | GtkInspectorResourceList *rl = GTK_INSPECTOR_RESOURCE_LIST (widget); |
388 | GtkEventController *controller; |
389 | GtkWidget *toplevel; |
390 | |
391 | GTK_WIDGET_CLASS (gtk_inspector_resource_list_parent_class)->root (widget); |
392 | |
393 | toplevel = GTK_WIDGET (gtk_widget_get_root (widget)); |
394 | |
395 | controller = gtk_event_controller_key_new (); |
396 | g_object_set_data_full (G_OBJECT (toplevel), key: "resource-controller" , data: controller, destroy: (GDestroyNotify)destroy_controller); |
397 | g_signal_connect (controller, "key-pressed" , G_CALLBACK (key_pressed), widget); |
398 | gtk_widget_add_controller (widget: toplevel, controller); |
399 | |
400 | gtk_search_bar_set_key_capture_widget (GTK_SEARCH_BAR (rl->search_bar), widget: toplevel); |
401 | } |
402 | |
403 | static void |
404 | unroot (GtkWidget *widget) |
405 | { |
406 | GtkWidget *toplevel; |
407 | |
408 | toplevel = GTK_WIDGET (gtk_widget_get_root (widget)); |
409 | g_object_set_data (G_OBJECT (toplevel), key: "resource-controller" , NULL); |
410 | |
411 | GTK_WIDGET_CLASS (gtk_inspector_resource_list_parent_class)->unroot (widget); |
412 | } |
413 | |
414 | static gboolean |
415 | match_string (const char *string, |
416 | const char *text) |
417 | { |
418 | char *lower; |
419 | gboolean match = FALSE; |
420 | |
421 | if (string) |
422 | { |
423 | lower = g_ascii_strdown (str: string, len: -1); |
424 | match = g_str_has_prefix (str: lower, prefix: text); |
425 | g_free (mem: lower); |
426 | } |
427 | |
428 | return match; |
429 | } |
430 | |
431 | static gboolean |
432 | match_object (GObject *object, |
433 | const char *text) |
434 | { |
435 | const char *name = resource_holder_get_name (holder: RESOURCE_HOLDER (ptr: object)); |
436 | |
437 | if (match_string (string: name, text)) |
438 | return TRUE; |
439 | |
440 | return FALSE; |
441 | } |
442 | |
443 | static GObject * |
444 | search_children (GObject *object, |
445 | const char *text, |
446 | gboolean forward) |
447 | { |
448 | GListModel *children; |
449 | GObject *child, *result; |
450 | guint i, n; |
451 | |
452 | children = resource_holder_get_children (holder: RESOURCE_HOLDER (ptr: object)); |
453 | if (children == NULL) |
454 | return NULL; |
455 | |
456 | n = g_list_model_get_n_items (list: children); |
457 | for (i = 0; i < n; i++) |
458 | { |
459 | child = g_list_model_get_item (list: children, position: forward ? i : n - i - 1); |
460 | if (match_object (object: child, text)) |
461 | return child; |
462 | |
463 | result = search_children (object: child, text, forward); |
464 | g_object_unref (object: child); |
465 | if (result) |
466 | return result; |
467 | } |
468 | |
469 | return NULL; |
470 | } |
471 | |
472 | static guint |
473 | model_get_item_index (GListModel *model, |
474 | gpointer item) |
475 | { |
476 | gpointer cmp; |
477 | guint i; |
478 | |
479 | for (i = 0; (cmp = g_list_model_get_item (list: model, position: i)); i++) |
480 | { |
481 | if (cmp == item) |
482 | { |
483 | g_object_unref (object: cmp); |
484 | return i; |
485 | } |
486 | g_object_unref (object: cmp); |
487 | } |
488 | |
489 | return G_MAXUINT; |
490 | } |
491 | |
492 | static GtkTreeListRow * |
493 | find_and_expand_object (GtkTreeListModel *model, |
494 | GObject *object) |
495 | { |
496 | GtkTreeListRow *result; |
497 | GObject *parent; |
498 | guint pos; |
499 | |
500 | parent = G_OBJECT (resource_holder_get_parent (RESOURCE_HOLDER (object))); |
501 | if (parent) |
502 | { |
503 | GtkTreeListRow *parent_row = find_and_expand_object (model, object: parent); |
504 | if (parent_row == NULL) |
505 | return NULL; |
506 | |
507 | gtk_tree_list_row_set_expanded (self: parent_row, TRUE); |
508 | pos = model_get_item_index (model: gtk_tree_list_row_get_children (self: parent_row), item: object); |
509 | result = gtk_tree_list_row_get_child_row (self: parent_row, position: pos); |
510 | g_object_unref (object: parent_row); |
511 | } |
512 | else |
513 | { |
514 | pos = model_get_item_index (model: gtk_tree_list_model_get_model (self: model), item: object); |
515 | result = gtk_tree_list_model_get_child_row (self: model, position: pos); |
516 | } |
517 | |
518 | return result; |
519 | } |
520 | |
521 | static void |
522 | select_object (GtkInspectorResourceList *rl, |
523 | GObject *object) |
524 | { |
525 | GtkTreeListRow *row_item; |
526 | |
527 | row_item = find_and_expand_object (model: rl->tree_model, object); |
528 | if (row_item == NULL) |
529 | return; |
530 | |
531 | gtk_single_selection_set_selected (self: rl->selection, |
532 | position: gtk_tree_list_row_get_position (self: row_item)); |
533 | } |
534 | |
535 | static gboolean |
536 | search (GtkInspectorResourceList *rl, |
537 | gboolean forward, |
538 | gboolean force_progress) |
539 | { |
540 | GListModel *model = G_LIST_MODEL (ptr: rl->tree_model); |
541 | GtkTreeListRow *row_item; |
542 | GObject *child, *result; |
543 | guint i, selected, n, row; |
544 | const char *text; |
545 | |
546 | text = gtk_editable_get_text (GTK_EDITABLE (rl->search_entry)); |
547 | selected = gtk_single_selection_get_selected (self: rl->selection); |
548 | n = g_list_model_get_n_items (list: model); |
549 | if (selected >= n) |
550 | selected = 0; |
551 | |
552 | for (i = 0; i < n; i++) |
553 | { |
554 | row = (selected + (forward ? i : n - i - 1)) % n; |
555 | row_item = g_list_model_get_item (list: model, position: row); |
556 | child = gtk_tree_list_row_get_item (self: row_item); |
557 | if (i > 0 || !force_progress) |
558 | { |
559 | if (match_object (object: child, text)) |
560 | { |
561 | gtk_single_selection_set_selected (self: rl->selection, position: row); |
562 | g_object_unref (object: child); |
563 | g_object_unref (object: row_item); |
564 | return TRUE; |
565 | } |
566 | } |
567 | |
568 | if (!gtk_tree_list_row_get_expanded (self: row_item)) |
569 | { |
570 | result = search_children (object: child, text, forward); |
571 | if (result) |
572 | { |
573 | select_object (rl, object: result); |
574 | g_object_unref (object: result); |
575 | g_object_unref (object: child); |
576 | g_object_unref (object: row_item); |
577 | return TRUE; |
578 | } |
579 | } |
580 | g_object_unref (object: child); |
581 | g_object_unref (object: row_item); |
582 | } |
583 | |
584 | return FALSE; |
585 | } |
586 | |
587 | static void |
588 | on_search_changed (GtkSearchEntry *entry, |
589 | GtkInspectorResourceList *rl) |
590 | { |
591 | if (!search (rl, TRUE, FALSE)) |
592 | gtk_widget_error_bell (GTK_WIDGET (rl)); |
593 | } |
594 | |
595 | static void |
596 | next_match (GtkButton *button, |
597 | GtkInspectorResourceList *rl) |
598 | { |
599 | if (gtk_search_bar_get_search_mode (GTK_SEARCH_BAR (rl->search_bar))) |
600 | { |
601 | if (!search (rl, TRUE, TRUE)) |
602 | gtk_widget_error_bell (GTK_WIDGET (rl)); |
603 | } |
604 | } |
605 | |
606 | static void |
607 | previous_match (GtkButton *button, |
608 | GtkInspectorResourceList *rl) |
609 | { |
610 | if (gtk_search_bar_get_search_mode (GTK_SEARCH_BAR (rl->search_bar))) |
611 | { |
612 | if (!search (rl, FALSE, TRUE)) |
613 | gtk_widget_error_bell (GTK_WIDGET (rl)); |
614 | } |
615 | } |
616 | |
617 | static void |
618 | stop_search (GtkWidget *entry, |
619 | GtkInspectorResourceList *rl) |
620 | { |
621 | gtk_editable_set_text (GTK_EDITABLE (rl->search_entry), text: "" ); |
622 | gtk_search_bar_set_search_mode (GTK_SEARCH_BAR (rl->search_bar), FALSE); |
623 | } |
624 | |
625 | static char * |
626 | holder_name (gpointer item) |
627 | { |
628 | return g_strdup (str: resource_holder_get_name (holder: RESOURCE_HOLDER (ptr: item))); |
629 | } |
630 | |
631 | static int |
632 | holder_count (gpointer item) |
633 | { |
634 | return resource_holder_get_count (holder: RESOURCE_HOLDER (ptr: item)); |
635 | } |
636 | |
637 | static gsize |
638 | holder_size (gpointer item) |
639 | { |
640 | return resource_holder_get_size (holder: RESOURCE_HOLDER (ptr: item)); |
641 | } |
642 | |
643 | static void |
644 | gtk_inspector_resource_list_init (GtkInspectorResourceList *rl) |
645 | { |
646 | GtkSorter *sorter; |
647 | |
648 | gtk_widget_init_template (GTK_WIDGET (rl)); |
649 | |
650 | g_signal_connect (rl, "map" , G_CALLBACK (on_map), NULL); |
651 | |
652 | gtk_search_bar_connect_entry (GTK_SEARCH_BAR (rl->search_bar), |
653 | GTK_EDITABLE (rl->search_entry)); |
654 | |
655 | sorter = GTK_SORTER (ptr: gtk_string_sorter_new (expression: gtk_cclosure_expression_new (G_TYPE_STRING, NULL, |
656 | n_params: 0, NULL, |
657 | callback_func: (GCallback)holder_name, |
658 | NULL, NULL))); |
659 | |
660 | gtk_column_view_column_set_sorter (self: rl->path, sorter); |
661 | g_object_unref (object: sorter); |
662 | |
663 | sorter = GTK_SORTER (ptr: gtk_numeric_sorter_new (expression: gtk_cclosure_expression_new (G_TYPE_INT, NULL, |
664 | n_params: 0, NULL, |
665 | callback_func: (GCallback)holder_count, |
666 | NULL, NULL))); |
667 | |
668 | gtk_column_view_column_set_sorter (self: rl->count, sorter); |
669 | g_object_unref (object: sorter); |
670 | |
671 | sorter = GTK_SORTER (ptr: gtk_numeric_sorter_new (expression: gtk_cclosure_expression_new (G_TYPE_UINT64, NULL, |
672 | n_params: 0, NULL, |
673 | callback_func: (GCallback)holder_size, |
674 | NULL, NULL))); |
675 | |
676 | gtk_column_view_column_set_sorter (self: rl->size, sorter); |
677 | g_object_unref (object: sorter); |
678 | } |
679 | |
680 | static GListModel * |
681 | create_model_for_object (gpointer item, gpointer data) |
682 | { |
683 | GListModel *model = resource_holder_get_children (holder: RESOURCE_HOLDER (ptr: item)); |
684 | |
685 | if (model) |
686 | return g_object_ref (model); |
687 | |
688 | return NULL; |
689 | } |
690 | |
691 | static void |
692 | constructed (GObject *object) |
693 | { |
694 | GtkInspectorResourceList *rl = GTK_INSPECTOR_RESOURCE_LIST (object); |
695 | GListModel *sort_model; |
696 | GtkSorter *column_sorter; |
697 | GtkSorter *sorter; |
698 | |
699 | g_signal_connect (rl->open_details_button, "clicked" , |
700 | G_CALLBACK (open_details), rl); |
701 | g_signal_connect (rl->close_details_button, "clicked" , |
702 | G_CALLBACK (close_details), rl); |
703 | |
704 | rl->tree_model = gtk_tree_list_model_new (root: load_resources (), |
705 | FALSE, |
706 | FALSE, |
707 | create_func: create_model_for_object, |
708 | NULL, |
709 | NULL); |
710 | |
711 | column_sorter = gtk_column_view_get_sorter (GTK_COLUMN_VIEW (rl->list)); |
712 | sorter = GTK_SORTER (ptr: gtk_tree_list_row_sorter_new (g_object_ref (column_sorter))); |
713 | sort_model = G_LIST_MODEL (ptr: gtk_sort_list_model_new (g_object_ref (G_LIST_MODEL (rl->tree_model)), sorter)); |
714 | rl->selection = gtk_single_selection_new (model: sort_model); |
715 | |
716 | gtk_column_view_set_model (GTK_COLUMN_VIEW (rl->list), model: GTK_SELECTION_MODEL (ptr: rl->selection)); |
717 | |
718 | g_signal_connect (rl->selection, "selection-changed" , G_CALLBACK (on_selection_changed), rl); |
719 | } |
720 | |
721 | static void |
722 | get_property (GObject *object, |
723 | guint param_id, |
724 | GValue *value, |
725 | GParamSpec *pspec) |
726 | { |
727 | GtkInspectorResourceList *rl = GTK_INSPECTOR_RESOURCE_LIST (object); |
728 | |
729 | switch (param_id) |
730 | { |
731 | case PROP_BUTTONS: |
732 | g_value_take_object (value, v_object: rl->buttons); |
733 | break; |
734 | |
735 | default: |
736 | G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec); |
737 | break; |
738 | } |
739 | } |
740 | |
741 | static void |
742 | set_property (GObject *object, |
743 | guint param_id, |
744 | const GValue *value, |
745 | GParamSpec *pspec) |
746 | { |
747 | GtkInspectorResourceList *rl = GTK_INSPECTOR_RESOURCE_LIST (object); |
748 | |
749 | switch (param_id) |
750 | { |
751 | case PROP_BUTTONS: |
752 | rl->buttons = g_value_get_object (value); |
753 | rl->open_details_button = gtk_stack_get_child_by_name (GTK_STACK (rl->buttons), name: "list" ); |
754 | rl->close_details_button = gtk_stack_get_child_by_name (GTK_STACK (rl->buttons), name: "details" ); |
755 | break; |
756 | |
757 | default: |
758 | G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec); |
759 | break; |
760 | } |
761 | } |
762 | |
763 | static void |
764 | dispose (GObject *object) |
765 | { |
766 | GtkInspectorResourceList *rl = GTK_INSPECTOR_RESOURCE_LIST (object); |
767 | |
768 | g_clear_pointer (&rl->stack, gtk_widget_unparent); |
769 | g_clear_object (&rl->selection); |
770 | g_clear_object (&rl->tree_model); |
771 | |
772 | G_OBJECT_CLASS (gtk_inspector_resource_list_parent_class)->dispose (object); |
773 | } |
774 | |
775 | static void |
776 | setup_name_cb (GtkSignalListItemFactory *factory, |
777 | GtkListItem *list_item) |
778 | { |
779 | GtkWidget *expander; |
780 | GtkWidget *label; |
781 | |
782 | expander = gtk_tree_expander_new (); |
783 | gtk_list_item_set_child (self: list_item, child: expander); |
784 | |
785 | label = gtk_label_new (NULL); |
786 | gtk_widget_set_margin_start (widget: label, margin: 5); |
787 | gtk_widget_set_margin_end (widget: label, margin: 5); |
788 | gtk_label_set_xalign (GTK_LABEL (label), xalign: 0.0); |
789 | gtk_tree_expander_set_child (self: GTK_TREE_EXPANDER (ptr: expander), child: label); |
790 | } |
791 | |
792 | static void |
793 | bind_name_cb (GtkSignalListItemFactory *factory, |
794 | GtkListItem *list_item) |
795 | { |
796 | GtkTreeListRow *list_row; |
797 | GtkWidget *expander; |
798 | GtkWidget *label; |
799 | gpointer item; |
800 | |
801 | list_row = gtk_list_item_get_item (self: list_item); |
802 | expander = gtk_list_item_get_child (self: list_item); |
803 | gtk_tree_expander_set_list_row (self: GTK_TREE_EXPANDER (ptr: expander), list_row); |
804 | item = gtk_tree_list_row_get_item (self: list_row); |
805 | label = gtk_tree_expander_get_child (self: GTK_TREE_EXPANDER (ptr: expander)); |
806 | |
807 | gtk_label_set_label (GTK_LABEL (label), str: resource_holder_get_name (holder: RESOURCE_HOLDER (ptr: item))); |
808 | g_object_unref (object: item); |
809 | } |
810 | |
811 | static void |
812 | setup_size_cb (GtkSignalListItemFactory *factory, |
813 | GtkListItem *list_item) |
814 | { |
815 | GtkWidget *label; |
816 | |
817 | label = gtk_label_new (NULL); |
818 | gtk_widget_set_margin_start (widget: label, margin: 5); |
819 | gtk_widget_set_margin_end (widget: label, margin: 5); |
820 | gtk_label_set_xalign (GTK_LABEL (label), xalign: 1.0); |
821 | gtk_list_item_set_child (self: list_item, child: label); |
822 | } |
823 | |
824 | static void |
825 | bind_size_cb (GtkSignalListItemFactory *factory, |
826 | GtkListItem *list_item) |
827 | { |
828 | GObject *item; |
829 | GtkWidget *label; |
830 | gsize size; |
831 | char *text; |
832 | |
833 | item = gtk_tree_list_row_get_item (self: gtk_list_item_get_item (self: list_item)); |
834 | label = gtk_list_item_get_child (self: list_item); |
835 | |
836 | size = resource_holder_get_size (holder: RESOURCE_HOLDER (ptr: item)); |
837 | text = g_format_size (size); |
838 | gtk_label_set_label (GTK_LABEL (label), str: text); |
839 | g_free (mem: text); |
840 | |
841 | g_object_unref (object: item); |
842 | } |
843 | |
844 | static void |
845 | setup_count_cb (GtkSignalListItemFactory *factory, |
846 | GtkListItem *list_item) |
847 | { |
848 | GtkWidget *label; |
849 | |
850 | label = gtk_label_new (NULL); |
851 | gtk_widget_set_margin_start (widget: label, margin: 5); |
852 | gtk_widget_set_margin_end (widget: label, margin: 5); |
853 | gtk_label_set_xalign (GTK_LABEL (label), xalign: 1.0); |
854 | gtk_list_item_set_child (self: list_item, child: label); |
855 | } |
856 | |
857 | static void |
858 | bind_count_cb (GtkSignalListItemFactory *factory, |
859 | GtkListItem *list_item) |
860 | { |
861 | GObject *item; |
862 | GtkWidget *label; |
863 | int count; |
864 | char *text; |
865 | |
866 | item = gtk_tree_list_row_get_item (self: gtk_list_item_get_item (self: list_item)); |
867 | label = gtk_list_item_get_child (self: list_item); |
868 | |
869 | count = resource_holder_get_count (holder: RESOURCE_HOLDER (ptr: item)); |
870 | if (count > 0) |
871 | { |
872 | text = g_strdup_printf (format: "%d" , count); |
873 | gtk_label_set_label (GTK_LABEL (label), str: text); |
874 | g_free (mem: text); |
875 | } |
876 | else |
877 | gtk_label_set_label (GTK_LABEL (label), str: "" ); |
878 | g_object_unref (object: item); |
879 | } |
880 | |
881 | static void |
882 | gtk_inspector_resource_list_class_init (GtkInspectorResourceListClass *klass) |
883 | { |
884 | GObjectClass *object_class = G_OBJECT_CLASS (klass); |
885 | GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); |
886 | |
887 | object_class->get_property = get_property; |
888 | object_class->set_property = set_property; |
889 | object_class->constructed = constructed; |
890 | object_class->dispose = dispose; |
891 | |
892 | widget_class->root = root; |
893 | widget_class->unroot = unroot; |
894 | |
895 | g_object_class_install_property (oclass: object_class, property_id: PROP_BUTTONS, |
896 | pspec: g_param_spec_object (name: "buttons" , NULL, NULL, |
897 | GTK_TYPE_WIDGET, flags: G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)); |
898 | |
899 | gtk_widget_class_set_template_from_resource (widget_class, resource_name: "/org/gtk/libgtk/inspector/resource-list.ui" ); |
900 | gtk_widget_class_bind_template_child (widget_class, GtkInspectorResourceList, buffer); |
901 | gtk_widget_class_bind_template_child (widget_class, GtkInspectorResourceList, content); |
902 | gtk_widget_class_bind_template_child (widget_class, GtkInspectorResourceList, image); |
903 | gtk_widget_class_bind_template_child (widget_class, GtkInspectorResourceList, name_label); |
904 | gtk_widget_class_bind_template_child (widget_class, GtkInspectorResourceList, type_label); |
905 | gtk_widget_class_bind_template_child (widget_class, GtkInspectorResourceList, type); |
906 | gtk_widget_class_bind_template_child (widget_class, GtkInspectorResourceList, size_label); |
907 | gtk_widget_class_bind_template_child (widget_class, GtkInspectorResourceList, info_grid); |
908 | gtk_widget_class_bind_template_child (widget_class, GtkInspectorResourceList, stack); |
909 | gtk_widget_class_bind_template_child (widget_class, GtkInspectorResourceList, search_bar); |
910 | gtk_widget_class_bind_template_child (widget_class, GtkInspectorResourceList, search_entry); |
911 | gtk_widget_class_bind_template_child (widget_class, GtkInspectorResourceList, list); |
912 | gtk_widget_class_bind_template_child (widget_class, GtkInspectorResourceList, path); |
913 | gtk_widget_class_bind_template_child (widget_class, GtkInspectorResourceList, count); |
914 | gtk_widget_class_bind_template_child (widget_class, GtkInspectorResourceList, size); |
915 | |
916 | gtk_widget_class_bind_template_callback (widget_class, on_search_changed); |
917 | gtk_widget_class_bind_template_callback (widget_class, on_row_activated); |
918 | gtk_widget_class_bind_template_callback (widget_class, next_match); |
919 | gtk_widget_class_bind_template_callback (widget_class, previous_match); |
920 | gtk_widget_class_bind_template_callback (widget_class, stop_search); |
921 | gtk_widget_class_bind_template_callback (widget_class, setup_name_cb); |
922 | gtk_widget_class_bind_template_callback (widget_class, bind_name_cb); |
923 | gtk_widget_class_bind_template_callback (widget_class, setup_count_cb); |
924 | gtk_widget_class_bind_template_callback (widget_class, bind_count_cb); |
925 | gtk_widget_class_bind_template_callback (widget_class, setup_size_cb); |
926 | gtk_widget_class_bind_template_callback (widget_class, bind_size_cb); |
927 | |
928 | gtk_widget_class_set_layout_manager_type (widget_class, GTK_TYPE_BIN_LAYOUT); |
929 | } |
930 | |
931 | // vim: set et sw=2 ts=2: |
932 | |