[nautilus/wip/antoniof/new-list-view-continuation: 24/27] list-view: Support expanding as a tree
- From: António Fernandes <antoniof src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [nautilus/wip/antoniof/new-list-view-continuation: 24/27] list-view: Support expanding as a tree
- Date: Fri, 10 Jun 2022 21:50:58 +0000 (UTC)
commit 98477757c2cfe491df73654feaf3deb01395e9d5
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 | 181 +++++++++++++++++++++++++++++++--
src/nautilus-name-cell.c | 8 ++
src/nautilus-name-cell.h | 1 +
src/resources/ui/nautilus-name-cell.ui | 137 +++++++++++++------------
6 files changed, 263 insertions(+), 76 deletions(-)
---
diff --git a/src/nautilus-files-view.c b/src/nautilus-files-view.c
index d4b22aa95..26263bbe0 100644
--- a/src/nautilus-files-view.c
+++ b/src/nautilus-files-view.c
@@ -4719,6 +4719,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 06ab85316..b19da3fd4 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;
@@ -90,17 +94,17 @@ get_icon_size_for_zoom_level (NautilusListZoomLevel zoom_level)
}
static guint
-real_get_icon_size (NautilusListBase *files_model_view)
+real_get_icon_size (NautilusListBase *list_base)
{
- NautilusListView *self = NAUTILUS_LIST_VIEW (files_model_view);
+ NautilusListView *self = NAUTILUS_LIST_VIEW (list_base);
return get_icon_size_for_zoom_level (self->zoom_level);
}
static GtkWidget *
-real_get_view_ui (NautilusListBase *files_model_view)
+real_get_view_ui (NautilusListBase *list_base)
{
- NautilusListView *self = NAUTILUS_LIST_VIEW (files_model_view);
+ NautilusListView *self = NAUTILUS_LIST_VIEW (list_base);
return GTK_WIDGET (self->view_ui);
}
@@ -227,10 +231,10 @@ apply_columns_settings (NautilusListView *self,
}
static void
-real_scroll_to_item (NautilusListBase *files_model_view,
+real_scroll_to_item (NautilusListBase *list_base,
guint position)
{
- NautilusListView *self = NAUTILUS_LIST_VIEW (files_model_view);
+ NautilusListView *self = NAUTILUS_LIST_VIEW (list_base);
GtkWidget *child;
child = gtk_widget_get_last_child (GTK_WIDGET (self->view_ui));
@@ -702,12 +706,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);
@@ -722,7 +733,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
@@ -823,6 +848,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,
@@ -897,6 +1030,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
@@ -904,11 +1048,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
@@ -916,12 +1069,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
@@ -1093,7 +1254,7 @@ nautilus_list_view_class_init (NautilusListViewClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
NautilusFilesViewClass *files_view_class = NAUTILUS_FILES_VIEW_CLASS (klass);
- NautilusListBaseClass *files_model_view_class = NAUTILUS_LIST_BASE_CLASS (klass);
+ NautilusListBaseClass *list_base_class = NAUTILUS_LIST_BASE_CLASS (klass);
object_class->dispose = nautilus_list_view_dispose;
object_class->finalize = nautilus_list_view_finalize;
@@ -1107,9 +1268,9 @@ nautilus_list_view_class_init (NautilusListViewClass *klass)
files_view_class->restore_standard_zoom_level = real_restore_standard_zoom_level;
files_view_class->is_zoom_level_default = real_is_zoom_level_default;
- files_model_view_class->get_icon_size = real_get_icon_size;
- files_model_view_class->get_view_ui = real_get_view_ui;
- files_model_view_class->scroll_to_item = real_scroll_to_item;
+ list_base_class->get_icon_size = real_get_icon_size;
+ list_base_class->get_view_ui = real_get_view_ui;
+ list_base_class->scroll_to_item = real_scroll_to_item;
}
NautilusListView *
diff --git a/src/nautilus-name-cell.c b/src/nautilus-name-cell.c
index 73479e44b..7a2866de5 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;
@@ -260,6 +261,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);
@@ -289,3 +291,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 d16ee295d..90dc1cb96 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 366e6e370..4d2b736f0 100644
--- a/src/resources/ui/nautilus-name-cell.ui
+++ b/src/resources/ui/nautilus-name-cell.ui
@@ -3,85 +3,90 @@
<requires lib="gtk" version="4.0"/>
<template class="NautilusNameCell" parent="NautilusViewCell">
<child>
- <object class="GtkBox">
- <property name="spacing">6</property>
- <property name="orientation">horizontal</property>
- <property name="halign">fill</property>
- <property name="valign">center</property>
- <child>
- <object class="GtkBox" id="fixed_height_box">
- <property name="orientation">vertical</property>
- <property name="halign">center</property>
- <property name="height-request">16</property>
- <property name="valign">center</property>
- <child>
- <object class="GtkPicture" id="icon">
- <property name="halign">center</property>
- <property name="valign">center</property>
- <property name="can-shrink">False</property>
- </object>
- </child>
- </object>
- </child>
- <child>
+ <object class="GtkTreeExpander" id="expander">
+ <property name="indent-for-icon">False</property>
+ <property name="child">
<object class="GtkBox">
- <property name="orientation">vertical</property>
+ <property name="spacing">6</property>
+ <property name="orientation">horizontal</property>
<property name="halign">fill</property>
- <property name="hexpand">True</property>
<property name="valign">center</property>
- <style>
- <class name="column-name-labels-box"/>
- </style>
- <child>
- <object class="GtkLabel" id="path">
- <property name="visible">False</property>
- <property name="ellipsize">start</property>
- <property name="justify">left</property>
- <property name="lines">2</property>
- <property name="wrap">True</property>
- <property name="wrap-mode">word-char</property>
- <property name="halign">fill</property>
- <property name="xalign">0.0</property>
- <attributes>
- <attribute name="insert-hyphens" value="false"></attribute>
- </attributes>
- <style>
- <class name="caption"/>
- <class name="dim-label"/>
- </style>
- </object>
- </child>
<child>
- <object class="GtkLabel" id="label">
- <property name="ellipsize">middle</property>
- <property name="lines">1</property>
- <property name="max-width-chars">-1</property>
- <property name="wrap">False</property>
- <property name="wrap-mode">word-char</property>
- <property name="halign">start</property>
- <attributes>
- <attribute name="insert-hyphens" value="false"></attribute>
- </attributes>
+ <object class="GtkBox" id="fixed_height_box">
+ <property name="orientation">vertical</property>
+ <property name="halign">center</property>
+ <property name="height-request">16</property>
+ <property name="valign">center</property>
+ <child>
+ <object class="GtkPicture" id="icon">
+ <property name="halign">center</property>
+ <property name="valign">center</property>
+ <property name="can-shrink">False</property>
+ </object>
+ </child>
</object>
</child>
<child>
- <object class="GtkLabel" id="snippet">
- <property name="visible">False</property>
- <property name="ellipsize">end</property>
- <property name="justify">left</property>
- <property name="lines">2</property>
- <property name="wrap">True</property>
- <property name="wrap-mode">word</property>
+ <object class="GtkBox">
+ <property name="orientation">vertical</property>
<property name="halign">fill</property>
- <property name="xalign">0.0</property>
+ <property name="hexpand">True</property>
+ <property name="valign">center</property>
<style>
- <class name="caption-heading"/>
- <class name="accent"/>
+ <class name="column-name-labels-box"/>
</style>
+ <child>
+ <object class="GtkLabel" id="path">
+ <property name="visible">False</property>
+ <property name="ellipsize">start</property>
+ <property name="justify">left</property>
+ <property name="lines">2</property>
+ <property name="wrap">True</property>
+ <property name="wrap-mode">word-char</property>
+ <property name="halign">fill</property>
+ <property name="xalign">0.0</property>
+ <attributes>
+ <attribute name="insert-hyphens" value="false"></attribute>
+ </attributes>
+ <style>
+ <class name="caption"/>
+ <class name="dim-label"/>
+ </style>
+ </object>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label">
+ <property name="ellipsize">middle</property>
+ <property name="lines">1</property>
+ <property name="max-width-chars">-1</property>
+ <property name="wrap">False</property>
+ <property name="wrap-mode">word-char</property>
+ <property name="halign">start</property>
+ <attributes>
+ <attribute name="insert-hyphens" value="false"></attribute>
+ </attributes>
+ </object>
+ </child>
+ <child>
+ <object class="GtkLabel" id="snippet">
+ <property name="visible">False</property>
+ <property name="ellipsize">end</property>
+ <property name="justify">left</property>
+ <property name="lines">2</property>
+ <property name="wrap">True</property>
+ <property name="wrap-mode">word</property>
+ <property name="halign">fill</property>
+ <property name="xalign">0.0</property>
+ <style>
+ <class name="caption-heading"/>
+ <class name="accent"/>
+ </style>
+ </object>
+ </child>
</object>
</child>
</object>
- </child>
+ </property>
</object>
</child>
</template>
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]