#include GSList *pending = NULL; guint active = 0; static void loading_cb (GtkDirectoryList *dir, GParamSpec *pspec, gpointer unused) { if (gtk_directory_list_is_loading (dir)) { active++; /* HACK: ensure loading finishes and the dir doesn't get destroyed */ g_object_ref (dir); } else { active--; g_object_unref (dir); while (active < 20 && pending) { GtkDirectoryList *dir2 = pending->data; pending = g_slist_remove (pending, dir2); gtk_directory_list_set_file (dir2, g_object_get_data (G_OBJECT (dir2), "file")); g_object_unref (dir2); } } } static GtkDirectoryList * create_directory_list (GFile *file) { GtkDirectoryList *dir; dir = gtk_directory_list_new ("*", NULL); gtk_directory_list_set_io_priority (dir, G_PRIORITY_DEFAULT_IDLE); g_signal_connect (dir, "notify::loading", G_CALLBACK (loading_cb), NULL); g_assert (!gtk_directory_list_is_loading (dir)); if (active > 20) { g_object_set_data_full (G_OBJECT (dir), "file", g_object_ref (file), g_object_unref); pending = g_slist_prepend (pending, g_object_ref (dir)); } else { gtk_directory_list_set_file (dir, file); } return dir; } static GListModel * create_list_model_for_directory (gpointer file) { if (g_file_query_file_type (file, G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS, NULL) != G_FILE_TYPE_DIRECTORY) return NULL; return G_LIST_MODEL (create_directory_list (file)); } static GListModel * create_recent_files_list (void) { GtkBookmarkList *dir; dir = gtk_bookmark_list_new (NULL, "*"); return G_LIST_MODEL (dir); } #if 0 typedef struct _RowData RowData; struct _RowData { GtkWidget *expander; GtkWidget *icon; GtkWidget *name; GCancellable *cancellable; GtkTreeListRow *current_item; }; static void row_data_notify_item (GtkListItem *item, GParamSpec *pspec, RowData *data); static void row_data_unbind (RowData *data) { if (data->current_item == NULL) return; if (data->cancellable) { g_cancellable_cancel (data->cancellable); g_clear_object (&data->cancellable); } g_clear_object (&data->current_item); } static void row_data_update_info (RowData *data, GFileInfo *info) { GIcon *icon; const char *thumbnail_path; thumbnail_path = g_file_info_get_attribute_byte_string (info, G_FILE_ATTRIBUTE_THUMBNAIL_PATH); if (thumbnail_path) { /* XXX: not async */ GFile *thumbnail_file = g_file_new_for_path (thumbnail_path); icon = g_file_icon_new (thumbnail_file); g_object_unref (thumbnail_file); } else { icon = g_file_info_get_icon (info); } gtk_widget_set_visible (data->icon, icon != NULL); gtk_image_set_from_gicon (GTK_IMAGE (data->icon), icon); } static void copy_attribute (GFileInfo *to, GFileInfo *from, const char *attribute) { GFileAttributeType type; gpointer value; if (g_file_info_get_attribute_data (from, attribute, &type, &value, NULL)) g_file_info_set_attribute (to, attribute, type, value); } static void row_data_got_thumbnail_info_cb (GObject *source, GAsyncResult *res, gpointer _data) { RowData *data = _data; /* invalid if operation was cancelled */ GFile *file = G_FILE (source); GFileInfo *queried, *info; queried = g_file_query_info_finish (file, res, NULL); if (queried == NULL) return; /* now we know row is valid */ info = gtk_tree_list_row_get_item (data->current_item); copy_attribute (info, queried, G_FILE_ATTRIBUTE_THUMBNAIL_PATH); copy_attribute (info, queried, G_FILE_ATTRIBUTE_THUMBNAILING_FAILED); copy_attribute (info, queried, G_FILE_ATTRIBUTE_STANDARD_ICON); g_object_unref (queried); row_data_update_info (data, info); g_clear_object (&data->cancellable); } static void row_data_bind (RowData *data, GtkTreeListRow *item) { GFileInfo *info; row_data_unbind (data); if (item == NULL) return; data->current_item = g_object_ref (item); gtk_tree_expander_set_list_row (GTK_TREE_EXPANDER (data->expander), item); info = gtk_tree_list_row_get_item (item); if (!g_file_info_has_attribute (info, "filechooser::queried")) { data->cancellable = g_cancellable_new (); g_file_info_set_attribute_boolean (info, "filechooser::queried", TRUE); g_file_query_info_async (G_FILE (g_file_info_get_attribute_object (info, "standard::file")), G_FILE_ATTRIBUTE_THUMBNAIL_PATH "," G_FILE_ATTRIBUTE_THUMBNAILING_FAILED "," G_FILE_ATTRIBUTE_STANDARD_ICON, G_FILE_QUERY_INFO_NONE, G_PRIORITY_DEFAULT, data->cancellable, row_data_got_thumbnail_info_cb, data); } row_data_update_info (data, info); gtk_label_set_label (GTK_LABEL (data->name), g_file_info_get_display_name (info)); g_object_unref (info); } static void row_data_notify_item (GtkListItem *item, GParamSpec *pspec, RowData *data) { row_data_bind (data, gtk_list_item_get_item (item)); } static void row_data_free (gpointer _data) { RowData *data = _data; row_data_unbind (data); g_slice_free (RowData, data); } static void setup_widget (GtkListItem *list_item, gpointer unused) { GtkWidget *box, *child; RowData *data; data = g_slice_new0 (RowData); g_signal_connect (list_item, "notify::item", G_CALLBACK (row_data_notify_item), data); g_object_set_data_full (G_OBJECT (list_item), "row-data", data, row_data_free); box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 4); gtk_container_add (GTK_CONTAINER (list_item), box); child = gtk_label_new (NULL); gtk_label_set_width_chars (GTK_LABEL (child), 5); gtk_label_set_xalign (GTK_LABEL (child), 1.0); g_object_bind_property (list_item, "position", child, "label", G_BINDING_SYNC_CREATE); gtk_container_add (GTK_CONTAINER (box), child); data->expander = gtk_tree_expander_new (); gtk_container_add (GTK_CONTAINER (box), data->expander); box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 4); gtk_tree_expander_set_child (GTK_TREE_EXPANDER (data->expander), box); data->icon = gtk_image_new (); gtk_container_add (GTK_CONTAINER (box), data->icon); data->name = gtk_label_new (NULL); gtk_label_set_max_width_chars (GTK_LABEL (data->name), 25); gtk_label_set_ellipsize (GTK_LABEL (data->name), PANGO_ELLIPSIZE_END); gtk_container_add (GTK_CONTAINER (box), data->name); } #endif static GListModel * create_list_model_for_file_info (gpointer file_info, gpointer unused) { GFile *file = G_FILE (g_file_info_get_attribute_object (file_info, "standard::file")); if (file == NULL) return NULL; return create_list_model_for_directory (file); } static gboolean update_statusbar (GtkStatusbar *statusbar) { GListModel *model = g_object_get_data (G_OBJECT (statusbar), "model"); GString *string = g_string_new (NULL); guint n; gboolean result = G_SOURCE_REMOVE; gtk_statusbar_remove_all (statusbar, 0); n = g_list_model_get_n_items (model); g_string_append_printf (string, "%u", n); if (GTK_IS_FILTER_LIST_MODEL (model)) { guint n_unfiltered = g_list_model_get_n_items (gtk_filter_list_model_get_model (GTK_FILTER_LIST_MODEL (model))); if (n != n_unfiltered) g_string_append_printf (string, "/%u", n_unfiltered); } g_string_append (string, " items"); if (pending || active) { g_string_append_printf (string, " (%u directories remaining)", active + g_slist_length (pending)); result = G_SOURCE_CONTINUE; } result = G_SOURCE_CONTINUE; gtk_statusbar_push (statusbar, 0, string->str); g_free (string->str); return result; } static gboolean match_file (gpointer item, gpointer data) { GtkWidget *search_entry = data; GFileInfo *info = gtk_tree_list_row_get_item (item); GFile *file = G_FILE (g_file_info_get_attribute_object (info, "standard::file")); char *path; gboolean result; path = g_file_get_path (file); result = strstr (path, gtk_editable_get_text (GTK_EDITABLE (search_entry))) != NULL; g_object_unref (info); g_free (path); return result; } static int compare_file_attribute (gconstpointer info1_, gconstpointer info2_, gpointer data) { GFileInfo *info1 = (gpointer) info1_; GFileInfo *info2 = (gpointer) info2_; const char *attribute = data; GFileAttributeType type1, type2; type1 = g_file_info_get_attribute_type (info1, attribute); type2 = g_file_info_get_attribute_type (info2, attribute); if (type1 != type2) return (int) type2 - (int) type1; switch (type1) { case G_FILE_ATTRIBUTE_TYPE_INVALID: case G_FILE_ATTRIBUTE_TYPE_OBJECT: case G_FILE_ATTRIBUTE_TYPE_STRINGV: return 0; case G_FILE_ATTRIBUTE_TYPE_STRING: return g_utf8_collate (g_file_info_get_attribute_string (info1, attribute), g_file_info_get_attribute_string (info2, attribute)); case G_FILE_ATTRIBUTE_TYPE_BYTE_STRING: return strcmp (g_file_info_get_attribute_byte_string (info1, attribute), g_file_info_get_attribute_byte_string (info2, attribute)); case G_FILE_ATTRIBUTE_TYPE_BOOLEAN: return g_file_info_get_attribute_boolean (info1, attribute) - g_file_info_get_attribute_boolean (info2, attribute); case G_FILE_ATTRIBUTE_TYPE_UINT32: return g_file_info_get_attribute_uint32 (info1, attribute) - g_file_info_get_attribute_uint32 (info2, attribute); case G_FILE_ATTRIBUTE_TYPE_INT32: return g_file_info_get_attribute_int32 (info1, attribute) - g_file_info_get_attribute_int32 (info2, attribute); case G_FILE_ATTRIBUTE_TYPE_UINT64: return g_file_info_get_attribute_uint64 (info1, attribute) - g_file_info_get_attribute_uint64 (info2, attribute); case G_FILE_ATTRIBUTE_TYPE_INT64: return g_file_info_get_attribute_int64 (info1, attribute) - g_file_info_get_attribute_int64 (info2, attribute); default: g_assert_not_reached (); return 0; } } static GObject * get_object (GObject *unused, GFileInfo *info, const char *attribute) { GObject *o; if (info == NULL) return NULL; o = g_file_info_get_attribute_object (info, attribute); if (o) g_object_ref (o); return o; } static char * get_string (GObject *unused, GFileInfo *info, const char *attribute) { if (info == NULL) return NULL; return g_file_info_get_attribute_as_string (info, attribute); } static gboolean get_boolean (GObject *unused, GFileInfo *info, const char *attribute) { if (info == NULL) return FALSE; return g_file_info_get_attribute_boolean (info, attribute); } const char *ui_file = "\n" "\n" " \n" " \n" " \n" " Name\n" " \n" " \n" " \n" "\n" " \n" "\n" " ]]>\n" " \n" " \n" " \n" " \n" " \n" " \n" " standard::display-name" " \n" " \n" " \n" " \n" " \n" " \n" " \n" "\n"; #define SIMPLE_STRING_FACTORY(attr, type) \ "\n" \ "\n" \ " \n" \ "\n" \ #define BOOLEAN_FACTORY(attr) \ "\n" \ "\n" \ " \n" \ "\n" \ #define ICON_FACTORY(attr) \ "\n" \ "\n" \ " \n" \ "\n" \ struct { const char *title; const char *attribute; const char *factory_xml; } extra_columns[] = { { "Type", G_FILE_ATTRIBUTE_STANDARD_TYPE, SIMPLE_STRING_FACTORY (G_FILE_ATTRIBUTE_STANDARD_TYPE, "uint32") }, { "Hidden", G_FILE_ATTRIBUTE_STANDARD_IS_HIDDEN, BOOLEAN_FACTORY (G_FILE_ATTRIBUTE_STANDARD_IS_HIDDEN) }, { "Backup", G_FILE_ATTRIBUTE_STANDARD_IS_BACKUP, BOOLEAN_FACTORY (G_FILE_ATTRIBUTE_STANDARD_IS_BACKUP) }, { "Symlink", G_FILE_ATTRIBUTE_STANDARD_IS_SYMLINK, BOOLEAN_FACTORY (G_FILE_ATTRIBUTE_STANDARD_IS_SYMLINK) }, { "Virtual", G_FILE_ATTRIBUTE_STANDARD_IS_VIRTUAL, BOOLEAN_FACTORY (G_FILE_ATTRIBUTE_STANDARD_IS_VIRTUAL) }, { "Volatile", G_FILE_ATTRIBUTE_STANDARD_IS_VOLATILE, BOOLEAN_FACTORY (G_FILE_ATTRIBUTE_STANDARD_IS_VOLATILE) }, { "Edit name", G_FILE_ATTRIBUTE_STANDARD_EDIT_NAME, SIMPLE_STRING_FACTORY (G_FILE_ATTRIBUTE_STANDARD_EDIT_NAME, "string") }, { "Copy name", G_FILE_ATTRIBUTE_STANDARD_COPY_NAME, SIMPLE_STRING_FACTORY (G_FILE_ATTRIBUTE_STANDARD_COPY_NAME, "string") }, { "Description", G_FILE_ATTRIBUTE_STANDARD_DESCRIPTION, SIMPLE_STRING_FACTORY (G_FILE_ATTRIBUTE_STANDARD_DESCRIPTION, "string") }, { "Icon", G_FILE_ATTRIBUTE_STANDARD_ICON, ICON_FACTORY (G_FILE_ATTRIBUTE_STANDARD_ICON) }, { "Symbolic icon", G_FILE_ATTRIBUTE_STANDARD_SYMBOLIC_ICON, ICON_FACTORY (G_FILE_ATTRIBUTE_STANDARD_SYMBOLIC_ICON) }, { "Content type", G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE, SIMPLE_STRING_FACTORY (G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE, "string") }, { "Fast content type", G_FILE_ATTRIBUTE_STANDARD_FAST_CONTENT_TYPE, SIMPLE_STRING_FACTORY (G_FILE_ATTRIBUTE_STANDARD_FAST_CONTENT_TYPE, "string") }, { "Size", G_FILE_ATTRIBUTE_STANDARD_SIZE, SIMPLE_STRING_FACTORY (G_FILE_ATTRIBUTE_STANDARD_SIZE, "uint64") }, { "Allocated size", G_FILE_ATTRIBUTE_STANDARD_ALLOCATED_SIZE, SIMPLE_STRING_FACTORY (G_FILE_ATTRIBUTE_STANDARD_ALLOCATED_SIZE, "uint64") }, { "Target URI", G_FILE_ATTRIBUTE_STANDARD_TARGET_URI, SIMPLE_STRING_FACTORY (G_FILE_ATTRIBUTE_STANDARD_TARGET_URI, "string") }, { "Sort order", G_FILE_ATTRIBUTE_STANDARD_SORT_ORDER, SIMPLE_STRING_FACTORY (G_FILE_ATTRIBUTE_STANDARD_SORT_ORDER, "int32") }, { "ETAG value", G_FILE_ATTRIBUTE_ETAG_VALUE, SIMPLE_STRING_FACTORY (G_FILE_ATTRIBUTE_ETAG_VALUE, "string") }, { "File ID", G_FILE_ATTRIBUTE_ID_FILE, SIMPLE_STRING_FACTORY (G_FILE_ATTRIBUTE_ID_FILE, "string") }, { "Filesystem ID", G_FILE_ATTRIBUTE_ID_FILESYSTEM, SIMPLE_STRING_FACTORY (G_FILE_ATTRIBUTE_ID_FILESYSTEM, "string") }, { "Read", G_FILE_ATTRIBUTE_ACCESS_CAN_READ, BOOLEAN_FACTORY (G_FILE_ATTRIBUTE_ACCESS_CAN_READ) }, { "Write", G_FILE_ATTRIBUTE_ACCESS_CAN_WRITE, BOOLEAN_FACTORY (G_FILE_ATTRIBUTE_ACCESS_CAN_WRITE) }, { "Execute", G_FILE_ATTRIBUTE_ACCESS_CAN_EXECUTE, BOOLEAN_FACTORY (G_FILE_ATTRIBUTE_ACCESS_CAN_EXECUTE) }, { "Delete", G_FILE_ATTRIBUTE_ACCESS_CAN_DELETE, BOOLEAN_FACTORY (G_FILE_ATTRIBUTE_ACCESS_CAN_DELETE) }, { "Trash", G_FILE_ATTRIBUTE_ACCESS_CAN_TRASH, BOOLEAN_FACTORY (G_FILE_ATTRIBUTE_ACCESS_CAN_TRASH) }, { "Rename", G_FILE_ATTRIBUTE_ACCESS_CAN_RENAME, BOOLEAN_FACTORY (G_FILE_ATTRIBUTE_ACCESS_CAN_RENAME) }, { "Can mount", G_FILE_ATTRIBUTE_MOUNTABLE_CAN_MOUNT, BOOLEAN_FACTORY (G_FILE_ATTRIBUTE_MOUNTABLE_CAN_MOUNT) }, { "Can unmount", G_FILE_ATTRIBUTE_MOUNTABLE_CAN_UNMOUNT, BOOLEAN_FACTORY (G_FILE_ATTRIBUTE_MOUNTABLE_CAN_UNMOUNT) }, { "Can eject", G_FILE_ATTRIBUTE_MOUNTABLE_CAN_EJECT, BOOLEAN_FACTORY (G_FILE_ATTRIBUTE_MOUNTABLE_CAN_EJECT) }, { "UNIX device", G_FILE_ATTRIBUTE_MOUNTABLE_UNIX_DEVICE, SIMPLE_STRING_FACTORY (G_FILE_ATTRIBUTE_MOUNTABLE_UNIX_DEVICE, "uint32") }, { "UNIX device file", G_FILE_ATTRIBUTE_MOUNTABLE_UNIX_DEVICE_FILE, SIMPLE_STRING_FACTORY (G_FILE_ATTRIBUTE_MOUNTABLE_UNIX_DEVICE_FILE, "string") }, { "owner", G_FILE_ATTRIBUTE_OWNER_USER, SIMPLE_STRING_FACTORY (G_FILE_ATTRIBUTE_OWNER_USER, "string") }, { "owner (real)", G_FILE_ATTRIBUTE_OWNER_USER_REAL, SIMPLE_STRING_FACTORY (G_FILE_ATTRIBUTE_OWNER_USER_REAL, "string") }, { "group", G_FILE_ATTRIBUTE_OWNER_GROUP, SIMPLE_STRING_FACTORY (G_FILE_ATTRIBUTE_OWNER_GROUP, "string") }, { "Preview icon", G_FILE_ATTRIBUTE_PREVIEW_ICON, ICON_FACTORY (G_FILE_ATTRIBUTE_PREVIEW_ICON) }, { "Private", "recent::private", BOOLEAN_FACTORY ("recent::private") }, }; #if 0 #define G_FILE_ATTRIBUTE_MOUNTABLE_CAN_START "mountable::can-start" /* boolean */ #define G_FILE_ATTRIBUTE_MOUNTABLE_CAN_START_DEGRADED "mountable::can-start-degraded" /* boolean */ #define G_FILE_ATTRIBUTE_MOUNTABLE_CAN_STOP "mountable::can-stop" /* boolean */ #define G_FILE_ATTRIBUTE_MOUNTABLE_START_STOP_TYPE "mountable::start-stop-type" /* uint32 (GDriveStartStopType) */ #define G_FILE_ATTRIBUTE_MOUNTABLE_CAN_POLL "mountable::can-poll" /* boolean */ #define G_FILE_ATTRIBUTE_MOUNTABLE_IS_MEDIA_CHECK_AUTOMATIC "mountable::is-media-check-automatic" /* boolean */ #define G_FILE_ATTRIBUTE_TIME_MODIFIED "time::modified" /* uint64 */ #define G_FILE_ATTRIBUTE_TIME_MODIFIED_USEC "time::modified-usec" /* uint32 */ #define G_FILE_ATTRIBUTE_TIME_ACCESS "time::access" /* uint64 */ #define G_FILE_ATTRIBUTE_TIME_ACCESS_USEC "time::access-usec" /* uint32 */ #define G_FILE_ATTRIBUTE_TIME_CHANGED "time::changed" /* uint64 */ #define G_FILE_ATTRIBUTE_TIME_CHANGED_USEC "time::changed-usec" /* uint32 */ #define G_FILE_ATTRIBUTE_TIME_CREATED "time::created" /* uint64 */ #define G_FILE_ATTRIBUTE_TIME_CREATED_USEC "time::created-usec" /* uint32 */ #define G_FILE_ATTRIBUTE_UNIX_DEVICE "unix::device" /* uint32 */ #define G_FILE_ATTRIBUTE_UNIX_INODE "unix::inode" /* uint64 */ #define G_FILE_ATTRIBUTE_UNIX_MODE "unix::mode" /* uint32 */ #define G_FILE_ATTRIBUTE_UNIX_NLINK "unix::nlink" /* uint32 */ #define G_FILE_ATTRIBUTE_UNIX_UID "unix::uid" /* uint32 */ #define G_FILE_ATTRIBUTE_UNIX_GID "unix::gid" /* uint32 */ #define G_FILE_ATTRIBUTE_UNIX_RDEV "unix::rdev" /* uint32 */ #define G_FILE_ATTRIBUTE_UNIX_BLOCK_SIZE "unix::block-size" /* uint32 */ #define G_FILE_ATTRIBUTE_UNIX_BLOCKS "unix::blocks" /* uint64 */ #define G_FILE_ATTRIBUTE_UNIX_IS_MOUNTPOINT "unix::is-mountpoint" /* boolean */ #define G_FILE_ATTRIBUTE_DOS_IS_ARCHIVE "dos::is-archive" /* boolean */ #define G_FILE_ATTRIBUTE_DOS_IS_SYSTEM "dos::is-system" /* boolean */ #define G_FILE_ATTRIBUTE_DOS_IS_MOUNTPOINT "dos::is-mountpoint" /* boolean */ #define G_FILE_ATTRIBUTE_DOS_REPARSE_POINT_TAG "dos::reparse-point-tag" /* uint32 */ #define G_FILE_ATTRIBUTE_THUMBNAIL_PATH "thumbnail::path" /* bytestring */ #define G_FILE_ATTRIBUTE_THUMBNAILING_FAILED "thumbnail::failed" /* boolean */ #define G_FILE_ATTRIBUTE_THUMBNAIL_IS_VALID "thumbnail::is-valid" /* boolean */ #define G_FILE_ATTRIBUTE_FILESYSTEM_SIZE "filesystem::size" /* uint64 */ #define G_FILE_ATTRIBUTE_FILESYSTEM_FREE "filesystem::free" /* uint64 */ #define G_FILE_ATTRIBUTE_FILESYSTEM_USED "filesystem::used" /* uint64 */ #define G_FILE_ATTRIBUTE_FILESYSTEM_TYPE "filesystem::type" /* string */ #define G_FILE_ATTRIBUTE_FILESYSTEM_READONLY "filesystem::readonly" /* boolean */ #define G_FILE_ATTRIBUTE_FILESYSTEM_USE_PREVIEW "filesystem::use-preview" /* uint32 (GFilesystemPreviewType) */ #define G_FILE_ATTRIBUTE_FILESYSTEM_REMOTE "filesystem::remote" /* boolean */ #define G_FILE_ATTRIBUTE_GVFS_BACKEND "gvfs::backend" /* string */ #define G_FILE_ATTRIBUTE_SELINUX_CONTEXT "selinux::context" /* string */ #define G_FILE_ATTRIBUTE_TRASH_ITEM_COUNT "trash::item-count" /* uint32 */ #define G_FILE_ATTRIBUTE_TRASH_ORIG_PATH "trash::orig-path" /* byte string */ #define G_FILE_ATTRIBUTE_TRASH_DELETION_DATE "trash::deletion-date" /* string */ #define G_FILE_ATTRIBUTE_RECENT_MODIFIED "recent::modified" /* int64 (time_t) */ #endif const char *factory_ui = "\n" "\n" " \n" "\n"; static GtkBuilderScope * create_scope (void) { #define ADD_SYMBOL(name) \ gtk_builder_cscope_add_callback_symbol (GTK_BUILDER_CSCOPE (scope), G_STRINGIFY (name), G_CALLBACK (name)) GtkBuilderScope *scope; scope = gtk_builder_cscope_new (); ADD_SYMBOL (get_object); ADD_SYMBOL (get_string); ADD_SYMBOL (get_boolean); return scope; #undef ADD_SYMBOL } static void add_extra_columns (GtkColumnView *view, GtkBuilderScope *scope) { GtkColumnViewColumn *column; GtkSorter *sorter; GBytes *bytes; guint i; for (i = 0; i < G_N_ELEMENTS(extra_columns); i++) { bytes = g_bytes_new_static (extra_columns[i].factory_xml, strlen (extra_columns[i].factory_xml)); column = gtk_column_view_column_new_with_factory (extra_columns[i].title, gtk_builder_list_item_factory_new_from_bytes (scope, bytes)); g_bytes_unref (bytes); sorter = gtk_custom_sorter_new (compare_file_attribute, (gpointer) extra_columns[i].attribute, NULL); gtk_column_view_column_set_sorter (column, sorter); g_object_unref (sorter); gtk_column_view_append_column (view, column); } } static void search_changed_cb (GtkSearchEntry *entry, GtkFilter *custom_filter) { gtk_filter_changed (custom_filter, GTK_FILTER_CHANGE_DIFFERENT); } int main (int argc, char *argv[]) { GListModel *toplevels; GtkWidget *win, *hbox, *vbox, *sw, *view, *list, *search_entry, *statusbar; GListModel *dirmodel; GtkTreeListModel *tree; GtkFilterListModel *filter; GtkFilter *custom_filter; GtkSortListModel *sort; GtkSorter *sorter; GFile *root; GtkBuilderScope *scope; GtkBuilder *builder; GError *error = NULL; GtkSelectionModel *selection; gtk_init (); win = gtk_window_new (); gtk_window_set_default_size (GTK_WINDOW (win), 800, 600); hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 6); gtk_window_set_child (GTK_WINDOW (win), hbox); vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0); gtk_box_append (GTK_BOX (hbox), vbox); search_entry = gtk_search_entry_new (); gtk_box_append (GTK_BOX (vbox), search_entry); sw = gtk_scrolled_window_new (); gtk_widget_set_hexpand (sw, TRUE); gtk_widget_set_vexpand (sw, TRUE); gtk_search_entry_set_key_capture_widget (GTK_SEARCH_ENTRY (search_entry), sw); gtk_box_append (GTK_BOX (vbox), sw); scope = create_scope (); builder = gtk_builder_new (); gtk_builder_set_scope (builder, scope); if (!gtk_builder_add_from_string (builder, ui_file, -1, &error)) { g_assert_no_error (error); } view = GTK_WIDGET (gtk_builder_get_object (builder, "view")); add_extra_columns (GTK_COLUMN_VIEW (view), scope); gtk_scrolled_window_set_child (GTK_SCROLLED_WINDOW (sw), view); g_object_unref (builder); if (argc > 1) { if (g_strcmp0 (argv[1], "--recent") == 0) { dirmodel = create_recent_files_list (); } else { root = g_file_new_for_commandline_arg (argv[1]); dirmodel = create_list_model_for_directory (root); g_object_unref (root); } } else { root = g_file_new_for_path (g_get_current_dir ()); dirmodel = create_list_model_for_directory (root); g_object_unref (root); } tree = gtk_tree_list_model_new (dirmodel, FALSE, TRUE, create_list_model_for_file_info, NULL, NULL); sorter = gtk_tree_list_row_sorter_new (g_object_ref (gtk_column_view_get_sorter (GTK_COLUMN_VIEW (view)))); sort = gtk_sort_list_model_new (G_LIST_MODEL (tree), sorter); custom_filter = gtk_custom_filter_new (match_file, g_object_ref (search_entry), g_object_unref); filter = gtk_filter_list_model_new (G_LIST_MODEL (sort), custom_filter); g_signal_connect (search_entry, "search-changed", G_CALLBACK (search_changed_cb), custom_filter); selection = GTK_SELECTION_MODEL (gtk_single_selection_new (G_LIST_MODEL (filter))); gtk_column_view_set_model (GTK_COLUMN_VIEW (view), selection); g_object_unref (selection); statusbar = gtk_statusbar_new (); gtk_widget_add_tick_callback (statusbar, (GtkTickCallback) update_statusbar, NULL, NULL); g_object_set_data (G_OBJECT (statusbar), "model", filter); g_signal_connect_swapped (filter, "items-changed", G_CALLBACK (update_statusbar), statusbar); update_statusbar (GTK_STATUSBAR (statusbar)); gtk_box_append (GTK_BOX (vbox), statusbar); g_object_unref (filter); list = gtk_list_view_new ( GTK_SELECTION_MODEL (gtk_single_selection_new (g_object_ref (gtk_column_view_get_columns (GTK_COLUMN_VIEW (view))))), gtk_builder_list_item_factory_new_from_bytes (scope, g_bytes_new_static (factory_ui, strlen (factory_ui)))); gtk_box_append (GTK_BOX (hbox), list); g_object_unref (scope); gtk_widget_show (win); toplevels = gtk_window_get_toplevels (); while (g_list_model_get_n_items (toplevels)) g_main_context_iteration (NULL, TRUE); return 0; }