summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAntónio Fernandes <antoniof@gnome.org>2022-04-06 00:40:00 +0100
committerAntónio Fernandes <antoniof@gnome.org>2023-01-10 01:11:33 +0000
commitdd4dbde1bcebe033b60859cdf1d351620788f0a5 (patch)
treed73c8b01f2615403476bce5023ef9979d051e6dc
parentd5ce96240ac0fc55ec64d3fcf756c84502f4e441 (diff)
downloadnautilus-dd4dbde1bcebe033b60859cdf1d351620788f0a5.tar.gz
list-view: Support expanding as a tree
Starred, Recent and Search still not supported due to bugs
-rw-r--r--src/nautilus-files-view.c10
-rw-r--r--src/nautilus-files-view.h2
-rw-r--r--src/nautilus-list-view.c162
-rw-r--r--src/nautilus-name-cell.c8
-rw-r--r--src/nautilus-name-cell.h1
-rw-r--r--src/resources/ui/nautilus-name-cell.ui155
-rw-r--r--src/resources/ui/nautilus-preferences-window.ui1
7 files changed, 262 insertions, 77 deletions
diff --git a/src/nautilus-files-view.c b/src/nautilus-files-view.c
index 047e71aa1..5fbee2bf7 100644
--- a/src/nautilus-files-view.c
+++ b/src/nautilus-files-view.c
@@ -4796,6 +4796,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 b0695f226..536c613f6 100644
--- a/src/nautilus-files-view.h
+++ b/src/nautilus-files-view.h
@@ -258,6 +258,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 4a7f331f7..3e5c9dbe3 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;
@@ -727,12 +731,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);
@@ -747,7 +758,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
@@ -825,7 +850,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));
@@ -838,6 +862,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,
@@ -910,6 +1042,17 @@ setup_name_cell (GtkSignalListItemFactory *factory,
}
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
@@ -918,6 +1061,7 @@ bind_name_cell (GtkSignalListItemFactory *factory,
gpointer user_data)
{
GtkWidget *cell;
+ NautilusListView *self = user_data;
NautilusViewItem *item;
cell = gtk_list_item_get_child (listitem);
@@ -938,6 +1082,14 @@ bind_name_cell (GtkSignalListItemFactory *factory,
GTK_ACCESSIBLE_RELATION_LABELLED_BY, cell, NULL,
-1);
}
+
+ 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
@@ -945,12 +1097,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 7c525869c..567838dc4 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;
@@ -305,6 +306,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);
@@ -336,3 +338,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 246aa3ab2..bc755918d 100644
--- a/src/resources/ui/nautilus-name-cell.ui
+++ b/src/resources/ui/nautilus-name-cell.ui
@@ -6,106 +6,111 @@
<relation name="labelled-by">label</relation>
</accessibility>
<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>
+ <object class="GtkTreeExpander" id="expander">
+ <property name="indent-for-icon">False</property>
+ <property name="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="GtkPicture" id="icon">
+ <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>
- <property name="can-shrink">False</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>
- </object>
- </child>
- <child>
- <object class="GtkBox">
- <property name="orientation">vertical</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="GtkBox">
- <property name="orientation">horizontal</property>
+ <property name="orientation">vertical</property>
<property name="halign">fill</property>
<property name="hexpand">True</property>
- <property name="spacing">6</property>
+ <property name="valign">center</property>
+ <style>
+ <class name="column-name-labels-box"/>
+ </style>
<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">
+ <property name="orientation">horizontal</property>
+ <property name="halign">fill</property>
+ <property name="hexpand">True</property>
+ <property name="spacing">6</property>
+ <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="GtkBox" id="emblems_box">
+ <property name="orientation">horizontal</property>
+ <property name="halign">start</property>
+ <property name="spacing">6</property>
+ </object>
+ </child>
</object>
</child>
<child>
- <object class="GtkBox" id="emblems_box">
- <property name="orientation">horizontal</property>
- <property name="halign">start</property>
- <property name="spacing">6</property>
+ <object class="GtkLabel" id="path">
+ <property name="visible">False</property>
+ <property name="ellipsize">start</property>
+ <property name="justify">left</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>
</object>
</child>
<child>
- <object class="GtkLabel" id="path">
+ <object class="GtkMenuButton" id="snippet_button">
+ <property name="tooltip-text" translatable="yes">Full text match</property>
<property name="visible">False</property>
- <property name="ellipsize">start</property>
- <property name="justify">left</property>
- <property name="halign">fill</property>
- <property name="xalign">0.0</property>
- <attributes>
- <attribute name="insert-hyphens" value="false"></attribute>
- </attributes>
+ <property name="icon-name">quotation-symbolic</property>
+ <property name="valign">center</property>
<style>
- <class name="caption"/>
- <class name="dim-label"/>
+ <class name="fts-snippet"/>
</style>
- </object>
- </child>
- </object>
- </child>
- <child>
- <object class="GtkMenuButton" id="snippet_button">
- <property name="tooltip-text" translatable="yes">Full text match</property>
- <property name="visible">False</property>
- <property name="icon-name">quotation-symbolic</property>
- <property name="valign">center</property>
- <style>
- <class name="fts-snippet"/>
- </style>
- <property name="popover">
- <object class="GtkPopover">
- <child>
- <object class="GtkLabel" id="snippet">
- <property name="ellipsize">none</property>
- <property name="justify">left</property>
- <property name="max-width-chars">65</property>
- <property name="lines">10</property>
- <property name="wrap">True</property>
- <property name="wrap-mode">word</property>
+ <property name="popover">
+ <object class="GtkPopover">
+ <child>
+ <object class="GtkLabel" id="snippet">
+ <property name="ellipsize">none</property>
+ <property name="justify">left</property>
+ <property name="max-width-chars">65</property>
+ <property name="lines">10</property>
+ <property name="wrap">True</property>
+ <property name="wrap-mode">word</property>
+ </object>
+ </child>
</object>
- </child>
+ </property>
</object>
- </property>
+ </child>
</object>
- </child>
+ </property>
</object>
</child>
</template>
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 @@
<property name="title" translatable="yes">_Expandable Folders in List View</property>
<property name="title_lines">0</property>
<property name="use_underline">True</property>
- <property name="visible">False</property>
<child>
<object class="GtkSwitch" id="use_tree_view_switch">
<property name="valign">center</property>