1 | #include <gtk/gtk.h> |
2 | |
3 | GSList *pending = NULL; |
4 | guint active = 0; |
5 | |
6 | static void |
7 | loading_cb (GtkDirectoryList *dir, |
8 | GParamSpec *pspec, |
9 | gpointer unused) |
10 | { |
11 | if (gtk_directory_list_is_loading (self: dir)) |
12 | { |
13 | active++; |
14 | /* HACK: ensure loading finishes and the dir doesn't get destroyed */ |
15 | g_object_ref (dir); |
16 | } |
17 | else |
18 | { |
19 | active--; |
20 | g_object_unref (object: dir); |
21 | |
22 | while (active < 20 && pending) |
23 | { |
24 | GtkDirectoryList *dir2 = pending->data; |
25 | pending = g_slist_remove (list: pending, data: dir2); |
26 | gtk_directory_list_set_file (self: dir2, file: g_object_get_data (G_OBJECT (dir2), key: "file" )); |
27 | g_object_unref (object: dir2); |
28 | } |
29 | } |
30 | } |
31 | |
32 | static GtkDirectoryList * |
33 | create_directory_list (GFile *file) |
34 | { |
35 | GtkDirectoryList *dir; |
36 | |
37 | dir = gtk_directory_list_new (attributes: "*" , |
38 | NULL); |
39 | gtk_directory_list_set_io_priority (self: dir, G_PRIORITY_DEFAULT_IDLE); |
40 | g_signal_connect (dir, "notify::loading" , G_CALLBACK (loading_cb), NULL); |
41 | g_assert (!gtk_directory_list_is_loading (dir)); |
42 | |
43 | if (active > 20) |
44 | { |
45 | g_object_set_data_full (G_OBJECT (dir), key: "file" , g_object_ref (file), destroy: g_object_unref); |
46 | pending = g_slist_prepend (list: pending, g_object_ref (dir)); |
47 | } |
48 | else |
49 | { |
50 | gtk_directory_list_set_file (self: dir, file); |
51 | } |
52 | |
53 | return dir; |
54 | } |
55 | |
56 | static GListModel * |
57 | create_list_model_for_directory (gpointer file) |
58 | { |
59 | if (g_file_query_file_type (file, flags: G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS, NULL) != G_FILE_TYPE_DIRECTORY) |
60 | return NULL; |
61 | |
62 | return G_LIST_MODEL (ptr: create_directory_list (file)); |
63 | } |
64 | |
65 | static GListModel * |
66 | create_recent_files_list (void) |
67 | { |
68 | GtkBookmarkList *dir; |
69 | |
70 | dir = gtk_bookmark_list_new (NULL, attributes: "*" ); |
71 | |
72 | return G_LIST_MODEL (ptr: dir); |
73 | } |
74 | |
75 | #if 0 |
76 | typedef struct _RowData RowData; |
77 | struct _RowData |
78 | { |
79 | GtkWidget *expander; |
80 | GtkWidget *icon; |
81 | GtkWidget *name; |
82 | GCancellable *cancellable; |
83 | |
84 | GtkTreeListRow *current_item; |
85 | }; |
86 | |
87 | static void row_data_notify_item (GtkListItem *item, |
88 | GParamSpec *pspec, |
89 | RowData *data); |
90 | static void |
91 | row_data_unbind (RowData *data) |
92 | { |
93 | if (data->current_item == NULL) |
94 | return; |
95 | |
96 | if (data->cancellable) |
97 | { |
98 | g_cancellable_cancel (data->cancellable); |
99 | g_clear_object (&data->cancellable); |
100 | } |
101 | |
102 | g_clear_object (&data->current_item); |
103 | } |
104 | |
105 | static void |
106 | row_data_update_info (RowData *data, |
107 | GFileInfo *info) |
108 | { |
109 | GIcon *icon; |
110 | const char *thumbnail_path; |
111 | |
112 | thumbnail_path = g_file_info_get_attribute_byte_string (info, G_FILE_ATTRIBUTE_THUMBNAIL_PATH); |
113 | if (thumbnail_path) |
114 | { |
115 | /* XXX: not async */ |
116 | GFile *thumbnail_file = g_file_new_for_path (thumbnail_path); |
117 | icon = g_file_icon_new (thumbnail_file); |
118 | g_object_unref (thumbnail_file); |
119 | } |
120 | else |
121 | { |
122 | icon = g_file_info_get_icon (info); |
123 | } |
124 | |
125 | gtk_widget_set_visible (data->icon, icon != NULL); |
126 | gtk_image_set_from_gicon (GTK_IMAGE (data->icon), icon); |
127 | } |
128 | |
129 | static void |
130 | copy_attribute (GFileInfo *to, |
131 | GFileInfo *from, |
132 | const char *attribute) |
133 | { |
134 | GFileAttributeType type; |
135 | gpointer value; |
136 | |
137 | if (g_file_info_get_attribute_data (from, attribute, &type, &value, NULL)) |
138 | g_file_info_set_attribute (to, attribute, type, value); |
139 | } |
140 | |
141 | static void |
142 | row_data_got_thumbnail_info_cb (GObject *source, |
143 | GAsyncResult *res, |
144 | gpointer _data) |
145 | { |
146 | RowData *data = _data; /* invalid if operation was cancelled */ |
147 | GFile *file = G_FILE (source); |
148 | GFileInfo *queried, *info; |
149 | |
150 | queried = g_file_query_info_finish (file, res, NULL); |
151 | if (queried == NULL) |
152 | return; |
153 | |
154 | /* now we know row is valid */ |
155 | |
156 | info = gtk_tree_list_row_get_item (data->current_item); |
157 | |
158 | copy_attribute (info, queried, G_FILE_ATTRIBUTE_THUMBNAIL_PATH); |
159 | copy_attribute (info, queried, G_FILE_ATTRIBUTE_THUMBNAILING_FAILED); |
160 | copy_attribute (info, queried, G_FILE_ATTRIBUTE_STANDARD_ICON); |
161 | |
162 | g_object_unref (queried); |
163 | |
164 | row_data_update_info (data, info); |
165 | |
166 | g_clear_object (&data->cancellable); |
167 | } |
168 | |
169 | static void |
170 | row_data_bind (RowData *data, |
171 | GtkTreeListRow *item) |
172 | { |
173 | GFileInfo *info; |
174 | |
175 | row_data_unbind (data); |
176 | |
177 | if (item == NULL) |
178 | return; |
179 | |
180 | data->current_item = g_object_ref (item); |
181 | |
182 | gtk_tree_expander_set_list_row (GTK_TREE_EXPANDER (data->expander), item); |
183 | |
184 | info = gtk_tree_list_row_get_item (item); |
185 | |
186 | if (!g_file_info_has_attribute (info, "filechooser::queried" )) |
187 | { |
188 | data->cancellable = g_cancellable_new (); |
189 | g_file_info_set_attribute_boolean (info, "filechooser::queried" , TRUE); |
190 | g_file_query_info_async (G_FILE (g_file_info_get_attribute_object (info, "standard::file" )), |
191 | G_FILE_ATTRIBUTE_THUMBNAIL_PATH "," |
192 | G_FILE_ATTRIBUTE_THUMBNAILING_FAILED "," |
193 | G_FILE_ATTRIBUTE_STANDARD_ICON, |
194 | G_FILE_QUERY_INFO_NONE, |
195 | G_PRIORITY_DEFAULT, |
196 | data->cancellable, |
197 | row_data_got_thumbnail_info_cb, |
198 | data); |
199 | } |
200 | |
201 | row_data_update_info (data, info); |
202 | |
203 | gtk_label_set_label (GTK_LABEL (data->name), g_file_info_get_display_name (info)); |
204 | |
205 | g_object_unref (info); |
206 | } |
207 | |
208 | static void |
209 | row_data_notify_item (GtkListItem *item, |
210 | GParamSpec *pspec, |
211 | RowData *data) |
212 | { |
213 | row_data_bind (data, gtk_list_item_get_item (item)); |
214 | } |
215 | |
216 | static void |
217 | row_data_free (gpointer _data) |
218 | { |
219 | RowData *data = _data; |
220 | |
221 | row_data_unbind (data); |
222 | |
223 | g_slice_free (RowData, data); |
224 | } |
225 | |
226 | static void |
227 | setup_widget (GtkListItem *list_item, |
228 | gpointer unused) |
229 | { |
230 | GtkWidget *box, *child; |
231 | RowData *data; |
232 | |
233 | data = g_slice_new0 (RowData); |
234 | g_signal_connect (list_item, "notify::item" , G_CALLBACK (row_data_notify_item), data); |
235 | g_object_set_data_full (G_OBJECT (list_item), "row-data" , data, row_data_free); |
236 | |
237 | box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 4); |
238 | gtk_container_add (GTK_CONTAINER (list_item), box); |
239 | |
240 | child = gtk_label_new (NULL); |
241 | gtk_label_set_width_chars (GTK_LABEL (child), 5); |
242 | gtk_label_set_xalign (GTK_LABEL (child), 1.0); |
243 | g_object_bind_property (list_item, "position" , child, "label" , G_BINDING_SYNC_CREATE); |
244 | gtk_container_add (GTK_CONTAINER (box), child); |
245 | |
246 | data->expander = gtk_tree_expander_new (); |
247 | gtk_container_add (GTK_CONTAINER (box), data->expander); |
248 | |
249 | box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 4); |
250 | gtk_tree_expander_set_child (GTK_TREE_EXPANDER (data->expander), box); |
251 | |
252 | data->icon = gtk_image_new (); |
253 | gtk_container_add (GTK_CONTAINER (box), data->icon); |
254 | |
255 | data->name = gtk_label_new (NULL); |
256 | gtk_label_set_max_width_chars (GTK_LABEL (data->name), 25); |
257 | gtk_label_set_ellipsize (GTK_LABEL (data->name), PANGO_ELLIPSIZE_END); |
258 | gtk_container_add (GTK_CONTAINER (box), data->name); |
259 | } |
260 | #endif |
261 | |
262 | static GListModel * |
263 | create_list_model_for_file_info (gpointer file_info, |
264 | gpointer unused) |
265 | { |
266 | GFile *file = G_FILE (g_file_info_get_attribute_object (file_info, "standard::file" )); |
267 | |
268 | if (file == NULL) |
269 | return NULL; |
270 | |
271 | return create_list_model_for_directory (file); |
272 | } |
273 | |
274 | static gboolean |
275 | update_statusbar (GtkStatusbar *statusbar) |
276 | { |
277 | GListModel *model = g_object_get_data (G_OBJECT (statusbar), key: "model" ); |
278 | GString *string = g_string_new (NULL); |
279 | guint n; |
280 | gboolean result = G_SOURCE_REMOVE; |
281 | |
282 | gtk_statusbar_remove_all (statusbar, context_id: 0); |
283 | |
284 | n = g_list_model_get_n_items (list: model); |
285 | g_string_append_printf (string, format: "%u" , n); |
286 | if (GTK_IS_FILTER_LIST_MODEL (ptr: model)) |
287 | { |
288 | guint n_unfiltered = g_list_model_get_n_items (list: gtk_filter_list_model_get_model (self: GTK_FILTER_LIST_MODEL (ptr: model))); |
289 | if (n != n_unfiltered) |
290 | g_string_append_printf (string, format: "/%u" , n_unfiltered); |
291 | } |
292 | g_string_append (string, val: " items" ); |
293 | |
294 | if (pending || active) |
295 | { |
296 | g_string_append_printf (string, format: " (%u directories remaining)" , active + g_slist_length (list: pending)); |
297 | result = G_SOURCE_CONTINUE; |
298 | } |
299 | result = G_SOURCE_CONTINUE; |
300 | |
301 | gtk_statusbar_push (statusbar, context_id: 0, text: string->str); |
302 | g_free (mem: string->str); |
303 | |
304 | return result; |
305 | } |
306 | |
307 | static gboolean |
308 | match_file (gpointer item, gpointer data) |
309 | { |
310 | GtkWidget *search_entry = data; |
311 | GFileInfo *info = gtk_tree_list_row_get_item (self: item); |
312 | GFile *file = G_FILE (g_file_info_get_attribute_object (info, "standard::file" )); |
313 | char *path; |
314 | gboolean result; |
315 | |
316 | path = g_file_get_path (file); |
317 | |
318 | result = strstr (haystack: path, needle: gtk_editable_get_text (GTK_EDITABLE (search_entry))) != NULL; |
319 | |
320 | g_object_unref (object: info); |
321 | g_free (mem: path); |
322 | |
323 | return result; |
324 | } |
325 | |
326 | static int |
327 | compare_file_attribute (gconstpointer info1_, |
328 | gconstpointer info2_, |
329 | gpointer data) |
330 | { |
331 | GFileInfo *info1 = (gpointer) info1_; |
332 | GFileInfo *info2 = (gpointer) info2_; |
333 | const char *attribute = data; |
334 | GFileAttributeType type1, type2; |
335 | |
336 | type1 = g_file_info_get_attribute_type (info: info1, attribute); |
337 | type2 = g_file_info_get_attribute_type (info: info2, attribute); |
338 | if (type1 != type2) |
339 | return (int) type2 - (int) type1; |
340 | |
341 | switch (type1) |
342 | { |
343 | case G_FILE_ATTRIBUTE_TYPE_INVALID: |
344 | case G_FILE_ATTRIBUTE_TYPE_OBJECT: |
345 | case G_FILE_ATTRIBUTE_TYPE_STRINGV: |
346 | return 0; |
347 | case G_FILE_ATTRIBUTE_TYPE_STRING: |
348 | return g_utf8_collate (str1: g_file_info_get_attribute_string (info: info1, attribute), |
349 | str2: g_file_info_get_attribute_string (info: info2, attribute)); |
350 | case G_FILE_ATTRIBUTE_TYPE_BYTE_STRING: |
351 | return strcmp (s1: g_file_info_get_attribute_byte_string (info: info1, attribute), |
352 | s2: g_file_info_get_attribute_byte_string (info: info2, attribute)); |
353 | case G_FILE_ATTRIBUTE_TYPE_BOOLEAN: |
354 | return g_file_info_get_attribute_boolean (info: info1, attribute) |
355 | - g_file_info_get_attribute_boolean (info: info2, attribute); |
356 | case G_FILE_ATTRIBUTE_TYPE_UINT32: |
357 | return g_file_info_get_attribute_uint32 (info: info1, attribute) |
358 | - g_file_info_get_attribute_uint32 (info: info2, attribute); |
359 | case G_FILE_ATTRIBUTE_TYPE_INT32: |
360 | return g_file_info_get_attribute_int32 (info: info1, attribute) |
361 | - g_file_info_get_attribute_int32 (info: info2, attribute); |
362 | case G_FILE_ATTRIBUTE_TYPE_UINT64: |
363 | return g_file_info_get_attribute_uint64 (info: info1, attribute) |
364 | - g_file_info_get_attribute_uint64 (info: info2, attribute); |
365 | case G_FILE_ATTRIBUTE_TYPE_INT64: |
366 | return g_file_info_get_attribute_int64 (info: info1, attribute) |
367 | - g_file_info_get_attribute_int64 (info: info2, attribute); |
368 | default: |
369 | g_assert_not_reached (); |
370 | return 0; |
371 | } |
372 | } |
373 | |
374 | static GObject * |
375 | get_object (GObject *unused, |
376 | GFileInfo *info, |
377 | const char *attribute) |
378 | { |
379 | GObject *o; |
380 | |
381 | if (info == NULL) |
382 | return NULL; |
383 | |
384 | o = g_file_info_get_attribute_object (info, attribute); |
385 | if (o) |
386 | g_object_ref (o); |
387 | |
388 | return o; |
389 | } |
390 | |
391 | static char * |
392 | get_string (GObject *unused, |
393 | GFileInfo *info, |
394 | const char *attribute) |
395 | { |
396 | if (info == NULL) |
397 | return NULL; |
398 | |
399 | return g_file_info_get_attribute_as_string (info, attribute); |
400 | } |
401 | |
402 | static gboolean |
403 | get_boolean (GObject *unused, |
404 | GFileInfo *info, |
405 | const char *attribute) |
406 | { |
407 | if (info == NULL) |
408 | return FALSE; |
409 | |
410 | return g_file_info_get_attribute_boolean (info, attribute); |
411 | } |
412 | |
413 | const char *ui_file = |
414 | "<?xml version='1.0' encoding='UTF-8'?>\n" |
415 | "<interface>\n" |
416 | " <object class='GtkColumnView' id='view'>\n" |
417 | " <child>\n" |
418 | " <object class='GtkColumnViewColumn'>\n" |
419 | " <property name='title'>Name</property>\n" |
420 | " <property name='factory'>\n" |
421 | " <object class='GtkBuilderListItemFactory'>\n" |
422 | " <property name='bytes'><![CDATA[\n" |
423 | "<?xml version='1.0' encoding='UTF-8'?>\n" |
424 | "<interface>\n" |
425 | " <template class='GtkListItem'>\n" |
426 | " <property name='child'>\n" |
427 | " <object class='GtkTreeExpander' id='expander'>\n" |
428 | " <binding name='list-row'>\n" |
429 | " <lookup name='item'>GtkListItem</lookup>\n" |
430 | " </binding>\n" |
431 | " <property name='child'>\n" |
432 | " <object class='GtkBox'>\n" |
433 | " <child>\n" |
434 | " <object class='GtkImage'>\n" |
435 | " <binding name='gicon'>\n" |
436 | " <closure type='GIcon' function='get_object'>\n" |
437 | " <lookup name='item'>expander</lookup>\n" |
438 | " <constant type='gchararray'>standard::icon</constant>" |
439 | " </closure>\n" |
440 | " </binding>\n" |
441 | " </object>\n" |
442 | " </child>\n" |
443 | " <child>\n" |
444 | " <object class='GtkLabel'>\n" |
445 | " <property name='halign'>start</property>\n" |
446 | " <property name='label'>start</property>\n" |
447 | " <binding name='label'>\n" |
448 | " <closure type='gchararray' function='get_string'>\n" |
449 | " <lookup name='item'>expander</lookup>\n" |
450 | " <constant type='gchararray'>standard::display-name</constant>" |
451 | " </closure>\n" |
452 | " </binding>\n" |
453 | " </object>\n" |
454 | " </child>\n" |
455 | " </object>\n" |
456 | " </property>\n" |
457 | " </object>\n" |
458 | " </property>\n" |
459 | " </template>\n" |
460 | "</interface>\n" |
461 | " ]]></property>\n" |
462 | " </object>\n" |
463 | " </property>\n" |
464 | " <property name='sorter'>\n" |
465 | " <object class='GtkStringSorter'>\n" |
466 | " <property name='expression'>\n" |
467 | " <closure type='gchararray' function='g_file_info_get_attribute_as_string'>\n" |
468 | " <constant type='gchararray'>standard::display-name</constant>" |
469 | " </closure>\n" |
470 | " </property>\n" |
471 | " </object>\n" |
472 | " </property>\n" |
473 | " </object>\n" |
474 | " </child>\n" |
475 | " </object>\n" |
476 | "</interface>\n" ; |
477 | |
478 | #define SIMPLE_STRING_FACTORY(attr, type) \ |
479 | "<?xml version='1.0' encoding='UTF-8'?>\n" \ |
480 | "<interface>\n" \ |
481 | " <template class='GtkListItem'>\n" \ |
482 | " <property name='child'>\n" \ |
483 | " <object class='GtkLabel'>\n" \ |
484 | " <property name='halign'>start</property>\n" \ |
485 | " <binding name='label'>\n" \ |
486 | " <closure type='gchararray' function='get_string'>\n" \ |
487 | " <lookup name='item' type='GtkTreeListRow'><lookup name='item'>GtkListItem</lookup></lookup>\n" \ |
488 | " <constant type='gchararray'>" attr "</constant>" \ |
489 | " </closure>\n" \ |
490 | " </binding>\n" \ |
491 | " </object>\n" \ |
492 | " </property>\n" \ |
493 | " </template>\n" \ |
494 | "</interface>\n" \ |
495 | |
496 | #define BOOLEAN_FACTORY(attr) \ |
497 | "<?xml version='1.0' encoding='UTF-8'?>\n" \ |
498 | "<interface>\n" \ |
499 | " <template class='GtkListItem'>\n" \ |
500 | " <property name='child'>\n" \ |
501 | " <object class='GtkCheckButton'>\n" \ |
502 | " <binding name='active'>\n" \ |
503 | " <closure type='gboolean' function='get_boolean'>\n" \ |
504 | " <lookup name='item' type='GtkTreeListRow'><lookup name='item'>GtkListItem</lookup></lookup>\n" \ |
505 | " <constant type='gchararray'>" attr "</constant>" \ |
506 | " </closure>\n" \ |
507 | " </binding>\n" \ |
508 | " </object>\n" \ |
509 | " </property>\n" \ |
510 | " </template>\n" \ |
511 | "</interface>\n" \ |
512 | |
513 | #define ICON_FACTORY(attr) \ |
514 | "<?xml version='1.0' encoding='UTF-8'?>\n" \ |
515 | "<interface>\n" \ |
516 | " <template class='GtkListItem'>\n" \ |
517 | " <property name='child'>\n" \ |
518 | " <object class='GtkImage'>\n" \ |
519 | " <binding name='gicon'>\n" \ |
520 | " <closure type='GIcon' function='get_object'>\n" \ |
521 | " <lookup name='item' type='GtkTreeListRow'><lookup name='item'>GtkListItem</lookup></lookup>\n" \ |
522 | " <constant type='gchararray'>" attr "</constant>" \ |
523 | " </closure>\n" \ |
524 | " </binding>\n" \ |
525 | " </object>\n" \ |
526 | " </property>\n" \ |
527 | " </template>\n" \ |
528 | "</interface>\n" \ |
529 | |
530 | struct { |
531 | const char *title; |
532 | const char *attribute; |
533 | const char *factory_xml; |
534 | } extra_columns[] = { |
535 | { "Type" , G_FILE_ATTRIBUTE_STANDARD_TYPE, SIMPLE_STRING_FACTORY (G_FILE_ATTRIBUTE_STANDARD_TYPE, "uint32" ) }, |
536 | { "Hidden" , G_FILE_ATTRIBUTE_STANDARD_IS_HIDDEN, BOOLEAN_FACTORY (G_FILE_ATTRIBUTE_STANDARD_IS_HIDDEN) }, |
537 | { "Backup" , G_FILE_ATTRIBUTE_STANDARD_IS_BACKUP, BOOLEAN_FACTORY (G_FILE_ATTRIBUTE_STANDARD_IS_BACKUP) }, |
538 | { "Symlink" , G_FILE_ATTRIBUTE_STANDARD_IS_SYMLINK, BOOLEAN_FACTORY (G_FILE_ATTRIBUTE_STANDARD_IS_SYMLINK) }, |
539 | { "Virtual" , G_FILE_ATTRIBUTE_STANDARD_IS_VIRTUAL, BOOLEAN_FACTORY (G_FILE_ATTRIBUTE_STANDARD_IS_VIRTUAL) }, |
540 | { "Volatile" , G_FILE_ATTRIBUTE_STANDARD_IS_VOLATILE, BOOLEAN_FACTORY (G_FILE_ATTRIBUTE_STANDARD_IS_VOLATILE) }, |
541 | { "Edit name" , G_FILE_ATTRIBUTE_STANDARD_EDIT_NAME, SIMPLE_STRING_FACTORY (G_FILE_ATTRIBUTE_STANDARD_EDIT_NAME, "string" ) }, |
542 | { "Copy name" , G_FILE_ATTRIBUTE_STANDARD_COPY_NAME, SIMPLE_STRING_FACTORY (G_FILE_ATTRIBUTE_STANDARD_COPY_NAME, "string" ) }, |
543 | { "Description" , G_FILE_ATTRIBUTE_STANDARD_DESCRIPTION, SIMPLE_STRING_FACTORY (G_FILE_ATTRIBUTE_STANDARD_DESCRIPTION, "string" ) }, |
544 | { "Icon" , G_FILE_ATTRIBUTE_STANDARD_ICON, ICON_FACTORY (G_FILE_ATTRIBUTE_STANDARD_ICON) }, |
545 | { "Symbolic icon" , G_FILE_ATTRIBUTE_STANDARD_SYMBOLIC_ICON, ICON_FACTORY (G_FILE_ATTRIBUTE_STANDARD_SYMBOLIC_ICON) }, |
546 | { "Content type" , G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE, SIMPLE_STRING_FACTORY (G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE, "string" ) }, |
547 | { "Fast content type" , G_FILE_ATTRIBUTE_STANDARD_FAST_CONTENT_TYPE, SIMPLE_STRING_FACTORY (G_FILE_ATTRIBUTE_STANDARD_FAST_CONTENT_TYPE, "string" ) }, |
548 | { "Size" , G_FILE_ATTRIBUTE_STANDARD_SIZE, SIMPLE_STRING_FACTORY (G_FILE_ATTRIBUTE_STANDARD_SIZE, "uint64" ) }, |
549 | { "Allocated size" , G_FILE_ATTRIBUTE_STANDARD_ALLOCATED_SIZE, SIMPLE_STRING_FACTORY (G_FILE_ATTRIBUTE_STANDARD_ALLOCATED_SIZE, "uint64" ) }, |
550 | { "Target URI" , G_FILE_ATTRIBUTE_STANDARD_TARGET_URI, SIMPLE_STRING_FACTORY (G_FILE_ATTRIBUTE_STANDARD_TARGET_URI, "string" ) }, |
551 | { "Sort order" , G_FILE_ATTRIBUTE_STANDARD_SORT_ORDER, SIMPLE_STRING_FACTORY (G_FILE_ATTRIBUTE_STANDARD_SORT_ORDER, "int32" ) }, |
552 | { "ETAG value" , G_FILE_ATTRIBUTE_ETAG_VALUE, SIMPLE_STRING_FACTORY (G_FILE_ATTRIBUTE_ETAG_VALUE, "string" ) }, |
553 | { "File ID" , G_FILE_ATTRIBUTE_ID_FILE, SIMPLE_STRING_FACTORY (G_FILE_ATTRIBUTE_ID_FILE, "string" ) }, |
554 | { "Filesystem ID" , G_FILE_ATTRIBUTE_ID_FILESYSTEM, SIMPLE_STRING_FACTORY (G_FILE_ATTRIBUTE_ID_FILESYSTEM, "string" ) }, |
555 | { "Read" , G_FILE_ATTRIBUTE_ACCESS_CAN_READ, BOOLEAN_FACTORY (G_FILE_ATTRIBUTE_ACCESS_CAN_READ) }, |
556 | { "Write" , G_FILE_ATTRIBUTE_ACCESS_CAN_WRITE, BOOLEAN_FACTORY (G_FILE_ATTRIBUTE_ACCESS_CAN_WRITE) }, |
557 | { "Execute" , G_FILE_ATTRIBUTE_ACCESS_CAN_EXECUTE, BOOLEAN_FACTORY (G_FILE_ATTRIBUTE_ACCESS_CAN_EXECUTE) }, |
558 | { "Delete" , G_FILE_ATTRIBUTE_ACCESS_CAN_DELETE, BOOLEAN_FACTORY (G_FILE_ATTRIBUTE_ACCESS_CAN_DELETE) }, |
559 | { "Trash" , G_FILE_ATTRIBUTE_ACCESS_CAN_TRASH, BOOLEAN_FACTORY (G_FILE_ATTRIBUTE_ACCESS_CAN_TRASH) }, |
560 | { "Rename" , G_FILE_ATTRIBUTE_ACCESS_CAN_RENAME, BOOLEAN_FACTORY (G_FILE_ATTRIBUTE_ACCESS_CAN_RENAME) }, |
561 | { "Can mount" , G_FILE_ATTRIBUTE_MOUNTABLE_CAN_MOUNT, BOOLEAN_FACTORY (G_FILE_ATTRIBUTE_MOUNTABLE_CAN_MOUNT) }, |
562 | { "Can unmount" , G_FILE_ATTRIBUTE_MOUNTABLE_CAN_UNMOUNT, BOOLEAN_FACTORY (G_FILE_ATTRIBUTE_MOUNTABLE_CAN_UNMOUNT) }, |
563 | { "Can eject" , G_FILE_ATTRIBUTE_MOUNTABLE_CAN_EJECT, BOOLEAN_FACTORY (G_FILE_ATTRIBUTE_MOUNTABLE_CAN_EJECT) }, |
564 | { "UNIX device" , G_FILE_ATTRIBUTE_MOUNTABLE_UNIX_DEVICE, SIMPLE_STRING_FACTORY (G_FILE_ATTRIBUTE_MOUNTABLE_UNIX_DEVICE, "uint32" ) }, |
565 | { "UNIX device file" , G_FILE_ATTRIBUTE_MOUNTABLE_UNIX_DEVICE_FILE, SIMPLE_STRING_FACTORY (G_FILE_ATTRIBUTE_MOUNTABLE_UNIX_DEVICE_FILE, "string" ) }, |
566 | { "owner" , G_FILE_ATTRIBUTE_OWNER_USER, SIMPLE_STRING_FACTORY (G_FILE_ATTRIBUTE_OWNER_USER, "string" ) }, |
567 | { "owner (real)" , G_FILE_ATTRIBUTE_OWNER_USER_REAL, SIMPLE_STRING_FACTORY (G_FILE_ATTRIBUTE_OWNER_USER_REAL, "string" ) }, |
568 | { "group" , G_FILE_ATTRIBUTE_OWNER_GROUP, SIMPLE_STRING_FACTORY (G_FILE_ATTRIBUTE_OWNER_GROUP, "string" ) }, |
569 | { "Preview icon" , G_FILE_ATTRIBUTE_PREVIEW_ICON, ICON_FACTORY (G_FILE_ATTRIBUTE_PREVIEW_ICON) }, |
570 | { "Private" , "recent::private" , BOOLEAN_FACTORY ("recent::private" ) }, |
571 | }; |
572 | |
573 | #if 0 |
574 | #define G_FILE_ATTRIBUTE_MOUNTABLE_CAN_START "mountable::can-start" /* boolean */ |
575 | #define G_FILE_ATTRIBUTE_MOUNTABLE_CAN_START_DEGRADED "mountable::can-start-degraded" /* boolean */ |
576 | #define G_FILE_ATTRIBUTE_MOUNTABLE_CAN_STOP "mountable::can-stop" /* boolean */ |
577 | #define G_FILE_ATTRIBUTE_MOUNTABLE_START_STOP_TYPE "mountable::start-stop-type" /* uint32 (GDriveStartStopType) */ |
578 | #define G_FILE_ATTRIBUTE_MOUNTABLE_CAN_POLL "mountable::can-poll" /* boolean */ |
579 | #define G_FILE_ATTRIBUTE_MOUNTABLE_IS_MEDIA_CHECK_AUTOMATIC "mountable::is-media-check-automatic" /* boolean */ |
580 | #define G_FILE_ATTRIBUTE_TIME_MODIFIED "time::modified" /* uint64 */ |
581 | #define G_FILE_ATTRIBUTE_TIME_MODIFIED_USEC "time::modified-usec" /* uint32 */ |
582 | #define G_FILE_ATTRIBUTE_TIME_ACCESS "time::access" /* uint64 */ |
583 | #define G_FILE_ATTRIBUTE_TIME_ACCESS_USEC "time::access-usec" /* uint32 */ |
584 | #define G_FILE_ATTRIBUTE_TIME_CHANGED "time::changed" /* uint64 */ |
585 | #define G_FILE_ATTRIBUTE_TIME_CHANGED_USEC "time::changed-usec" /* uint32 */ |
586 | #define G_FILE_ATTRIBUTE_TIME_CREATED "time::created" /* uint64 */ |
587 | #define G_FILE_ATTRIBUTE_TIME_CREATED_USEC "time::created-usec" /* uint32 */ |
588 | #define G_FILE_ATTRIBUTE_UNIX_DEVICE "unix::device" /* uint32 */ |
589 | #define G_FILE_ATTRIBUTE_UNIX_INODE "unix::inode" /* uint64 */ |
590 | #define G_FILE_ATTRIBUTE_UNIX_MODE "unix::mode" /* uint32 */ |
591 | #define G_FILE_ATTRIBUTE_UNIX_NLINK "unix::nlink" /* uint32 */ |
592 | #define G_FILE_ATTRIBUTE_UNIX_UID "unix::uid" /* uint32 */ |
593 | #define G_FILE_ATTRIBUTE_UNIX_GID "unix::gid" /* uint32 */ |
594 | #define G_FILE_ATTRIBUTE_UNIX_RDEV "unix::rdev" /* uint32 */ |
595 | #define G_FILE_ATTRIBUTE_UNIX_BLOCK_SIZE "unix::block-size" /* uint32 */ |
596 | #define G_FILE_ATTRIBUTE_UNIX_BLOCKS "unix::blocks" /* uint64 */ |
597 | #define G_FILE_ATTRIBUTE_UNIX_IS_MOUNTPOINT "unix::is-mountpoint" /* boolean */ |
598 | #define G_FILE_ATTRIBUTE_DOS_IS_ARCHIVE "dos::is-archive" /* boolean */ |
599 | #define G_FILE_ATTRIBUTE_DOS_IS_SYSTEM "dos::is-system" /* boolean */ |
600 | #define G_FILE_ATTRIBUTE_DOS_IS_MOUNTPOINT "dos::is-mountpoint" /* boolean */ |
601 | #define G_FILE_ATTRIBUTE_DOS_REPARSE_POINT_TAG "dos::reparse-point-tag" /* uint32 */ |
602 | #define G_FILE_ATTRIBUTE_THUMBNAIL_PATH "thumbnail::path" /* bytestring */ |
603 | #define G_FILE_ATTRIBUTE_THUMBNAILING_FAILED "thumbnail::failed" /* boolean */ |
604 | #define G_FILE_ATTRIBUTE_THUMBNAIL_IS_VALID "thumbnail::is-valid" /* boolean */ |
605 | #define G_FILE_ATTRIBUTE_FILESYSTEM_SIZE "filesystem::size" /* uint64 */ |
606 | #define G_FILE_ATTRIBUTE_FILESYSTEM_FREE "filesystem::free" /* uint64 */ |
607 | #define G_FILE_ATTRIBUTE_FILESYSTEM_USED "filesystem::used" /* uint64 */ |
608 | #define G_FILE_ATTRIBUTE_FILESYSTEM_TYPE "filesystem::type" /* string */ |
609 | #define G_FILE_ATTRIBUTE_FILESYSTEM_READONLY "filesystem::readonly" /* boolean */ |
610 | #define G_FILE_ATTRIBUTE_FILESYSTEM_USE_PREVIEW "filesystem::use-preview" /* uint32 (GFilesystemPreviewType) */ |
611 | #define G_FILE_ATTRIBUTE_FILESYSTEM_REMOTE "filesystem::remote" /* boolean */ |
612 | #define G_FILE_ATTRIBUTE_GVFS_BACKEND "gvfs::backend" /* string */ |
613 | #define G_FILE_ATTRIBUTE_SELINUX_CONTEXT "selinux::context" /* string */ |
614 | #define G_FILE_ATTRIBUTE_TRASH_ITEM_COUNT "trash::item-count" /* uint32 */ |
615 | #define G_FILE_ATTRIBUTE_TRASH_ORIG_PATH "trash::orig-path" /* byte string */ |
616 | #define G_FILE_ATTRIBUTE_TRASH_DELETION_DATE "trash::deletion-date" /* string */ |
617 | #define G_FILE_ATTRIBUTE_RECENT_MODIFIED "recent::modified" /* int64 (time_t) */ |
618 | #endif |
619 | |
620 | const char *factory_ui = |
621 | "<?xml version='1.0' encoding='UTF-8'?>\n" |
622 | "<interface>\n" |
623 | " <template class='GtkListItem'>\n" |
624 | " <property name='child'>\n" |
625 | " <object class='GtkLabel'>\n" |
626 | " <binding name='label'>\n" |
627 | " <lookup name='title' type='GtkColumnViewColumn'>\n" |
628 | " <lookup name='item'>GtkListItem</lookup>\n" |
629 | " </lookup>\n" |
630 | " </binding>\n" |
631 | " </object>\n" |
632 | " </property>\n" |
633 | " </template>\n" |
634 | "</interface>\n" ; |
635 | |
636 | static GtkBuilderScope * |
637 | create_scope (void) |
638 | { |
639 | #define ADD_SYMBOL(name) \ |
640 | gtk_builder_cscope_add_callback_symbol (GTK_BUILDER_CSCOPE (scope), G_STRINGIFY (name), G_CALLBACK (name)) |
641 | GtkBuilderScope *scope; |
642 | |
643 | scope = gtk_builder_cscope_new (); |
644 | |
645 | ADD_SYMBOL (get_object); |
646 | ADD_SYMBOL (get_string); |
647 | ADD_SYMBOL (get_boolean); |
648 | |
649 | return scope; |
650 | #undef ADD_SYMBOL |
651 | } |
652 | |
653 | static void |
654 | add_extra_columns (GtkColumnView *view, |
655 | GtkBuilderScope *scope) |
656 | { |
657 | GtkColumnViewColumn *column; |
658 | GtkSorter *sorter; |
659 | GBytes *bytes; |
660 | guint i; |
661 | |
662 | for (i = 0; i < G_N_ELEMENTS(extra_columns); i++) |
663 | { |
664 | bytes = g_bytes_new_static (data: extra_columns[i].factory_xml, size: strlen (s: extra_columns[i].factory_xml)); |
665 | column = gtk_column_view_column_new (title: extra_columns[i].title, |
666 | factory: gtk_builder_list_item_factory_new_from_bytes (scope, bytes)); |
667 | g_bytes_unref (bytes); |
668 | sorter = GTK_SORTER (ptr: gtk_custom_sorter_new (sort_func: compare_file_attribute, user_data: (gpointer) extra_columns[i].attribute, NULL)); |
669 | gtk_column_view_column_set_sorter (self: column, sorter); |
670 | g_object_unref (object: sorter); |
671 | gtk_column_view_append_column (self: view, column); |
672 | } |
673 | } |
674 | |
675 | static void |
676 | search_changed_cb (GtkSearchEntry *entry, |
677 | GtkFilter *custom_filter) |
678 | { |
679 | gtk_filter_changed (self: custom_filter, change: GTK_FILTER_CHANGE_DIFFERENT); |
680 | } |
681 | |
682 | int |
683 | main (int argc, char *argv[]) |
684 | { |
685 | GListModel *toplevels; |
686 | GtkWidget *win, *hbox, *vbox, *sw, *view, *list, *search_entry, *statusbar; |
687 | GListModel *dirmodel; |
688 | GtkTreeListModel *tree; |
689 | GtkFilterListModel *filter; |
690 | GtkFilter *custom_filter; |
691 | GtkSortListModel *sort; |
692 | GtkSorter *sorter; |
693 | GFile *root; |
694 | GtkBuilderScope *scope; |
695 | GtkBuilder *builder; |
696 | GError *error = NULL; |
697 | GtkSelectionModel *selection; |
698 | |
699 | gtk_init (); |
700 | |
701 | win = gtk_window_new (); |
702 | gtk_window_set_default_size (GTK_WINDOW (win), width: 800, height: 600); |
703 | |
704 | hbox = gtk_box_new (orientation: GTK_ORIENTATION_HORIZONTAL, spacing: 6); |
705 | gtk_window_set_child (GTK_WINDOW (win), child: hbox); |
706 | |
707 | vbox = gtk_box_new (orientation: GTK_ORIENTATION_VERTICAL, spacing: 0); |
708 | gtk_box_append (GTK_BOX (hbox), child: vbox); |
709 | |
710 | search_entry = gtk_search_entry_new (); |
711 | gtk_box_append (GTK_BOX (vbox), child: search_entry); |
712 | |
713 | sw = gtk_scrolled_window_new (); |
714 | gtk_widget_set_hexpand (widget: sw, TRUE); |
715 | gtk_widget_set_vexpand (widget: sw, TRUE); |
716 | gtk_search_entry_set_key_capture_widget (GTK_SEARCH_ENTRY (search_entry), widget: sw); |
717 | gtk_box_append (GTK_BOX (vbox), child: sw); |
718 | |
719 | scope = create_scope (); |
720 | builder = gtk_builder_new (); |
721 | gtk_builder_set_scope (builder, scope); |
722 | if (!gtk_builder_add_from_string (builder, buffer: ui_file, length: -1, error: &error)) |
723 | { |
724 | g_assert_no_error (error); |
725 | } |
726 | view = GTK_WIDGET (gtk_builder_get_object (builder, "view" )); |
727 | add_extra_columns (GTK_COLUMN_VIEW (view), scope); |
728 | gtk_scrolled_window_set_child (GTK_SCROLLED_WINDOW (sw), child: view); |
729 | g_object_unref (object: builder); |
730 | |
731 | if (argc > 1) |
732 | { |
733 | if (g_strcmp0 (str1: argv[1], str2: "--recent" ) == 0) |
734 | { |
735 | dirmodel = create_recent_files_list (); |
736 | } |
737 | else |
738 | { |
739 | root = g_file_new_for_commandline_arg (arg: argv[1]); |
740 | dirmodel = create_list_model_for_directory (file: root); |
741 | g_object_unref (object: root); |
742 | } |
743 | } |
744 | else |
745 | { |
746 | root = g_file_new_for_path (path: g_get_current_dir ()); |
747 | dirmodel = create_list_model_for_directory (file: root); |
748 | g_object_unref (object: root); |
749 | } |
750 | tree = gtk_tree_list_model_new (root: dirmodel, |
751 | FALSE, |
752 | TRUE, |
753 | create_func: create_list_model_for_file_info, |
754 | NULL, NULL); |
755 | |
756 | sorter = GTK_SORTER (ptr: gtk_tree_list_row_sorter_new (g_object_ref (gtk_column_view_get_sorter (GTK_COLUMN_VIEW (view))))); |
757 | sort = gtk_sort_list_model_new (model: G_LIST_MODEL (ptr: tree), sorter); |
758 | |
759 | custom_filter = GTK_FILTER (ptr: gtk_custom_filter_new (match_func: match_file, g_object_ref (search_entry), user_destroy: g_object_unref)); |
760 | filter = gtk_filter_list_model_new (model: G_LIST_MODEL (ptr: sort), filter: custom_filter); |
761 | g_signal_connect (search_entry, "search-changed" , G_CALLBACK (search_changed_cb), custom_filter); |
762 | |
763 | selection = GTK_SELECTION_MODEL (ptr: gtk_single_selection_new (model: G_LIST_MODEL (ptr: filter))); |
764 | gtk_column_view_set_model (GTK_COLUMN_VIEW (view), model: selection); |
765 | g_object_unref (object: selection); |
766 | |
767 | statusbar = gtk_statusbar_new (); |
768 | gtk_widget_add_tick_callback (widget: statusbar, callback: (GtkTickCallback) update_statusbar, NULL, NULL); |
769 | g_object_set_data (G_OBJECT (statusbar), key: "model" , data: filter); |
770 | g_signal_connect_swapped (filter, "items-changed" , G_CALLBACK (update_statusbar), statusbar); |
771 | update_statusbar (GTK_STATUSBAR (statusbar)); |
772 | gtk_box_append (GTK_BOX (vbox), child: statusbar); |
773 | |
774 | list = gtk_list_view_new ( |
775 | model: GTK_SELECTION_MODEL (ptr: gtk_single_selection_new (g_object_ref (gtk_column_view_get_columns (GTK_COLUMN_VIEW (view))))), |
776 | factory: gtk_builder_list_item_factory_new_from_bytes (scope, bytes: g_bytes_new_static (data: factory_ui, size: strlen (s: factory_ui)))); |
777 | gtk_box_append (GTK_BOX (hbox), child: list); |
778 | |
779 | g_object_unref (object: scope); |
780 | |
781 | gtk_widget_show (widget: win); |
782 | |
783 | toplevels = gtk_window_get_toplevels (); |
784 | while (g_list_model_get_n_items (list: toplevels)) |
785 | g_main_context_iteration (NULL, TRUE); |
786 | |
787 | return 0; |
788 | } |
789 | |