From c6b909ed3bb293fad58e9755549760cabba6daa6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ant=C3=B3nio=20Fernandes?= Date: Wed, 6 Apr 2022 00:56:57 +0100 Subject: [PATCH 1/9] view-model: Use GSortListModel internally This is a required step in order to use GtkTreeListModel later. GtkTreeListModel doesn't work well when its internal models get resorted: it assumes the items are gone, not reordered, causing the row objects to get destroyed and the view to lose track of things. This might not be as efficient, but we've got no other option other than implementing a custom tree list model... --- src/nautilus-view-model.c | 96 ++++++++++++++++----------------------- src/nautilus-view-model.h | 3 +- 2 files changed, 41 insertions(+), 58 deletions(-) diff --git a/src/nautilus-view-model.c b/src/nautilus-view-model.c index 71b838af0..adff16658 100644 --- a/src/nautilus-view-model.c +++ b/src/nautilus-view-model.c @@ -8,9 +8,8 @@ struct _NautilusViewModel GHashTable *map_files_to_model; GListStore *internal_model; + GtkSortListModel *sort_model; GtkMultiSelection *selection_model; - GtkSorter *sorter; - gulong sorter_changed_id; }; static GType @@ -38,12 +37,12 @@ nautilus_view_model_get_item (GListModel *list, { NautilusViewModel *self = NAUTILUS_VIEW_MODEL (list); - if (self->internal_model == NULL) + if (self->sort_model == NULL) { return NULL; } - return g_list_model_get_item (G_LIST_MODEL (self->internal_model), position); + return g_list_model_get_item (G_LIST_MODEL (self->sort_model), position); } static void @@ -137,16 +136,16 @@ dispose (GObject *object) self->selection_model = NULL; } - if (self->internal_model != NULL) + if (self->sort_model != NULL) { - g_signal_handlers_disconnect_by_func (self->internal_model, + g_signal_handlers_disconnect_by_func (self->sort_model, g_list_model_items_changed, self); - g_object_unref (self->internal_model); - self->internal_model = NULL; + g_object_unref (self->sort_model); + self->sort_model = NULL; } - g_clear_signal_handler (&self->sorter_changed_id, self->sorter); + g_clear_object (&self->internal_model); G_OBJECT_CLASS (nautilus_view_model_parent_class)->dispose (object); } @@ -159,7 +158,6 @@ finalize (GObject *object) G_OBJECT_CLASS (nautilus_view_model_parent_class)->finalize (object); g_hash_table_destroy (self->map_files_to_model); - g_clear_object (&self->sorter); } static void @@ -216,10 +214,11 @@ constructed (GObject *object) G_OBJECT_CLASS (nautilus_view_model_parent_class)->constructed (object); self->internal_model = g_list_store_new (NAUTILUS_TYPE_VIEW_ITEM); - self->selection_model = gtk_multi_selection_new (g_object_ref (G_LIST_MODEL (self->internal_model))); + self->sort_model = gtk_sort_list_model_new (g_object_ref (G_LIST_MODEL (self->internal_model)), NULL); + self->selection_model = gtk_multi_selection_new (g_object_ref (G_LIST_MODEL (self->sort_model))); self->map_files_to_model = g_hash_table_new (NULL, NULL); - g_signal_connect_swapped (self->internal_model, "items-changed", + g_signal_connect_swapped (self->sort_model, "items-changed", G_CALLBACK (g_list_model_items_changed), self); g_signal_connect_swapped (self->selection_model, "selection-changed", G_CALLBACK (gtk_selection_model_selection_changed), self); @@ -250,31 +249,6 @@ nautilus_view_model_init (NautilusViewModel *self) { } -static gint -compare_data_func (gconstpointer a, - gconstpointer b, - gpointer user_data) -{ - NautilusViewModel *self = NAUTILUS_VIEW_MODEL (user_data); - - if (self->sorter == NULL) - { - return GTK_ORDERING_EQUAL; - } - - return gtk_sorter_compare (self->sorter, (gpointer) a, (gpointer) b); -} - -static void -on_sorter_changed (GtkSorter *sorter, - GtkSorterChange change, - gpointer user_data) -{ - NautilusViewModel *self = NAUTILUS_VIEW_MODEL (user_data); - - g_list_store_sort (self->internal_model, compare_data_func, self); -} - NautilusViewModel * nautilus_view_model_new () { @@ -284,26 +258,24 @@ nautilus_view_model_new () GtkSorter * nautilus_view_model_get_sorter (NautilusViewModel *self) { - return self->sorter; + return gtk_sort_list_model_get_sorter (self->sort_model); } void nautilus_view_model_set_sorter (NautilusViewModel *self, GtkSorter *sorter) { - if (self->sorter != NULL) - { - g_clear_signal_handler (&self->sorter_changed_id, self->sorter); - } + gtk_sort_list_model_set_sorter (self->sort_model, sorter); - if (g_set_object (&self->sorter, sorter)) - { - g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_SORTER]); - } + g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_SORTER]); +} - self->sorter_changed_id = g_signal_connect (self->sorter, "changed", - G_CALLBACK (on_sorter_changed), self); - g_list_store_sort (self->internal_model, compare_data_func, self); +void +nautilus_view_model_sort (NautilusViewModel *self) +{ + /* Hack: Reset the sorter to trigger ressorting. */ + gtk_sort_list_model_set_sorter (self->sort_model, + gtk_sort_list_model_get_sorter (self->sort_model)); } GQueue * @@ -380,7 +352,7 @@ nautilus_view_model_add_item (NautilusViewModel *self, g_hash_table_insert (self->map_files_to_model, nautilus_view_item_get_file (item), item); - g_list_store_insert_sorted (self->internal_model, item, compare_data_func, self); + g_list_store_append (self->internal_model, item); } void @@ -406,19 +378,29 @@ nautilus_view_model_add_items (NautilusViewModel *self, g_list_store_splice (self->internal_model, g_list_model_get_n_items (G_LIST_MODEL (self->internal_model)), 0, array, g_queue_get_length (items)); - - g_list_store_sort (self->internal_model, compare_data_func, self); } guint nautilus_view_model_get_index (NautilusViewModel *self, NautilusViewItem *item) { - guint i = G_MAXUINT; - gboolean found; + guint n_items; + guint i = 0; - found = g_list_store_find (self->internal_model, item, &i); - g_warn_if_fail (found); + n_items = g_list_model_get_n_items (G_LIST_MODEL (self->sort_model)); + while (i < n_items) + { + g_autoptr (NautilusViewItem) item_i = NULL; + + item_i = NAUTILUS_VIEW_ITEM (g_list_model_get_item (G_LIST_MODEL (self->sort_model), i)); + g_warn_if_fail (item_i != NULL); + + if (item_i == item) + { + return i; + } + i++; + } - return i; + return G_MAXUINT; } diff --git a/src/nautilus-view-model.h b/src/nautilus-view-model.h index 839c6d2c7..00d857b2e 100644 --- a/src/nautilus-view-model.h +++ b/src/nautilus-view-model.h @@ -15,8 +15,9 @@ NautilusViewModel * nautilus_view_model_new (void); GtkSorter *nautilus_view_model_get_sorter (NautilusViewModel *self); void nautilus_view_model_set_sorter (NautilusViewModel *self, GtkSorter *sorter); +void nautilus_view_model_sort (NautilusViewModel *self); NautilusViewItem * nautilus_view_model_get_item_from_file (NautilusViewModel *self, - NautilusFile *file); + NautilusFile *file); GQueue * nautilus_view_model_get_items_from_files (NautilusViewModel *self, GQueue *files); /* Don't use inside a loop, use nautilus_view_model_remove_all_items instead. */ -- GitLab From 04ccf8637119fc8f876f102c1b887c5a33a8631e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ant=C3=B3nio=20Fernandes?= Date: Tue, 5 Apr 2022 16:14:16 +0100 Subject: [PATCH 2/9] view-model: Use GtkTreeViewModel internally The view doesn't see any difference yet, but this prepares for tree expansion support later. --- src/nautilus-view-model.c | 247 +++++++++++++++++++++++++++++--------- src/nautilus-view-model.h | 8 +- 2 files changed, 199 insertions(+), 56 deletions(-) diff --git a/src/nautilus-view-model.c b/src/nautilus-view-model.c index adff16658..53aae16ca 100644 --- a/src/nautilus-view-model.c +++ b/src/nautilus-view-model.c @@ -7,11 +7,45 @@ struct _NautilusViewModel GObject parent_instance; GHashTable *map_files_to_model; - GListStore *internal_model; + GHashTable *directory_reverse_map; + + GtkTreeListModel *tree_model; GtkSortListModel *sort_model; GtkMultiSelection *selection_model; + + gboolean expand_as_a_tree; }; +static inline GListStore * +get_directory_store (NautilusViewModel *self, + NautilusFile *directory) +{ + GListStore *store; + + store = g_hash_table_lookup (self->directory_reverse_map, directory); + if (store == NULL) + { + store = G_LIST_STORE (gtk_tree_list_model_get_model (self->tree_model)); + } + + return store; +} + +static inline GtkTreeListRow * +get_child_row (NautilusViewModel *self, + GtkTreeListRow *parent, + guint position) +{ + if (parent != NULL) + { + return gtk_tree_list_row_get_child_row (parent, position); + } + else + { + return gtk_tree_list_model_get_child_row (self->tree_model, position); + } +} + static GType nautilus_view_model_get_item_type (GListModel *list) { @@ -23,12 +57,12 @@ nautilus_view_model_get_n_items (GListModel *list) { NautilusViewModel *self = NAUTILUS_VIEW_MODEL (list); - if (self->internal_model == NULL) + if (self->tree_model == NULL) { return 0; } - return g_list_model_get_n_items (G_LIST_MODEL (self->internal_model)); + return g_list_model_get_n_items (G_LIST_MODEL (self->tree_model)); } static gpointer @@ -36,13 +70,15 @@ nautilus_view_model_get_item (GListModel *list, guint position) { NautilusViewModel *self = NAUTILUS_VIEW_MODEL (list); + g_autoptr (GtkTreeListRow) row = NULL; if (self->sort_model == NULL) { return NULL; } - return g_list_model_get_item (G_LIST_MODEL (self->sort_model), position); + row = g_list_model_get_item (G_LIST_MODEL (self->sort_model), position); + return gtk_tree_list_row_get_item (row); } static void @@ -145,7 +181,7 @@ dispose (GObject *object) self->sort_model = NULL; } - g_clear_object (&self->internal_model); + g_clear_object (&self->tree_model); G_OBJECT_CLASS (nautilus_view_model_parent_class)->dispose (object); } @@ -158,6 +194,7 @@ finalize (GObject *object) G_OBJECT_CLASS (nautilus_view_model_parent_class)->finalize (object); g_hash_table_destroy (self->map_files_to_model); + g_hash_table_destroy (self->directory_reverse_map); } static void @@ -206,6 +243,34 @@ set_property (GObject *object, } } +static GListModel * +create_model_func (GObject *item, + NautilusViewModel *self) +{ + NautilusFile *file; + GListStore *store; + + if (!self->expand_as_a_tree) + { + return NULL; + } + + file = nautilus_view_item_get_file (NAUTILUS_VIEW_ITEM (item)); + if (!nautilus_file_is_directory (file)) + { + return NULL; + } + + store = g_hash_table_lookup (self->directory_reverse_map, file); + if (store == NULL) + { + store = g_list_store_new (NAUTILUS_TYPE_VIEW_ITEM); + g_hash_table_insert (self->directory_reverse_map, file, store); + } + + return g_object_ref (G_LIST_MODEL (store)); +} + static void constructed (GObject *object) { @@ -213,10 +278,15 @@ constructed (GObject *object) G_OBJECT_CLASS (nautilus_view_model_parent_class)->constructed (object); - self->internal_model = g_list_store_new (NAUTILUS_TYPE_VIEW_ITEM); - self->sort_model = gtk_sort_list_model_new (g_object_ref (G_LIST_MODEL (self->internal_model)), NULL); + self->tree_model = gtk_tree_list_model_new (G_LIST_MODEL (g_list_store_new (NAUTILUS_TYPE_VIEW_ITEM)), + FALSE, FALSE, + (GtkTreeListModelCreateModelFunc) create_model_func, + self, NULL); + self->sort_model = gtk_sort_list_model_new (g_object_ref (G_LIST_MODEL (self->tree_model)), NULL); self->selection_model = gtk_multi_selection_new (g_object_ref (G_LIST_MODEL (self->sort_model))); + self->map_files_to_model = g_hash_table_new (NULL, NULL); + self->directory_reverse_map = g_hash_table_new_full (NULL, NULL, NULL, g_object_unref); g_signal_connect_swapped (self->sort_model, "items-changed", G_CALLBACK (g_list_model_items_changed), self); @@ -258,14 +328,23 @@ nautilus_view_model_new () GtkSorter * nautilus_view_model_get_sorter (NautilusViewModel *self) { - return gtk_sort_list_model_get_sorter (self->sort_model); + GtkTreeListRowSorter *row_sorter; + + row_sorter = GTK_TREE_LIST_ROW_SORTER (gtk_sort_list_model_get_sorter (self->sort_model)); + + return gtk_tree_list_row_sorter_get_sorter (row_sorter); } void nautilus_view_model_set_sorter (NautilusViewModel *self, GtkSorter *sorter) { - gtk_sort_list_model_set_sorter (self->sort_model, sorter); + GtkTreeListRowSorter *row_sorter; + + row_sorter = gtk_tree_list_row_sorter_new (NULL); + + gtk_tree_list_row_sorter_set_sorter (row_sorter, sorter); + gtk_sort_list_model_set_sorter (self->sort_model, GTK_SORTER (row_sorter)); g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_SORTER]); } @@ -283,33 +362,15 @@ nautilus_view_model_get_items_from_files (NautilusViewModel *self, GQueue *files) { GList *l; - guint n_items; GQueue *items; - n_items = g_list_model_get_n_items (G_LIST_MODEL (self->internal_model)); items = g_queue_new (); for (l = g_queue_peek_head_link (files); l != NULL; l = l->next) { - NautilusFile *file1; + NautilusViewItem *item; - file1 = NAUTILUS_FILE (l->data); - for (guint i = 0; i < n_items; i++) - { - g_autoptr (NautilusViewItem) item = NULL; - NautilusFile *file2; - g_autofree gchar *file1_uri = NULL; - g_autofree gchar *file2_uri = NULL; - - item = g_list_model_get_item (G_LIST_MODEL (self->internal_model), i); - file2 = nautilus_view_item_get_file (item); - file1_uri = nautilus_file_get_uri (file1); - file2_uri = nautilus_file_get_uri (file2); - if (g_strcmp0 (file1_uri, file2_uri) == 0) - { - g_queue_push_tail (items, item); - break; - } - } + item = nautilus_view_model_get_item_from_file (self, l->data); + g_queue_push_tail (items, item); } return items; @@ -326,58 +387,98 @@ void nautilus_view_model_remove_item (NautilusViewModel *self, NautilusViewItem *item) { + NautilusFile *file; + g_autoptr (NautilusFile) parent = NULL; + GListStore *dir_store; guint i; - if (g_list_store_find (self->internal_model, item, &i)) + file = nautilus_view_item_get_file (item); + parent = nautilus_file_get_parent (file); + dir_store = get_directory_store (self, parent); + if (g_list_store_find (dir_store, item, &i)) { - NautilusFile *file; - - file = nautilus_view_item_get_file (item); - g_list_store_remove (self->internal_model, i); + g_list_store_remove (dir_store, i); g_hash_table_remove (self->map_files_to_model, file); + if (nautilus_file_is_directory (file)) + { + g_hash_table_remove (self->directory_reverse_map, file); + } } } void nautilus_view_model_remove_all_items (NautilusViewModel *self) { - g_list_store_remove_all (self->internal_model); + g_list_store_remove_all (G_LIST_STORE (gtk_tree_list_model_get_model (self->tree_model))); g_hash_table_remove_all (self->map_files_to_model); + g_hash_table_remove_all (self->directory_reverse_map); } void nautilus_view_model_add_item (NautilusViewModel *self, NautilusViewItem *item) { - g_hash_table_insert (self->map_files_to_model, - nautilus_view_item_get_file (item), - item); - g_list_store_append (self->internal_model, item); + NautilusFile *file; + g_autoptr (NautilusFile) parent = NULL; + + file = nautilus_view_item_get_file (item); + parent = nautilus_file_get_parent (file); + + g_list_store_append (get_directory_store (self, parent), item); + g_hash_table_insert (self->map_files_to_model, file, item); +} + +static void +splice_items_into_common_parent (NautilusViewModel *self, + GPtrArray *items, + NautilusFile *common_parent) +{ + GListStore *dir_store; + + dir_store = get_directory_store (self, common_parent); + g_list_store_splice (dir_store, + g_list_model_get_n_items (G_LIST_MODEL (dir_store)), + 0, items->pdata, items->len); } void nautilus_view_model_add_items (NautilusViewModel *self, GQueue *items) { - g_autofree gpointer *array = NULL; + g_autoptr (GPtrArray) array = g_ptr_array_new (); + g_autoptr (NautilusFile) previous_parent = NULL; GList *l; - int i = 0; - - array = g_malloc_n (g_queue_get_length (items), - sizeof (NautilusViewItem *)); + NautilusViewItem *item; for (l = g_queue_peek_head_link (items); l != NULL; l = l->next) { - array[i] = l->data; + g_autoptr (NautilusFile) parent = NULL; + + item = NAUTILUS_VIEW_ITEM (l->data); + parent = nautilus_file_get_parent (nautilus_view_item_get_file (item)); + + if (previous_parent != NULL && previous_parent != parent) + { + /* The pending items share a common parent. */ + splice_items_into_common_parent (self, array, previous_parent); + + /* Clear pending items and start a new with a new parent. */ + g_ptr_array_unref (array); + array = g_ptr_array_new (); + } + g_set_object (&previous_parent, parent); + + g_ptr_array_add (array, item); g_hash_table_insert (self->map_files_to_model, - nautilus_view_item_get_file (l->data), - l->data); - i++; + nautilus_view_item_get_file (item), + item); } - g_list_store_splice (self->internal_model, - g_list_model_get_n_items (G_LIST_MODEL (self->internal_model)), - 0, array, g_queue_get_length (items)); + if (previous_parent != NULL) + { + /* Flush the pending items. */ + splice_items_into_common_parent (self, array, previous_parent); + } } guint @@ -390,10 +491,14 @@ nautilus_view_model_get_index (NautilusViewModel *self, n_items = g_list_model_get_n_items (G_LIST_MODEL (self->sort_model)); while (i < n_items) { + g_autoptr (GtkTreeListRow) row = NULL; g_autoptr (NautilusViewItem) item_i = NULL; - item_i = NAUTILUS_VIEW_ITEM (g_list_model_get_item (G_LIST_MODEL (self->sort_model), i)); - g_warn_if_fail (item_i != NULL); + row = g_list_model_get_item (G_LIST_MODEL (self->sort_model), i); + g_warn_if_fail (GTK_IS_TREE_LIST_ROW (row)); + + item_i = gtk_tree_list_row_get_item (row); + g_warn_if_fail (NAUTILUS_IS_VIEW_ITEM (item_i)); if (item_i == item) { @@ -404,3 +509,37 @@ nautilus_view_model_get_index (NautilusViewModel *self, return G_MAXUINT; } + +void +nautilus_view_model_clear_subdirectory (NautilusViewModel *self, + NautilusViewItem *item) +{ + NautilusFile *file; + GListModel *children; + guint n_children = 0; + + g_return_if_fail (NAUTILUS_IS_VIEW_MODEL (self)); + g_return_if_fail (NAUTILUS_IS_VIEW_ITEM (item)); + + file = nautilus_view_item_get_file (item); + children = G_LIST_MODEL (g_hash_table_lookup (self->directory_reverse_map, file)); + n_children = (children != NULL) ? g_list_model_get_n_items (children) : 0; + for (guint i = 0; i < n_children; i++) + { + g_autoptr (NautilusViewItem) child = g_list_model_get_item (children, i); + + if (nautilus_file_is_directory (nautilus_view_item_get_file (child))) + { + /* Clear recursively */ + nautilus_view_model_clear_subdirectory (self, child); + } + } + g_hash_table_remove (self->directory_reverse_map, file); +} + +void +nautilus_view_model_expand_as_a_tree (NautilusViewModel *self, + gboolean expand_as_a_tree) +{ + self->expand_as_a_tree = expand_as_a_tree; +} diff --git a/src/nautilus-view-model.h b/src/nautilus-view-model.h index 00d857b2e..5f613ca72 100644 --- a/src/nautilus-view-model.h +++ b/src/nautilus-view-model.h @@ -29,7 +29,11 @@ void nautilus_view_model_add_item (NautilusViewModel *self, NautilusViewItem *item); void nautilus_view_model_add_items (NautilusViewModel *self, GQueue *items); -guint nautilus_view_model_get_index (NautilusViewModel *self, - NautilusViewItem *item); +guint nautilus_view_model_get_index (NautilusViewModel *self, + NautilusViewItem *item); +void nautilus_view_model_clear_subdirectory (NautilusViewModel *self, + NautilusViewItem *item); +void nautilus_view_model_expand_as_a_tree (NautilusViewModel *self, + gboolean expand_as_a_tree); G_END_DECLS -- GitLab From 4a2d5147e1d7e92f54ba22a48ffebcd0c3e6f611 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ant=C3=B3nio=20Fernandes?= Date: Wed, 6 Apr 2022 01:22:53 +0100 Subject: [PATCH 3/9] view-model: Expose row objects to the view This is going to allow us to reuse the model for a GtkColumnView-based list view with expanding trees support. As documented, GtkTreeViewRow:item may be NULL when the item it held is destroyed. So, we must take care to handle the NULL case when expected and also warn and return when not expected. --- src/nautilus-grid-view.c | 11 ++++++++--- src/nautilus-list-base-private.h | 3 +++ src/nautilus-list-base.c | 13 +++++++++---- src/nautilus-list-view.c | 8 +++----- src/nautilus-view-model.c | 8 +++----- 5 files changed, 26 insertions(+), 17 deletions(-) diff --git a/src/nautilus-grid-view.c b/src/nautilus-grid-view.c index 74a80e1f5..344bbbf06 100644 --- a/src/nautilus-grid-view.c +++ b/src/nautilus-grid-view.c @@ -349,7 +349,8 @@ bind_cell (GtkSignalListItemFactory *factory, NautilusViewItem *item; cell = gtk_list_item_get_child (listitem); - item = NAUTILUS_VIEW_ITEM (gtk_list_item_get_item (listitem)); + item = listitem_get_view_item (listitem); + g_return_if_fail (item != NULL); nautilus_view_item_set_item_ui (item, cell); @@ -377,9 +378,13 @@ unbind_cell (GtkSignalListItemFactory *factory, { NautilusViewItem *item; - item = NAUTILUS_VIEW_ITEM (gtk_list_item_get_item (listitem)); + item = listitem_get_view_item (listitem); - nautilus_view_item_set_item_ui (item, NULL); + /* item may be NULL when row has just been destroyed. */ + if (item != NULL) + { + nautilus_view_item_set_item_ui (item, NULL); + } } static void diff --git a/src/nautilus-list-base-private.h b/src/nautilus-list-base-private.h index 96944d52e..c5d2ebbce 100644 --- a/src/nautilus-list-base-private.h +++ b/src/nautilus-list-base-private.h @@ -30,4 +30,7 @@ const NautilusFileSortType get_sorts_type_from_metadata_text (const char *m void setup_cell_common (GtkListItem *listitem, NautilusViewCell *cell); +#define listitem_get_view_item(li) \ +(NAUTILUS_VIEW_ITEM (gtk_tree_list_row_get_item (GTK_TREE_LIST_ROW (gtk_list_item_get_item (li))))) + G_END_DECLS diff --git a/src/nautilus-list-base.c b/src/nautilus-list-base.c index 4bd754b8e..5c1bbdb0a 100644 --- a/src/nautilus-list-base.c +++ b/src/nautilus-list-base.c @@ -105,7 +105,10 @@ static inline NautilusViewItem * get_view_item (GListModel *model, guint position) { - return NAUTILUS_VIEW_ITEM (g_list_model_get_item (model, position)); + g_autoptr (GtkTreeListRow) row = g_list_model_get_item (model, position); + + g_return_val_if_fail (GTK_IS_TREE_LIST_ROW (row), NULL); + return NAUTILUS_VIEW_ITEM (gtk_tree_list_row_get_item (GTK_TREE_LIST_ROW (row))); } static const SortConstants * @@ -843,12 +846,14 @@ void setup_cell_common (GtkListItem *listitem, NautilusViewCell *cell) { + GtkExpression *expression; GtkEventController *controller; GtkDropTarget *drop_target; - g_object_bind_property (listitem, "item", - cell, "item", - G_BINDING_SYNC_CREATE); + expression = gtk_property_expression_new (GTK_TYPE_LIST_ITEM, NULL, "item"); + expression = gtk_property_expression_new (GTK_TYPE_TREE_LIST_ROW, expression, "item"); + gtk_expression_bind (expression, cell, "item", listitem); + gtk_list_item_set_child (listitem, GTK_WIDGET (cell)); controller = GTK_EVENT_CONTROLLER (gtk_gesture_click_new ()); diff --git a/src/nautilus-list-view.c b/src/nautilus-list-view.c index e2389eac3..f785345d2 100644 --- a/src/nautilus-list-view.c +++ b/src/nautilus-list-view.c @@ -839,9 +839,7 @@ real_sort_directories_first_changed (NautilusFilesView *files_view) self->directories_first = nautilus_files_view_should_sort_directories_first (NAUTILUS_FILES_VIEW (self)); - /* Reset the sorter to trigger ressorting */ - model = nautilus_list_base_get_model (NAUTILUS_LIST_BASE (self)); - nautilus_view_model_set_sorter (model, nautilus_view_model_get_sorter (model)); + nautilus_view_model_sort (nautilus_list_base_get_model (NAUTILUS_LIST_BASE (self))); } static void @@ -947,7 +945,7 @@ bind_name_cell (GtkSignalListItemFactory *factory, { NautilusViewItem *item; - item = NAUTILUS_VIEW_ITEM (gtk_list_item_get_item (listitem)); + item = listitem_get_view_item (listitem); nautilus_view_item_set_item_ui (item, gtk_list_item_get_child (listitem)); } @@ -959,7 +957,7 @@ unbind_name_cell (GtkSignalListItemFactory *factory, { NautilusViewItem *item; - item = NAUTILUS_VIEW_ITEM (gtk_list_item_get_item (listitem)); + item = listitem_get_view_item (listitem); g_return_if_fail (NAUTILUS_IS_VIEW_ITEM (item)); nautilus_view_item_set_item_ui (item, NULL); diff --git a/src/nautilus-view-model.c b/src/nautilus-view-model.c index 53aae16ca..82692ccdd 100644 --- a/src/nautilus-view-model.c +++ b/src/nautilus-view-model.c @@ -49,7 +49,7 @@ get_child_row (NautilusViewModel *self, static GType nautilus_view_model_get_item_type (GListModel *list) { - return NAUTILUS_TYPE_VIEW_ITEM; + return GTK_TYPE_TREE_LIST_ROW; } static guint @@ -70,15 +70,13 @@ nautilus_view_model_get_item (GListModel *list, guint position) { NautilusViewModel *self = NAUTILUS_VIEW_MODEL (list); - g_autoptr (GtkTreeListRow) row = NULL; if (self->sort_model == NULL) { return NULL; } - row = g_list_model_get_item (G_LIST_MODEL (self->sort_model), position); - return gtk_tree_list_row_get_item (row); + return g_list_model_get_item (G_LIST_MODEL (self->sort_model), position); } static void @@ -332,7 +330,7 @@ nautilus_view_model_get_sorter (NautilusViewModel *self) row_sorter = GTK_TREE_LIST_ROW_SORTER (gtk_sort_list_model_get_sorter (self->sort_model)); - return gtk_tree_list_row_sorter_get_sorter (row_sorter); + return row_sorter != NULL ? gtk_tree_list_row_sorter_get_sorter (row_sorter) : NULL; } void -- GitLab From 783b039350d9465ef5b1998e01bcdea88641d501 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ant=C3=B3nio=20Fernandes?= Date: Wed, 6 Apr 2022 00:40:00 +0100 Subject: [PATCH 4/9] list-view: Support expanding as a tree Starred, Recent and Search still not supported due to bugs --- src/nautilus-files-view.c | 10 ++ src/nautilus-files-view.h | 2 + src/nautilus-list-view.c | 162 +++++++++++++++++- src/nautilus-name-cell.c | 8 + src/nautilus-name-cell.h | 1 + src/resources/ui/nautilus-name-cell.ui | 155 +++++++++-------- .../ui/nautilus-preferences-window.ui | 1 - 7 files changed, 262 insertions(+), 77 deletions(-) diff --git a/src/nautilus-files-view.c b/src/nautilus-files-view.c index 0486c8a17..b123f2ce1 100644 --- a/src/nautilus-files-view.c +++ b/src/nautilus-files-view.c @@ -4773,6 +4773,16 @@ load_error_callback (NautilusDirectory *directory, nautilus_files_view_get_containing_window (view)); } +gboolean +nautilus_files_view_has_subdirectory (NautilusFilesView *view, + NautilusDirectory *directory) +{ + NautilusFilesViewPrivate *priv; + priv = nautilus_files_view_get_instance_private (view); + + return g_list_find (priv->subdirectory_list, directory) != NULL; +} + void nautilus_files_view_add_subdirectory (NautilusFilesView *view, NautilusDirectory *directory) diff --git a/src/nautilus-files-view.h b/src/nautilus-files-view.h index e1882b7ba..ae2031920 100644 --- a/src/nautilus-files-view.h +++ b/src/nautilus-files-view.h @@ -268,6 +268,8 @@ gboolean nautilus_files_view_should_show_file (Nautil gboolean nautilus_files_view_should_sort_directories_first (NautilusFilesView *view); void nautilus_files_view_ignore_hidden_file_preferences (NautilusFilesView *view); +gboolean nautilus_files_view_has_subdirectory (NautilusFilesView *view, + NautilusDirectory *directory); void nautilus_files_view_add_subdirectory (NautilusFilesView *view, NautilusDirectory *directory); void nautilus_files_view_remove_subdirectory (NautilusFilesView *view, diff --git a/src/nautilus-list-view.c b/src/nautilus-list-view.c index f785345d2..5e0244d1d 100644 --- a/src/nautilus-list-view.c +++ b/src/nautilus-list-view.c @@ -27,6 +27,9 @@ #include "nautilus-star-cell.h" #include "nautilus-tag-manager.h" +/* We wait two seconds after row is collapsed to unload the subdirectory */ +#define COLLAPSE_TO_UNLOAD_DELAY 2 + struct _NautilusListView { NautilusListBase parent_instance; @@ -37,6 +40,7 @@ struct _NautilusListView gint zoom_level; gboolean directories_first; + gboolean expand_as_a_tree; GQuark path_attribute_q; GFile *file_path_base_location; @@ -737,12 +741,19 @@ static void real_begin_loading (NautilusFilesView *files_view) { NautilusListView *self = NAUTILUS_LIST_VIEW (files_view); + NautilusViewModel *model; NautilusFile *file; NAUTILUS_FILES_VIEW_CLASS (nautilus_list_view_parent_class)->begin_loading (files_view); update_columns_settings_from_metadata_and_preferences (self); + /* TODO Reload the view if this setting changes. We can't easily switch + * tree mode on/off on an already loaded view and the preference is not + * expected to be changed frequently. */ + self->expand_as_a_tree = g_settings_get_boolean (nautilus_list_view_preferences, + NAUTILUS_PREFERENCES_LIST_VIEW_USE_TREE); + self->path_attribute_q = 0; g_clear_object (&self->file_path_base_location); file = nautilus_files_view_get_directory_as_file (files_view); @@ -757,7 +768,21 @@ real_begin_loading (NautilusFilesView *files_view) { self->path_attribute_q = g_quark_from_string ("where"); self->file_path_base_location = get_base_location (self); + + /* Forcefully disabling tree in these special locations because this + * view and its model currently don't expect the same file appearing + * more than once. + * + * NautilusFilesView still has support for the same file being present + * in multiple directories (struct FileAndDirectory), so, if someone + * cares enough about expanding folders in these special locations: + * TODO: Making the model items aware of their current model instead of + * relying on `nautilus_file_get_parent()`. */ + self->expand_as_a_tree = FALSE; } + + model = nautilus_list_base_get_model (NAUTILUS_LIST_BASE (self)); + nautilus_view_model_expand_as_a_tree (model, self->expand_as_a_tree); } static void @@ -835,7 +860,6 @@ static void real_sort_directories_first_changed (NautilusFilesView *files_view) { NautilusListView *self = NAUTILUS_LIST_VIEW (files_view); - NautilusViewModel *model; self->directories_first = nautilus_files_view_should_sort_directories_first (NAUTILUS_FILES_VIEW (self)); @@ -862,6 +886,114 @@ real_get_view_id (NautilusFilesView *files_view) return NAUTILUS_VIEW_LIST_ID; } +typedef struct +{ + NautilusListView *self; + NautilusViewItem *item; + NautilusDirectory *directory; +} UnloadDelayData; + +static void +unload_delay_data_free (UnloadDelayData *unload_data) +{ + g_clear_weak_pointer (&unload_data->self); + g_clear_object (&unload_data->item); + g_clear_object (&unload_data->directory); + + g_free (unload_data); +} + +G_DEFINE_AUTOPTR_CLEANUP_FUNC (UnloadDelayData, unload_delay_data_free) + +static UnloadDelayData * +unload_delay_data_new (NautilusListView *self, + NautilusViewItem *item, + NautilusDirectory *directory) +{ + UnloadDelayData *unload_data; + + unload_data = g_new0 (UnloadDelayData, 1); + g_set_weak_pointer (&unload_data->self, self); + g_set_object (&unload_data->item, item); + g_set_object (&unload_data->directory, directory); + + return unload_data; +} + +static gboolean +unload_file_timeout (gpointer data) +{ + g_autoptr (UnloadDelayData) unload_data = data; + NautilusListView *self = unload_data->self; + NautilusViewModel *model = nautilus_list_base_get_model (NAUTILUS_LIST_BASE (self)); + if (unload_data->self == NULL) + { + return G_SOURCE_REMOVE; + } + + for (guint i = 0; i < g_list_model_get_n_items (G_LIST_MODEL (model)); i++) + { + g_autoptr (GtkTreeListRow) row = g_list_model_get_item (G_LIST_MODEL (model), i); + g_autoptr (NautilusViewItem) item = gtk_tree_list_row_get_item (row); + if (item != NULL && item == unload_data->item) + { + if (gtk_tree_list_row_get_expanded (row)) + { + /* It has been expanded again before the timeout. Do nothing. */ + return G_SOURCE_REMOVE; + } + break; + } + } + + if (nautilus_files_view_has_subdirectory (NAUTILUS_FILES_VIEW (self), + unload_data->directory)) + { + nautilus_files_view_remove_subdirectory (NAUTILUS_FILES_VIEW (self), + unload_data->directory); + } + + /* The model holds a GListStore for every subdirectory. Empty it. */ + nautilus_view_model_clear_subdirectory (model, unload_data->item); + + return G_SOURCE_REMOVE; +} + +static void +on_row_expanded_changed (GObject *gobject, + GParamSpec *pspec, + gpointer user_data) +{ + GtkTreeListRow *row = GTK_TREE_LIST_ROW (gobject); + NautilusListView *self = NAUTILUS_LIST_VIEW (user_data); + NautilusViewItem *item; + g_autoptr (NautilusDirectory) directory = NULL; + gboolean expanded; + + item = NAUTILUS_VIEW_ITEM (gtk_tree_list_row_get_item (row)); + if (item == NULL) + { + /* Row has been destroyed. */ + return; + } + + directory = nautilus_directory_get_for_file (nautilus_view_item_get_file (item)); + expanded = gtk_tree_list_row_get_expanded (row); + if (expanded) + { + if (!nautilus_files_view_has_subdirectory (NAUTILUS_FILES_VIEW (self), directory)) + { + nautilus_files_view_add_subdirectory (NAUTILUS_FILES_VIEW (self), directory); + } + } + else + { + g_timeout_add_seconds (COLLAPSE_TO_UNLOAD_DELAY, + unload_file_timeout, + unload_delay_data_new (self, item, directory)); + } +} + static void on_item_click_released_workaround (GtkGestureClick *gesture, gint n_press, @@ -936,6 +1068,17 @@ setup_name_cell (GtkSignalListItemFactory *factory, gtk_list_item_set_child (listitem, GTK_WIDGET (cell)); setup_selection_click_workaround (cell); + + if (self->expand_as_a_tree) + { + GtkTreeExpander *expander; + + expander = nautilus_name_cell_get_expander (NAUTILUS_NAME_CELL (cell)); + gtk_tree_expander_set_indent_for_icon (expander, TRUE); + g_object_bind_property (listitem, "item", + expander, "list-row", + G_BINDING_SYNC_CREATE); + } } static void @@ -943,11 +1086,20 @@ bind_name_cell (GtkSignalListItemFactory *factory, GtkListItem *listitem, gpointer user_data) { + NautilusListView *self = user_data; NautilusViewItem *item; item = listitem_get_view_item (listitem); nautilus_view_item_set_item_ui (item, gtk_list_item_get_child (listitem)); + + if (self->expand_as_a_tree) + { + g_signal_connect_object (GTK_TREE_LIST_ROW (gtk_list_item_get_item (listitem)), + "notify::expanded", + G_CALLBACK (on_row_expanded_changed), + self, 0); + } } static void @@ -955,12 +1107,20 @@ unbind_name_cell (GtkSignalListItemFactory *factory, GtkListItem *listitem, gpointer user_data) { + NautilusListView *self = user_data; NautilusViewItem *item; item = listitem_get_view_item (listitem); g_return_if_fail (NAUTILUS_IS_VIEW_ITEM (item)); nautilus_view_item_set_item_ui (item, NULL); + + if (self->expand_as_a_tree) + { + g_signal_handlers_disconnect_by_func (gtk_list_item_get_item (listitem), + on_row_expanded_changed, + self); + } } static void diff --git a/src/nautilus-name-cell.c b/src/nautilus-name-cell.c index cf46e17e6..c8cf109ce 100644 --- a/src/nautilus-name-cell.c +++ b/src/nautilus-name-cell.c @@ -16,6 +16,7 @@ struct _NautilusNameCell GQuark path_attribute_q; GFile *file_path_base_location; + GtkWidget *expander; GtkWidget *fixed_height_box; GtkWidget *icon; GtkWidget *label; @@ -294,6 +295,7 @@ nautilus_name_cell_class_init (NautilusNameCellClass *klass) gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/nautilus/ui/nautilus-name-cell.ui"); + gtk_widget_class_bind_template_child (widget_class, NautilusNameCell, expander); gtk_widget_class_bind_template_child (widget_class, NautilusNameCell, fixed_height_box); gtk_widget_class_bind_template_child (widget_class, NautilusNameCell, icon); gtk_widget_class_bind_template_child (widget_class, NautilusNameCell, label); @@ -325,3 +327,9 @@ nautilus_name_cell_show_snippet (NautilusNameCell *self) { self->show_snippet = TRUE; } + +GtkTreeExpander * +nautilus_name_cell_get_expander (NautilusNameCell *self) +{ + return GTK_TREE_EXPANDER (self->expander); +} diff --git a/src/nautilus-name-cell.h b/src/nautilus-name-cell.h index 62862cdf5..11b84931e 100644 --- a/src/nautilus-name-cell.h +++ b/src/nautilus-name-cell.h @@ -19,5 +19,6 @@ void nautilus_name_cell_set_path (NautilusNameCell *self, GQuark path_attribute_q, GFile *base_location); void nautilus_name_cell_show_snippet (NautilusNameCell *self); +GtkTreeExpander * nautilus_name_cell_get_expander (NautilusNameCell *self); G_END_DECLS diff --git a/src/resources/ui/nautilus-name-cell.ui b/src/resources/ui/nautilus-name-cell.ui index f2105fb55..65db3d4a6 100644 --- a/src/resources/ui/nautilus-name-cell.ui +++ b/src/resources/ui/nautilus-name-cell.ui @@ -3,106 +3,111 @@ diff --git a/src/resources/ui/nautilus-preferences-window.ui b/src/resources/ui/nautilus-preferences-window.ui index abee4f2af..9b2cd2edc 100644 --- a/src/resources/ui/nautilus-preferences-window.ui +++ b/src/resources/ui/nautilus-preferences-window.ui @@ -34,7 +34,6 @@ _Expandable Folders in List View 0 True - False center -- GitLab From 8e6ad68b92c9cd292bd8819c24699337d400509a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ant=C3=B3nio=20Fernandes?= Date: Thu, 7 Apr 2022 00:17:45 +0100 Subject: [PATCH 5/9] list-view-item-ui: Show spinner while loading subfolders The spinner is shown only after 1 second without the directory having finished loading, to avoid visual noise when directories loads quickly. --- src/nautilus-list-view.c | 22 +++++++++++ src/nautilus-name-cell.c | 53 ++++++++++++++++++++++++++ src/nautilus-view-item.c | 27 +++++++++++++ src/nautilus-view-item.h | 2 + src/resources/ui/nautilus-name-cell.ui | 8 ++++ 5 files changed, 112 insertions(+) diff --git a/src/nautilus-list-view.c b/src/nautilus-list-view.c index 5e0244d1d..6fe04a84f 100644 --- a/src/nautilus-list-view.c +++ b/src/nautilus-list-view.c @@ -959,6 +959,18 @@ unload_file_timeout (gpointer data) return G_SOURCE_REMOVE; } +static void +on_subdirectory_done_loading (NautilusDirectory *directory, + GtkTreeListRow *row) +{ + NautilusViewItem *item; + + g_signal_handlers_disconnect_by_func (directory, on_subdirectory_done_loading, row); + + item = NAUTILUS_VIEW_ITEM (gtk_tree_list_row_get_item (row)); + nautilus_view_item_set_loading (item, FALSE); +} + static void on_row_expanded_changed (GObject *gobject, GParamSpec *pspec, @@ -985,9 +997,19 @@ on_row_expanded_changed (GObject *gobject, { nautilus_files_view_add_subdirectory (NAUTILUS_FILES_VIEW (self), directory); } + if (!nautilus_directory_are_all_files_seen (directory)) + { + nautilus_view_item_set_loading (item, TRUE); + g_signal_connect_object (directory, + "done-loading", + G_CALLBACK (on_subdirectory_done_loading), + row, + 0); + } } else { + nautilus_view_item_set_loading (item, FALSE); g_timeout_add_seconds (COLLAPSE_TO_UNLOAD_DELAY, unload_file_timeout, unload_delay_data_new (self, item, directory)); diff --git a/src/nautilus-name-cell.c b/src/nautilus-name-cell.c index c8cf109ce..d67357319 100644 --- a/src/nautilus-name-cell.c +++ b/src/nautilus-name-cell.c @@ -7,6 +7,8 @@ #include "nautilus-name-cell.h" #include "nautilus-file-utilities.h" +#define LOADING_TIMEOUT_SECONDS 1 + struct _NautilusNameCell { NautilusViewCell parent_instance; @@ -18,6 +20,7 @@ struct _NautilusNameCell GtkWidget *expander; GtkWidget *fixed_height_box; + GtkWidget *spinner; GtkWidget *icon; GtkWidget *label; GtkWidget *emblems_box; @@ -26,6 +29,7 @@ struct _NautilusNameCell GtkWidget *path; gboolean show_snippet; + guint loading_timeout_id; }; G_DEFINE_TYPE (NautilusNameCell, nautilus_name_cell, NAUTILUS_TYPE_VIEW_CELL) @@ -251,6 +255,52 @@ on_item_is_cut_changed (NautilusNameCell *self) } } +static gboolean +on_loading_timeout (gpointer user_data) +{ + NautilusNameCell *self = NAUTILUS_NAME_CELL (user_data); + gboolean is_loading; + + g_object_get (nautilus_view_cell_get_item (NAUTILUS_VIEW_CELL (self)), + "is-loading", &is_loading, + NULL); + if (is_loading) + { + gtk_widget_show (self->spinner); + gtk_spinner_start (GTK_SPINNER (self->spinner)); + } + + return G_SOURCE_REMOVE; +} + +static void +on_item_is_loading_changed (GObject *object, + GParamSpec *pspec, + gpointer user_data) +{ + NautilusNameCell *self = NAUTILUS_NAME_CELL (user_data); + gboolean is_loading; + + if (object != G_OBJECT (nautilus_view_cell_get_item (NAUTILUS_VIEW_CELL (self)))) + { + return; + } + + g_clear_handle_id (&self->loading_timeout_id, g_source_remove); + g_object_get (object, "is-loading", &is_loading, NULL); + if (is_loading) + { + self->loading_timeout_id = g_timeout_add_seconds (LOADING_TIMEOUT_SECONDS, + G_SOURCE_FUNC (on_loading_timeout), + self); + } + else + { + gtk_widget_hide (self->spinner); + gtk_spinner_stop (GTK_SPINNER (self->spinner)); + } +} + static void nautilus_name_cell_init (NautilusNameCell *self) { @@ -264,6 +314,8 @@ nautilus_name_cell_init (NautilusNameCell *self) (GCallback) on_item_drag_accept_changed, self); g_signal_group_connect_swapped (self->item_signal_group, "notify::is-cut", (GCallback) on_item_is_cut_changed, self); + g_signal_group_connect_swapped (self->item_signal_group, "notify::is-loading", + (GCallback) on_item_is_loading_changed, self); g_signal_group_connect_swapped (self->item_signal_group, "file-changed", (GCallback) on_file_changed, self); g_signal_connect_object (self->item_signal_group, "bind", @@ -297,6 +349,7 @@ nautilus_name_cell_class_init (NautilusNameCellClass *klass) gtk_widget_class_bind_template_child (widget_class, NautilusNameCell, expander); gtk_widget_class_bind_template_child (widget_class, NautilusNameCell, fixed_height_box); + gtk_widget_class_bind_template_child (widget_class, NautilusNameCell, spinner); gtk_widget_class_bind_template_child (widget_class, NautilusNameCell, icon); gtk_widget_class_bind_template_child (widget_class, NautilusNameCell, label); gtk_widget_class_bind_template_child (widget_class, NautilusNameCell, emblems_box); diff --git a/src/nautilus-view-item.c b/src/nautilus-view-item.c index 84423fa2d..31c11e3fe 100644 --- a/src/nautilus-view-item.c +++ b/src/nautilus-view-item.c @@ -12,6 +12,7 @@ struct _NautilusViewItem guint icon_size; gboolean is_cut; gboolean drag_accept; + gboolean is_loading; NautilusFile *file; GtkWidget *item_ui; }; @@ -25,6 +26,7 @@ enum PROP_ICON_SIZE, PROP_IS_CUT, PROP_DRAG_ACCEPT, + PROP_IS_LOADING, PROP_ITEM_UI, N_PROPS }; @@ -93,6 +95,12 @@ nautilus_view_item_get_property (GObject *object, } break; + case PROP_IS_LOADING: + { + g_value_set_boolean (value, self->is_loading); + } + break; + case PROP_ITEM_UI: { g_value_set_object (value, self->item_ui); @@ -140,6 +148,12 @@ nautilus_view_item_set_property (GObject *object, } break; + case PROP_IS_LOADING: + { + self->is_loading = g_value_get_boolean (value); + } + break; + case PROP_ITEM_UI: { g_set_object (&self->item_ui, g_value_get_object (value)); @@ -186,6 +200,10 @@ nautilus_view_item_class_init (NautilusViewItemClass *klass) "", "", NAUTILUS_TYPE_FILE, G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS); + properties[PROP_IS_LOADING] = g_param_spec_boolean ("is-loading", + "", "", + FALSE, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); properties[PROP_ITEM_UI] = g_param_spec_object ("item-ui", "", "", GTK_TYPE_WIDGET, @@ -246,6 +264,15 @@ nautilus_view_item_set_drag_accept (NautilusViewItem *self, g_object_set (self, "drag-accept", drag_accept, NULL); } +void +nautilus_view_item_set_loading (NautilusViewItem *self, + gboolean loading) +{ + g_return_if_fail (NAUTILUS_IS_VIEW_ITEM (self)); + + g_object_set (self, "is-loading", loading, NULL); +} + NautilusFile * nautilus_view_item_get_file (NautilusViewItem *self) { diff --git a/src/nautilus-view-item.h b/src/nautilus-view-item.h index 9bdaff158..268b20929 100644 --- a/src/nautilus-view-item.h +++ b/src/nautilus-view-item.h @@ -28,6 +28,8 @@ void nautilus_view_item_set_cut (NautilusViewItem *self, gboolean is_cut); void nautilus_view_item_set_drag_accept (NautilusViewItem *self, gboolean drag_accept); +void nautilus_view_item_set_loading (NautilusViewItem *self, + gboolean is_loading); NautilusFile * nautilus_view_item_get_file (NautilusViewItem *self); diff --git a/src/resources/ui/nautilus-name-cell.ui b/src/resources/ui/nautilus-name-cell.ui index 65db3d4a6..cb259d8fc 100644 --- a/src/resources/ui/nautilus-name-cell.ui +++ b/src/resources/ui/nautilus-name-cell.ui @@ -11,6 +11,14 @@ horizontal fill center + + + False + False + center + center + + vertical -- GitLab From 938242140f7a4de07269bcaa89d82c7ef3da3102 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ant=C3=B3nio=20Fernandes?= Date: Fri, 15 Apr 2022 12:15:58 +0100 Subject: [PATCH 6/9] list-view: Hide expander if subdirectory is empty The old list view used to add a dummy row with the "(empty)" label. That solution required a lot of code gymnastics to special case the dummy row everywhere, which was error-prone. So, for the new list view, I wanted to avoid that solution. For now, the simplest solution is to follow the example of other file managers and hide the expander if the folder is found to be empty after trying to expand it. --- src/nautilus-list-view.c | 7 +++++++ src/nautilus-view-model.c | 11 +++++++++++ 2 files changed, 18 insertions(+) diff --git a/src/nautilus-list-view.c b/src/nautilus-list-view.c index 6fe04a84f..dd474724b 100644 --- a/src/nautilus-list-view.c +++ b/src/nautilus-list-view.c @@ -969,6 +969,13 @@ on_subdirectory_done_loading (NautilusDirectory *directory, item = NAUTILUS_VIEW_ITEM (gtk_tree_list_row_get_item (row)); nautilus_view_item_set_loading (item, FALSE); + + if (!nautilus_directory_is_not_empty (directory)) + { + /* Collapse to trigger a reassessment of the tree in order to make the + * expander disappear. */ + gtk_tree_list_row_set_expanded (row, FALSE); + } } static void diff --git a/src/nautilus-view-model.c b/src/nautilus-view-model.c index 82692ccdd..f3ff42f1d 100644 --- a/src/nautilus-view-model.c +++ b/src/nautilus-view-model.c @@ -1,5 +1,6 @@ #include "nautilus-view-model.h" #include "nautilus-view-item.h" +#include "nautilus-directory.h" #include "nautilus-global-preferences.h" struct _NautilusViewModel @@ -258,6 +259,16 @@ create_model_func (GObject *item, { return NULL; } + else /* Check whether we already know it to be empty. */ + { + g_autoptr (NautilusDirectory) directory = NULL; + directory = nautilus_directory_get_for_file (file); + if (nautilus_directory_are_all_files_seen (directory) && + !nautilus_directory_is_not_empty (directory)) + { + return NULL; + } + } store = g_hash_table_lookup (self->directory_reverse_map, file); if (store == NULL) -- GitLab From 9dd68b6317814ef97b95b375da9e88bee50a4f8f Mon Sep 17 00:00:00 2001 From: Corey Berla Date: Sun, 17 Jul 2022 23:17:03 -0700 Subject: [PATCH 7/9] list-view: Check if self is NULL before getting listbase If the view is no longer available i.e. switched to the GridView, this crashes. Simply move the call to nautilus_list_base_get_model() after the NULL check. --- src/nautilus-list-view.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/nautilus-list-view.c b/src/nautilus-list-view.c index dd474724b..760c43197 100644 --- a/src/nautilus-list-view.c +++ b/src/nautilus-list-view.c @@ -925,11 +925,13 @@ unload_file_timeout (gpointer data) { g_autoptr (UnloadDelayData) unload_data = data; NautilusListView *self = unload_data->self; - NautilusViewModel *model = nautilus_list_base_get_model (NAUTILUS_LIST_BASE (self)); + NautilusViewModel *model; + if (unload_data->self == NULL) { return G_SOURCE_REMOVE; } + model = nautilus_list_base_get_model (NAUTILUS_LIST_BASE (self)); for (guint i = 0; i < g_list_model_get_n_items (G_LIST_MODEL (model)); i++) { -- GitLab From 73afd53838cd771063163da7c5e6136cfdb52e22 Mon Sep 17 00:00:00 2001 From: Corey Berla Date: Sun, 17 Jul 2022 23:18:28 -0700 Subject: [PATCH 8/9] list-view: Check for NULL row in unbind_name_cell() If we unexpanded a row, then the item would no longer be available. Return early --- src/nautilus-list-view.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/nautilus-list-view.c b/src/nautilus-list-view.c index 760c43197..0dbb5a63e 100644 --- a/src/nautilus-list-view.c +++ b/src/nautilus-list-view.c @@ -1142,6 +1142,11 @@ unbind_name_cell (GtkSignalListItemFactory *factory, NautilusViewItem *item; item = listitem_get_view_item (listitem); + if (item == NULL) + { + /* The row is gone */ + return; + } g_return_if_fail (NAUTILUS_IS_VIEW_ITEM (item)); nautilus_view_item_set_item_ui (item, NULL); -- GitLab From 3042977ff9b0414476928705c03cc908992525b1 Mon Sep 17 00:00:00 2001 From: Corey Berla Date: Sun, 17 Jul 2022 23:19:20 -0700 Subject: [PATCH 9/9] name-cell: Cleanup handler on_item_is_loading_changed() The handler warns that NAUTILUS_IS_NAME_CELL fails. Clean up the handler to match others in the signal group. --- src/nautilus-name-cell.c | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/src/nautilus-name-cell.c b/src/nautilus-name-cell.c index d67357319..03caedc91 100644 --- a/src/nautilus-name-cell.c +++ b/src/nautilus-name-cell.c @@ -274,20 +274,14 @@ on_loading_timeout (gpointer user_data) } static void -on_item_is_loading_changed (GObject *object, - GParamSpec *pspec, - gpointer user_data) +on_item_is_loading_changed (NautilusNameCell *self) { - NautilusNameCell *self = NAUTILUS_NAME_CELL (user_data); gboolean is_loading; + NautilusViewItem *item = nautilus_view_cell_get_item (NAUTILUS_VIEW_CELL (self)); - if (object != G_OBJECT (nautilus_view_cell_get_item (NAUTILUS_VIEW_CELL (self)))) - { - return; - } g_clear_handle_id (&self->loading_timeout_id, g_source_remove); - g_object_get (object, "is-loading", &is_loading, NULL); + g_object_get (item, "is-loading", &is_loading, NULL); if (is_loading) { self->loading_timeout_id = g_timeout_add_seconds (LOADING_TIMEOUT_SECONDS, -- GitLab