1 | #include <gtk/gtk.h> |
2 | |
3 | #define FILE_INFO_TYPE_SELECTION (file_info_selection_get_type ()) |
4 | |
5 | G_DECLARE_FINAL_TYPE (FileInfoSelection, file_info_selection, FILE_INFO, SELECTION, GObject) |
6 | |
7 | struct _FileInfoSelection |
8 | { |
9 | GObject parent_instance; |
10 | |
11 | GListModel *model; |
12 | }; |
13 | |
14 | struct _FileInfoSelectionClass |
15 | { |
16 | GObjectClass parent_class; |
17 | }; |
18 | |
19 | static GType |
20 | file_info_selection_get_item_type (GListModel *list) |
21 | { |
22 | FileInfoSelection *self = FILE_INFO_SELECTION (ptr: list); |
23 | |
24 | return g_list_model_get_item_type (list: self->model); |
25 | } |
26 | |
27 | static guint |
28 | file_info_selection_get_n_items (GListModel *list) |
29 | { |
30 | FileInfoSelection *self = FILE_INFO_SELECTION (ptr: list); |
31 | |
32 | return g_list_model_get_n_items (list: self->model); |
33 | } |
34 | |
35 | static gpointer |
36 | file_info_selection_get_item (GListModel *list, |
37 | guint position) |
38 | { |
39 | FileInfoSelection *self = FILE_INFO_SELECTION (ptr: list); |
40 | |
41 | return g_list_model_get_item (list: self->model, position); |
42 | } |
43 | |
44 | static void |
45 | file_info_selection_list_model_init (GListModelInterface *iface) |
46 | { |
47 | iface->get_item_type = file_info_selection_get_item_type; |
48 | iface->get_n_items = file_info_selection_get_n_items; |
49 | iface->get_item = file_info_selection_get_item; |
50 | } |
51 | |
52 | static gboolean |
53 | file_info_selection_is_selected (GtkSelectionModel *model, |
54 | guint position) |
55 | { |
56 | FileInfoSelection *self = FILE_INFO_SELECTION (ptr: model); |
57 | gpointer item; |
58 | |
59 | item = g_list_model_get_item (list: self->model, position); |
60 | if (item == NULL) |
61 | return FALSE; |
62 | |
63 | if (GTK_IS_TREE_LIST_ROW (ptr: item)) |
64 | { |
65 | GtkTreeListRow *row = item; |
66 | item = gtk_tree_list_row_get_item (self: row); |
67 | g_object_unref (object: row); |
68 | } |
69 | |
70 | return g_file_info_get_attribute_boolean (info: item, attribute: "filechooser::selected" ); |
71 | } |
72 | |
73 | static void |
74 | file_info_selection_set_selected (FileInfoSelection *self, |
75 | guint position, |
76 | gboolean selected) |
77 | { |
78 | gpointer item; |
79 | |
80 | item = g_list_model_get_item (list: self->model, position); |
81 | if (item == NULL) |
82 | return; |
83 | |
84 | if (GTK_IS_TREE_LIST_ROW (ptr: item)) |
85 | { |
86 | GtkTreeListRow *row = item; |
87 | item = gtk_tree_list_row_get_item (self: row); |
88 | g_object_unref (object: row); |
89 | } |
90 | |
91 | g_file_info_set_attribute_boolean (info: item, attribute: "filechooser::selected" , attr_value: selected); |
92 | } |
93 | |
94 | static gboolean |
95 | file_info_selection_select_item (GtkSelectionModel *model, |
96 | guint position, |
97 | gboolean exclusive) |
98 | { |
99 | FileInfoSelection *self = FILE_INFO_SELECTION (ptr: model); |
100 | |
101 | if (exclusive) |
102 | { |
103 | guint i; |
104 | |
105 | for (i = 0; i < g_list_model_get_n_items (list: self->model); i++) |
106 | file_info_selection_set_selected (self, position: i, selected: i == position); |
107 | |
108 | gtk_selection_model_selection_changed (model, position: 0, n_items: g_list_model_get_n_items (list: self->model)); |
109 | } |
110 | else |
111 | { |
112 | file_info_selection_set_selected (self, position, TRUE); |
113 | |
114 | gtk_selection_model_selection_changed (model, position, n_items: 1); |
115 | } |
116 | |
117 | return TRUE; |
118 | } |
119 | |
120 | static gboolean |
121 | file_info_selection_unselect_item (GtkSelectionModel *model, |
122 | guint position) |
123 | { |
124 | FileInfoSelection *self = FILE_INFO_SELECTION (ptr: model); |
125 | |
126 | file_info_selection_set_selected (self, position, FALSE); |
127 | |
128 | gtk_selection_model_selection_changed (model, position, n_items: 1); |
129 | |
130 | return TRUE; |
131 | } |
132 | |
133 | static gboolean |
134 | file_info_selection_select_range (GtkSelectionModel *model, |
135 | guint position, |
136 | guint n_items, |
137 | gboolean exclusive) |
138 | { |
139 | FileInfoSelection *self = FILE_INFO_SELECTION (ptr: model); |
140 | guint i; |
141 | |
142 | if (exclusive) |
143 | for (i = 0; i < position; i++) |
144 | file_info_selection_set_selected (self, position: i, FALSE); |
145 | |
146 | for (i = position; i < position + n_items; i++) |
147 | file_info_selection_set_selected (self, position: i, TRUE); |
148 | |
149 | if (exclusive) |
150 | for (i = position + n_items; i < g_list_model_get_n_items (list: self->model); i++) |
151 | file_info_selection_set_selected (self, position: i, FALSE); |
152 | |
153 | if (exclusive) |
154 | gtk_selection_model_selection_changed (model, position: 0, n_items: g_list_model_get_n_items (list: self->model)); |
155 | else |
156 | gtk_selection_model_selection_changed (model, position, n_items); |
157 | |
158 | return TRUE; |
159 | } |
160 | |
161 | static gboolean |
162 | file_info_selection_unselect_range (GtkSelectionModel *model, |
163 | guint position, |
164 | guint n_items) |
165 | { |
166 | FileInfoSelection *self = FILE_INFO_SELECTION (ptr: model); |
167 | guint i; |
168 | |
169 | for (i = position; i < position + n_items; i++) |
170 | file_info_selection_set_selected (self, position: i, FALSE); |
171 | |
172 | gtk_selection_model_selection_changed (model, position, n_items); |
173 | |
174 | return TRUE; |
175 | } |
176 | |
177 | static void |
178 | file_info_selection_selection_model_init (GtkSelectionModelInterface *iface) |
179 | { |
180 | iface->is_selected = file_info_selection_is_selected; |
181 | iface->select_item = file_info_selection_select_item; |
182 | iface->unselect_item = file_info_selection_unselect_item; |
183 | iface->select_range = file_info_selection_select_range; |
184 | iface->unselect_range = file_info_selection_unselect_range; |
185 | } |
186 | |
187 | G_DEFINE_TYPE_EXTENDED (FileInfoSelection, file_info_selection, G_TYPE_OBJECT, 0, |
188 | G_IMPLEMENT_INTERFACE (G_TYPE_LIST_MODEL, |
189 | file_info_selection_list_model_init) |
190 | G_IMPLEMENT_INTERFACE (GTK_TYPE_SELECTION_MODEL, |
191 | file_info_selection_selection_model_init)) |
192 | |
193 | static void |
194 | file_info_selection_items_changed_cb (GListModel *model, |
195 | guint position, |
196 | guint removed, |
197 | guint added, |
198 | FileInfoSelection *self) |
199 | { |
200 | g_list_model_items_changed (list: G_LIST_MODEL (ptr: self), position, removed, added); |
201 | } |
202 | |
203 | static void |
204 | file_info_selection_clear_model (FileInfoSelection *self) |
205 | { |
206 | if (self->model == NULL) |
207 | return; |
208 | |
209 | g_signal_handlers_disconnect_by_func (self->model, |
210 | file_info_selection_items_changed_cb, |
211 | self); |
212 | g_clear_object (&self->model); |
213 | } |
214 | |
215 | static void |
216 | file_info_selection_dispose (GObject *object) |
217 | { |
218 | FileInfoSelection *self = FILE_INFO_SELECTION (ptr: object); |
219 | |
220 | file_info_selection_clear_model (self); |
221 | |
222 | G_OBJECT_CLASS (file_info_selection_parent_class)->dispose (object); |
223 | } |
224 | |
225 | static void |
226 | file_info_selection_class_init (FileInfoSelectionClass *klass) |
227 | { |
228 | GObjectClass *gobject_class = G_OBJECT_CLASS (klass); |
229 | |
230 | gobject_class->dispose = file_info_selection_dispose; |
231 | } |
232 | |
233 | static void |
234 | file_info_selection_init (FileInfoSelection *self) |
235 | { |
236 | } |
237 | |
238 | static FileInfoSelection * |
239 | file_info_selection_new (GListModel *model) |
240 | { |
241 | FileInfoSelection *result; |
242 | |
243 | result = g_object_new (FILE_INFO_TYPE_SELECTION, NULL); |
244 | |
245 | result->model = g_object_ref (model); |
246 | g_signal_connect (result->model, "items-changed" , |
247 | G_CALLBACK (file_info_selection_items_changed_cb), result); |
248 | |
249 | return result; |
250 | } |
251 | |
252 | /*** ---------------------- ***/ |
253 | |
254 | GSList *pending = NULL; |
255 | guint active = 0; |
256 | |
257 | static void |
258 | loading_cb (GtkDirectoryList *dir, |
259 | GParamSpec *pspec, |
260 | gpointer unused) |
261 | { |
262 | if (gtk_directory_list_is_loading (self: dir)) |
263 | { |
264 | active++; |
265 | /* HACK: ensure loading finishes and the dir doesn't get destroyed */ |
266 | g_object_ref (dir); |
267 | } |
268 | else |
269 | { |
270 | active--; |
271 | g_object_unref (object: dir); |
272 | |
273 | while (active < 20 && pending) |
274 | { |
275 | GtkDirectoryList *dir2 = pending->data; |
276 | pending = g_slist_remove (list: pending, data: dir2); |
277 | gtk_directory_list_set_file (self: dir2, file: g_object_get_data (G_OBJECT (dir2), key: "file" )); |
278 | g_object_unref (object: dir2); |
279 | } |
280 | } |
281 | } |
282 | |
283 | static GtkDirectoryList * |
284 | create_directory_list (GFile *file) |
285 | { |
286 | GtkDirectoryList *dir; |
287 | |
288 | dir = gtk_directory_list_new (G_FILE_ATTRIBUTE_STANDARD_TYPE |
289 | "," G_FILE_ATTRIBUTE_STANDARD_NAME |
290 | "," G_FILE_ATTRIBUTE_STANDARD_DISPLAY_NAME, |
291 | NULL); |
292 | gtk_directory_list_set_io_priority (self: dir, G_PRIORITY_DEFAULT_IDLE); |
293 | g_signal_connect (dir, "notify::loading" , G_CALLBACK (loading_cb), NULL); |
294 | g_assert (!gtk_directory_list_is_loading (dir)); |
295 | |
296 | if (active > 20) |
297 | { |
298 | g_object_set_data_full (G_OBJECT (dir), key: "file" , g_object_ref (file), destroy: g_object_unref); |
299 | pending = g_slist_prepend (list: pending, g_object_ref (dir)); |
300 | } |
301 | else |
302 | { |
303 | gtk_directory_list_set_file (self: dir, file); |
304 | } |
305 | |
306 | return dir; |
307 | } |
308 | |
309 | static char * |
310 | get_file_path (GFileInfo *info) |
311 | { |
312 | GFile *file; |
313 | |
314 | file = G_FILE (g_file_info_get_attribute_object (info, "standard::file" )); |
315 | return g_file_get_path (file); |
316 | } |
317 | |
318 | static GListModel * |
319 | create_list_model_for_directory (gpointer file) |
320 | { |
321 | GtkDirectoryList *dir; |
322 | GtkSorter *sorter; |
323 | |
324 | if (g_file_query_file_type (file, flags: G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS, NULL) != G_FILE_TYPE_DIRECTORY) |
325 | return NULL; |
326 | |
327 | dir = create_directory_list (file); |
328 | sorter = GTK_SORTER (ptr: gtk_string_sorter_new (expression: gtk_cclosure_expression_new (G_TYPE_STRING, NULL, n_params: 0, NULL, callback_func: (GCallback) get_file_path, NULL, NULL))); |
329 | |
330 | return G_LIST_MODEL (ptr: gtk_sort_list_model_new (model: G_LIST_MODEL (ptr: dir), sorter)); |
331 | } |
332 | |
333 | typedef struct _RowData RowData; |
334 | struct _RowData |
335 | { |
336 | GtkWidget *expander; |
337 | GtkWidget *icon; |
338 | GtkWidget *name; |
339 | GCancellable *cancellable; |
340 | |
341 | GtkTreeListRow *current_item; |
342 | }; |
343 | |
344 | static void row_data_notify_item (GtkListItem *item, |
345 | GParamSpec *pspec, |
346 | RowData *data); |
347 | static void |
348 | row_data_unbind (RowData *data) |
349 | { |
350 | if (data->current_item == NULL) |
351 | return; |
352 | |
353 | if (data->cancellable) |
354 | { |
355 | g_cancellable_cancel (cancellable: data->cancellable); |
356 | g_clear_object (&data->cancellable); |
357 | } |
358 | |
359 | g_clear_object (&data->current_item); |
360 | } |
361 | |
362 | static void |
363 | row_data_update_info (RowData *data, |
364 | GFileInfo *info) |
365 | { |
366 | GIcon *icon; |
367 | const char *thumbnail_path; |
368 | |
369 | thumbnail_path = g_file_info_get_attribute_byte_string (info, G_FILE_ATTRIBUTE_THUMBNAIL_PATH); |
370 | if (thumbnail_path) |
371 | { |
372 | /* XXX: not async */ |
373 | GFile *thumbnail_file = g_file_new_for_path (path: thumbnail_path); |
374 | icon = g_file_icon_new (file: thumbnail_file); |
375 | g_object_unref (object: thumbnail_file); |
376 | } |
377 | else |
378 | { |
379 | icon = g_file_info_get_icon (info); |
380 | } |
381 | |
382 | gtk_widget_set_visible (widget: data->icon, visible: icon != NULL); |
383 | gtk_image_set_from_gicon (GTK_IMAGE (data->icon), icon); |
384 | } |
385 | |
386 | static void |
387 | copy_attribute (GFileInfo *to, |
388 | GFileInfo *from, |
389 | const char *attribute) |
390 | { |
391 | GFileAttributeType type; |
392 | gpointer value; |
393 | |
394 | if (g_file_info_get_attribute_data (info: from, attribute, type: &type, value_pp: &value, NULL)) |
395 | g_file_info_set_attribute (info: to, attribute, type, value_p: value); |
396 | } |
397 | |
398 | static void |
399 | row_data_got_thumbnail_info_cb (GObject *source, |
400 | GAsyncResult *res, |
401 | gpointer _data) |
402 | { |
403 | RowData *data = _data; /* invalid if operation was cancelled */ |
404 | GFile *file = G_FILE (source); |
405 | GFileInfo *queried, *info; |
406 | |
407 | queried = g_file_query_info_finish (file, res, NULL); |
408 | if (queried == NULL) |
409 | return; |
410 | |
411 | /* now we know row is valid */ |
412 | |
413 | info = gtk_tree_list_row_get_item (self: data->current_item); |
414 | |
415 | copy_attribute (to: info, from: queried, G_FILE_ATTRIBUTE_THUMBNAIL_PATH); |
416 | copy_attribute (to: info, from: queried, G_FILE_ATTRIBUTE_THUMBNAILING_FAILED); |
417 | copy_attribute (to: info, from: queried, G_FILE_ATTRIBUTE_STANDARD_ICON); |
418 | |
419 | g_object_unref (object: queried); |
420 | |
421 | row_data_update_info (data, info); |
422 | |
423 | g_clear_object (&data->cancellable); |
424 | } |
425 | |
426 | static void |
427 | row_data_bind (RowData *data, |
428 | GtkTreeListRow *item) |
429 | { |
430 | GFileInfo *info; |
431 | |
432 | row_data_unbind (data); |
433 | |
434 | if (item == NULL) |
435 | return; |
436 | |
437 | data->current_item = g_object_ref (item); |
438 | |
439 | gtk_tree_expander_set_list_row (self: GTK_TREE_EXPANDER (ptr: data->expander), list_row: item); |
440 | |
441 | info = gtk_tree_list_row_get_item (self: item); |
442 | |
443 | if (!g_file_info_has_attribute (info, attribute: "filechooser::queried" )) |
444 | { |
445 | data->cancellable = g_cancellable_new (); |
446 | g_file_info_set_attribute_boolean (info, attribute: "filechooser::queried" , TRUE); |
447 | g_file_query_info_async (G_FILE (g_file_info_get_attribute_object (info, "standard::file" )), |
448 | G_FILE_ATTRIBUTE_THUMBNAIL_PATH "," |
449 | G_FILE_ATTRIBUTE_THUMBNAILING_FAILED "," |
450 | G_FILE_ATTRIBUTE_STANDARD_ICON, |
451 | flags: G_FILE_QUERY_INFO_NONE, |
452 | G_PRIORITY_DEFAULT, |
453 | cancellable: data->cancellable, |
454 | callback: row_data_got_thumbnail_info_cb, |
455 | user_data: data); |
456 | } |
457 | |
458 | row_data_update_info (data, info); |
459 | |
460 | gtk_label_set_label (GTK_LABEL (data->name), str: g_file_info_get_display_name (info)); |
461 | |
462 | g_object_unref (object: info); |
463 | } |
464 | |
465 | static void |
466 | row_data_notify_item (GtkListItem *item, |
467 | GParamSpec *pspec, |
468 | RowData *data) |
469 | { |
470 | row_data_bind (data, item: gtk_list_item_get_item (self: item)); |
471 | } |
472 | |
473 | static void |
474 | row_data_free (gpointer _data) |
475 | { |
476 | RowData *data = _data; |
477 | |
478 | row_data_unbind (data); |
479 | |
480 | g_slice_free (RowData, data); |
481 | } |
482 | |
483 | static void |
484 | setup_widget (GtkSignalListItemFactory *factory, |
485 | GtkListItem *list_item) |
486 | { |
487 | GtkWidget *box, *child; |
488 | RowData *data; |
489 | |
490 | data = g_slice_new0 (RowData); |
491 | |
492 | box = gtk_box_new (orientation: GTK_ORIENTATION_HORIZONTAL, spacing: 4); |
493 | gtk_list_item_set_child (self: list_item, child: box); |
494 | |
495 | child = gtk_label_new (NULL); |
496 | gtk_label_set_width_chars (GTK_LABEL (child), n_chars: 5); |
497 | gtk_box_append (GTK_BOX (box), child); |
498 | |
499 | data->expander = gtk_tree_expander_new (); |
500 | gtk_box_append (GTK_BOX (box), child: data->expander); |
501 | |
502 | box = gtk_box_new (orientation: GTK_ORIENTATION_HORIZONTAL, spacing: 4); |
503 | gtk_tree_expander_set_child (self: GTK_TREE_EXPANDER (ptr: data->expander), child: box); |
504 | |
505 | data->icon = gtk_image_new (); |
506 | gtk_box_append (GTK_BOX (box), child: data->icon); |
507 | |
508 | data->name = gtk_label_new (NULL); |
509 | gtk_label_set_max_width_chars (GTK_LABEL (data->name), n_chars: 25); |
510 | gtk_label_set_ellipsize (GTK_LABEL (data->name), mode: PANGO_ELLIPSIZE_END); |
511 | gtk_box_append (GTK_BOX (box), child: data->name); |
512 | |
513 | g_signal_connect (list_item, "notify::item" , G_CALLBACK (row_data_notify_item), data); |
514 | g_object_set_data_full (G_OBJECT (list_item), key: "row-data" , data, destroy: row_data_free); |
515 | } |
516 | |
517 | static GListModel * |
518 | create_list_model_for_file_info (gpointer file_info, |
519 | gpointer unused) |
520 | { |
521 | GFile *file = G_FILE (g_file_info_get_attribute_object (file_info, "standard::file" )); |
522 | |
523 | if (file == NULL) |
524 | return NULL; |
525 | |
526 | return create_list_model_for_directory (file); |
527 | } |
528 | |
529 | static gboolean |
530 | update_statusbar (GtkStatusbar *statusbar) |
531 | { |
532 | GListModel *model = g_object_get_data (G_OBJECT (statusbar), key: "model" ); |
533 | GString *string = g_string_new (NULL); |
534 | guint n; |
535 | gboolean result = G_SOURCE_REMOVE; |
536 | |
537 | gtk_statusbar_remove_all (statusbar, context_id: 0); |
538 | |
539 | n = g_list_model_get_n_items (list: model); |
540 | g_string_append_printf (string, format: "%u" , n); |
541 | if (GTK_IS_FILTER_LIST_MODEL (ptr: model)) |
542 | { |
543 | guint n_unfiltered = g_list_model_get_n_items (list: gtk_filter_list_model_get_model (self: GTK_FILTER_LIST_MODEL (ptr: model))); |
544 | if (n != n_unfiltered) |
545 | g_string_append_printf (string, format: "/%u" , n_unfiltered); |
546 | } |
547 | g_string_append (string, val: " items" ); |
548 | |
549 | if (pending || active) |
550 | { |
551 | g_string_append_printf (string, format: " (%u directories remaining)" , active + g_slist_length (list: pending)); |
552 | result = G_SOURCE_CONTINUE; |
553 | } |
554 | result = G_SOURCE_CONTINUE; |
555 | |
556 | gtk_statusbar_push (statusbar, context_id: 0, text: string->str); |
557 | g_free (mem: string->str); |
558 | |
559 | return result; |
560 | } |
561 | |
562 | static gboolean |
563 | match_file (gpointer item, gpointer data) |
564 | { |
565 | GtkWidget *search_entry = data; |
566 | GFileInfo *info = gtk_tree_list_row_get_item (self: item); |
567 | GFile *file = G_FILE (g_file_info_get_attribute_object (info, "standard::file" )); |
568 | char *path; |
569 | gboolean result; |
570 | |
571 | path = g_file_get_path (file); |
572 | |
573 | result = strstr (haystack: path, needle: gtk_editable_get_text (GTK_EDITABLE (search_entry))) != NULL; |
574 | |
575 | g_object_unref (object: info); |
576 | g_free (mem: path); |
577 | |
578 | return result; |
579 | } |
580 | |
581 | static void |
582 | search_changed_cb (GtkSearchEntry *entry, |
583 | GtkFilter *custom_filter) |
584 | { |
585 | gtk_filter_changed (self: custom_filter, change: GTK_FILTER_CHANGE_DIFFERENT); |
586 | } |
587 | |
588 | int |
589 | main (int argc, char *argv[]) |
590 | { |
591 | GtkWidget *win, *vbox, *sw, *listview, *search_entry, *statusbar; |
592 | GtkTreeListModel *tree; |
593 | GtkFilterListModel *filter; |
594 | GtkFilter *custom_filter; |
595 | FileInfoSelection *selectionmodel; |
596 | GFile *root; |
597 | GListModel *toplevels; |
598 | GtkListItemFactory *factory; |
599 | |
600 | gtk_init (); |
601 | |
602 | win = gtk_window_new (); |
603 | gtk_window_set_default_size (GTK_WINDOW (win), width: 400, height: 600); |
604 | |
605 | vbox = gtk_box_new (orientation: GTK_ORIENTATION_VERTICAL, spacing: 0); |
606 | gtk_window_set_child (GTK_WINDOW (win), child: vbox); |
607 | |
608 | search_entry = gtk_search_entry_new (); |
609 | gtk_box_append (GTK_BOX (vbox), child: search_entry); |
610 | |
611 | sw = gtk_scrolled_window_new (); |
612 | gtk_widget_set_vexpand (widget: sw, TRUE); |
613 | gtk_search_entry_set_key_capture_widget (GTK_SEARCH_ENTRY (search_entry), widget: sw); |
614 | gtk_box_append (GTK_BOX (vbox), child: sw); |
615 | |
616 | factory = gtk_signal_list_item_factory_new (); |
617 | g_signal_connect (factory, "setup" , G_CALLBACK (setup_widget), NULL); |
618 | listview = gtk_list_view_new (NULL, factory); |
619 | gtk_scrolled_window_set_child (GTK_SCROLLED_WINDOW (sw), child: listview); |
620 | |
621 | if (argc > 1) |
622 | root = g_file_new_for_commandline_arg (arg: argv[1]); |
623 | else |
624 | root = g_file_new_for_path (path: g_get_current_dir ()); |
625 | tree = gtk_tree_list_model_new (root: create_list_model_for_directory (file: root), |
626 | FALSE, |
627 | TRUE, |
628 | create_func: create_list_model_for_file_info, |
629 | NULL, NULL); |
630 | g_object_unref (object: root); |
631 | |
632 | custom_filter = GTK_FILTER (ptr: gtk_custom_filter_new (match_func: match_file, user_data: search_entry, NULL)); |
633 | filter = gtk_filter_list_model_new (model: G_LIST_MODEL (ptr: tree), filter: custom_filter); |
634 | g_signal_connect (search_entry, "search-changed" , G_CALLBACK (search_changed_cb), custom_filter); |
635 | |
636 | selectionmodel = file_info_selection_new (model: G_LIST_MODEL (ptr: filter)); |
637 | g_object_unref (object: filter); |
638 | |
639 | gtk_list_view_set_model (GTK_LIST_VIEW (listview), model: GTK_SELECTION_MODEL (ptr: selectionmodel)); |
640 | |
641 | statusbar = gtk_statusbar_new (); |
642 | gtk_widget_add_tick_callback (widget: statusbar, callback: (GtkTickCallback) update_statusbar, NULL, NULL); |
643 | g_object_set_data (G_OBJECT (statusbar), key: "model" , data: filter); |
644 | g_signal_connect_swapped (filter, "items-changed" , G_CALLBACK (update_statusbar), statusbar); |
645 | update_statusbar (GTK_STATUSBAR (statusbar)); |
646 | gtk_box_append (GTK_BOX (vbox), child: statusbar); |
647 | |
648 | g_object_unref (object: selectionmodel); |
649 | |
650 | gtk_widget_show (widget: win); |
651 | |
652 | toplevels = gtk_window_get_toplevels (); |
653 | while (g_list_model_get_n_items (list: toplevels)) |
654 | g_main_context_iteration (NULL, TRUE); |
655 | |
656 | return 0; |
657 | } |
658 | |