1#include <gtk/gtk.h>
2
3GSList *pending = NULL;
4guint active = 0;
5
6static void
7loading_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
32static GtkDirectoryList *
33create_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
56static GListModel *
57create_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
65static GListModel *
66create_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
76typedef struct _RowData RowData;
77struct _RowData
78{
79 GtkWidget *expander;
80 GtkWidget *icon;
81 GtkWidget *name;
82 GCancellable *cancellable;
83
84 GtkTreeListRow *current_item;
85};
86
87static void row_data_notify_item (GtkListItem *item,
88 GParamSpec *pspec,
89 RowData *data);
90static void
91row_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
105static void
106row_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
129static void
130copy_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
141static void
142row_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
169static void
170row_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
208static void
209row_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
216static void
217row_data_free (gpointer _data)
218{
219 RowData *data = _data;
220
221 row_data_unbind (data);
222
223 g_slice_free (RowData, data);
224}
225
226static void
227setup_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
262static GListModel *
263create_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
274static gboolean
275update_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
307static gboolean
308match_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
326static int
327compare_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
374static GObject *
375get_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
391static char *
392get_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
402static gboolean
403get_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
413const 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
530struct {
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
620const 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
636static GtkBuilderScope *
637create_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
653static void
654add_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
675static void
676search_changed_cb (GtkSearchEntry *entry,
677 GtkFilter *custom_filter)
678{
679 gtk_filter_changed (self: custom_filter, change: GTK_FILTER_CHANGE_DIFFERENT);
680}
681
682int
683main (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

source code of gtk/tests/testcolumnview.c