[nautilus/wip/antoniof/new-list-view-continuation: 2/4] list-view: Support expanding as a tree




commit 8297d06e932ccec3c216ff4cabfa0079fe73e379
Author: António Fernandes <antoniof gnome org>
Date:   Wed Apr 6 00:40:00 2022 +0100

    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  | 165 +++++++++++++++++++++++++++++++++++++++++++++-
 3 files changed, 174 insertions(+), 3 deletions(-)
---
diff --git a/src/nautilus-files-view.c b/src/nautilus-files-view.c
index c5295e2a4..acdfcb145 100644
--- a/src/nautilus-files-view.c
+++ b/src/nautilus-files-view.c
@@ -4724,6 +4724,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 eeb3c02a9..293fa5c07 100644
--- a/src/nautilus-list-view.c
+++ b/src/nautilus-list-view.c
@@ -22,6 +22,9 @@
 
 #define GUTTER_WIDTH 12
 
+/* We wait two seconds after row is collapsed to unload the subdirectory */
+#define COLLAPSE_TO_UNLOAD_DELAY 2
+
 struct _NautilusListView
 {
     NautilusFilesModelView parent_instance;
@@ -52,6 +55,10 @@ static void on_sorter_changed (GtkSorter      *sorter,
                                GtkSorterChange change,
                                gpointer        user_data);
 
+static void on_row_expanded_changed (GObject    *gobject,
+                                     GParamSpec *pspec,
+                                     gpointer    user_data);
+
 static const char *default_columns_for_recent[] =
 {
     "name", "size", "recency", NULL
@@ -969,17 +976,25 @@ bind_item_ui (GtkSignalListItemFactory *factory,
               gpointer                  user_data)
 {
     NautilusListView *self = NAUTILUS_LIST_VIEW (user_data);
+    GtkTreeExpander *expander;
     NautilusListViewItemUi *item_ui;
+    GtkTreeListRow *row;
     NautilusViewItemModel *item_model;
     GtkWidget *row_widget;
 
-    item_ui = NAUTILUS_LIST_VIEW_ITEM_UI (gtk_list_item_get_child (listitem));
+    expander = GTK_TREE_EXPANDER (gtk_list_item_get_child (listitem));
+    item_ui = NAUTILUS_LIST_VIEW_ITEM_UI (gtk_tree_expander_get_child (expander));
+    row = gtk_list_item_get_item (listitem);
     item_model = listitem_get_item (listitem);
     g_return_if_fail (item_model != NULL);
 
+    gtk_tree_expander_set_list_row (expander, row);
     nautilus_list_view_item_ui_set_model (item_ui, item_model);
     nautilus_view_item_model_set_item_ui (item_model, GTK_WIDGET (item_ui));
 
+    g_signal_connect_object (row, "notify::expanded",
+                             G_CALLBACK (on_row_expanded_changed), self, 0);
+
     row_widget = list_item_get_row_widget (listitem);
     g_object_set_data (G_OBJECT (row_widget), "nautilus-view-model-item", item_model);
 
@@ -1015,12 +1030,20 @@ unbind_item_ui (GtkSignalListItemFactory *factory,
                 GtkListItem              *listitem,
                 gpointer                  user_data)
 {
+    NautilusListView *self = NAUTILUS_LIST_VIEW (user_data);
+    GtkTreeExpander *expander;
     NautilusListViewItemUi *item_ui;
+    GtkTreeListRow *row;
     NautilusViewItemModel *item_model;
 
-    item_ui = NAUTILUS_LIST_VIEW_ITEM_UI (gtk_list_item_get_child (listitem));
+    expander = GTK_TREE_EXPANDER (gtk_list_item_get_child (listitem));
+    item_ui = NAUTILUS_LIST_VIEW_ITEM_UI (gtk_tree_expander_get_child (expander));
+    row = gtk_list_item_get_item (listitem);
     item_model = listitem_get_item (listitem);
 
+    g_signal_handlers_disconnect_by_func (row, on_row_expanded_changed, self);
+
+    gtk_tree_expander_set_list_row (expander, NULL);
     nautilus_list_view_item_ui_set_model (item_ui, NULL);
 
     /* item may be NULL when row has just been destroyed. */
@@ -1040,6 +1063,7 @@ setup_item_ui (GtkSignalListItemFactory *factory,
                gpointer                  user_data)
 {
     NautilusListView *self = NAUTILUS_LIST_VIEW (user_data);
+    GtkWidget *expander;
     NautilusListViewItemUi *item_ui;
 
     item_ui = nautilus_list_view_item_ui_new ();
@@ -1050,7 +1074,13 @@ setup_item_ui (GtkSignalListItemFactory *factory,
     {
         nautilus_list_view_item_ui_show_snippet (item_ui);
     }
-    gtk_list_item_set_child (listitem, GTK_WIDGET (item_ui));
+
+    expander = gtk_tree_expander_new ();
+    gtk_tree_expander_set_indent_for_icon (GTK_TREE_EXPANDER (expander),
+                                           self->expand_as_a_tree);
+    gtk_tree_expander_set_child (GTK_TREE_EXPANDER (expander),
+                                 GTK_WIDGET (item_ui));
+    gtk_list_item_set_child (listitem, expander);
 
     /* The built-in activation doesn't fit all our needs and might get in the
      * way, e.g. by claiming click gesture events, so keep it disabled. */
@@ -1342,12 +1372,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);
@@ -1362,7 +1399,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_files_model_view_get_model (NAUTILUS_FILES_MODEL_VIEW (self));
+    nautilus_view_model_expand_as_a_tree (model, self->expand_as_a_tree);
 }
 
 static void
@@ -1501,6 +1552,114 @@ finalize (GObject *object)
     G_OBJECT_CLASS (nautilus_list_view_parent_class)->finalize (object);
 }
 
+typedef struct
+{
+    NautilusListView *self;
+    NautilusViewItemModel *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,
+                       NautilusViewItemModel *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_files_model_view_get_model (NAUTILUS_FILES_MODEL_VIEW (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 (NautilusViewItemModel) 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);
+    NautilusViewItemModel *item_model;
+    g_autoptr (NautilusDirectory) directory = NULL;
+    gboolean expanded;
+    gboolean has_subdirectory;
+
+    item_model = NAUTILUS_VIEW_ITEM_MODEL (gtk_tree_list_row_get_item (row));
+    if (item_model == NULL)
+    {
+        /* Row has been destroyed. */
+        return;
+    }
+
+    directory = nautilus_directory_get_for_file (nautilus_view_item_model_get_file (item_model));
+    expanded = gtk_tree_list_row_get_expanded (row);
+    has_subdirectory = nautilus_files_view_has_subdirectory (NAUTILUS_FILES_VIEW (self), directory);
+
+    if (expanded && !has_subdirectory)
+    {
+        nautilus_files_view_add_subdirectory (NAUTILUS_FILES_VIEW (self), directory);
+    }
+    else if (!expanded && has_subdirectory)
+    {
+        g_timeout_add_seconds (COLLAPSE_TO_UNLOAD_DELAY,
+                               unload_file_timeout,
+                               unload_delay_data_new (self, item_model, directory));
+    }
+}
+
 static void
 nautilus_list_view_class_init (NautilusListViewClass *klass)
 {


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]