summaryrefslogtreecommitdiffstats
path: root/src/nautilus-list-view.c
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--src/nautilus-list-view.c1258
1 files changed, 1258 insertions, 0 deletions
diff --git a/src/nautilus-list-view.c b/src/nautilus-list-view.c
new file mode 100644
index 0000000..18f860b
--- /dev/null
+++ b/src/nautilus-list-view.c
@@ -0,0 +1,1258 @@
+/*
+ * Copyright (C) 2000 Eazel, Inc.
+ * Copyright (C) 2001, 2002 Anders Carlsson <andersca@gnu.org>
+ * Copyright (C) 2022 GNOME project contributors
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#include <glib/gi18n.h>
+
+/* Needed for NautilusColumn. */
+#include <nautilus-extension.h>
+
+#include "nautilus-list-base-private.h"
+#include "nautilus-list-view.h"
+
+#include "nautilus-column-chooser.h"
+#include "nautilus-column-utilities.h"
+#include "nautilus-directory.h"
+#include "nautilus-file.h"
+#include "nautilus-file-utilities.h"
+#include "nautilus-global-preferences.h"
+#include "nautilus-label-cell.h"
+#include "nautilus-metadata.h"
+#include "nautilus-name-cell.h"
+#include "nautilus-search-directory.h"
+#include "nautilus-star-cell.h"
+#include "nautilus-tag-manager.h"
+
+struct _NautilusListView
+{
+ NautilusListBase parent_instance;
+
+ GtkColumnView *view_ui;
+
+ GActionGroup *action_group;
+ gint zoom_level;
+
+ gboolean directories_first;
+
+ GQuark path_attribute_q;
+ GFile *file_path_base_location;
+
+ GtkColumnViewColumn *star_column;
+ GtkWidget *column_editor;
+ GHashTable *factory_to_column_map;
+
+ GHashTable *all_view_columns_hash;
+
+ /* Column sort hack state */
+ gboolean column_header_was_clicked;
+ GQuark clicked_column_attribute_q;
+};
+
+G_DEFINE_TYPE (NautilusListView, nautilus_list_view, NAUTILUS_TYPE_LIST_BASE)
+
+
+static void on_sorter_changed (GtkSorter *sorter,
+ GtkSorterChange change,
+ gpointer user_data);
+
+static const char *default_columns_for_recent[] =
+{
+ "name", "size", "recency", NULL
+};
+
+static const char *default_columns_for_trash[] =
+{
+ "name", "size", "trashed_on", NULL
+};
+
+static guint
+get_icon_size_for_zoom_level (NautilusListZoomLevel zoom_level)
+{
+ switch (zoom_level)
+ {
+ case NAUTILUS_LIST_ZOOM_LEVEL_SMALL:
+ {
+ return NAUTILUS_LIST_ICON_SIZE_SMALL;
+ }
+ break;
+
+ case NAUTILUS_LIST_ZOOM_LEVEL_MEDIUM:
+ {
+ return NAUTILUS_LIST_ICON_SIZE_MEDIUM;
+ }
+ break;
+
+ case NAUTILUS_LIST_ZOOM_LEVEL_LARGE:
+ {
+ return NAUTILUS_LIST_ICON_SIZE_LARGE;
+ }
+ break;
+ }
+ g_return_val_if_reached (NAUTILUS_LIST_ICON_SIZE_MEDIUM);
+}
+
+static guint
+real_get_icon_size (NautilusListBase *list_base_view)
+{
+ NautilusListView *self = NAUTILUS_LIST_VIEW (list_base_view);
+
+ return get_icon_size_for_zoom_level (self->zoom_level);
+}
+
+static GtkWidget *
+real_get_view_ui (NautilusListBase *list_base_view)
+{
+ NautilusListView *self = NAUTILUS_LIST_VIEW (list_base_view);
+
+ return GTK_WIDGET (self->view_ui);
+}
+
+static void
+apply_columns_settings (NautilusListView *self,
+ char **column_order,
+ char **visible_columns)
+{
+ g_autolist (NautilusColumn) all_columns = NULL;
+ NautilusFile *file;
+ NautilusDirectory *directory;
+ g_autoptr (GFile) location = NULL;
+ g_autoptr (GList) view_columns = NULL;
+ GListModel *old_view_columns;
+ g_autoptr (GHashTable) visible_columns_hash = NULL;
+ int column_i = 0;
+
+ file = nautilus_files_view_get_directory_as_file (NAUTILUS_FILES_VIEW (self));
+ directory = nautilus_files_view_get_model (NAUTILUS_FILES_VIEW (self));
+ if (NAUTILUS_IS_SEARCH_DIRECTORY (directory))
+ {
+ g_autoptr (NautilusQuery) query = NULL;
+
+ query = nautilus_search_directory_get_query (NAUTILUS_SEARCH_DIRECTORY (directory));
+ location = nautilus_query_get_location (query);
+ }
+ else
+ {
+ location = nautilus_file_get_location (file);
+ }
+
+ all_columns = nautilus_get_columns_for_file (file);
+ all_columns = nautilus_sort_columns (all_columns, column_order);
+
+ /* hash table to lookup if a given column should be visible */
+ visible_columns_hash = g_hash_table_new_full (g_str_hash,
+ g_str_equal,
+ (GDestroyNotify) g_free,
+ (GDestroyNotify) g_free);
+ /* always show name column */
+ g_hash_table_insert (visible_columns_hash, g_strdup ("name"), g_strdup ("name"));
+
+ /* always show star column if supported */
+ if (nautilus_tag_manager_can_star_contents (nautilus_tag_manager_get (), location) ||
+ nautilus_is_starred_directory (location))
+ {
+ g_hash_table_insert (visible_columns_hash, g_strdup ("starred"), g_strdup ("starred"));
+ }
+
+ if (visible_columns != NULL)
+ {
+ for (int i = 0; visible_columns[i] != NULL; ++i)
+ {
+ g_hash_table_insert (visible_columns_hash,
+ g_ascii_strdown (visible_columns[i], -1),
+ g_ascii_strdown (visible_columns[i], -1));
+ }
+ }
+
+ old_view_columns = gtk_column_view_get_columns (self->view_ui);
+ for (GList *l = all_columns; l != NULL; l = l->next)
+ {
+ g_autofree char *name = NULL;
+ g_autofree char *lowercase = NULL;
+
+ g_object_get (G_OBJECT (l->data), "name", &name, NULL);
+ lowercase = g_ascii_strdown (name, -1);
+
+ if (g_hash_table_lookup (visible_columns_hash, lowercase) != NULL)
+ {
+ GtkColumnViewColumn *view_column;
+
+ view_column = g_hash_table_lookup (self->all_view_columns_hash, name);
+ if (view_column != NULL)
+ {
+ view_columns = g_list_prepend (view_columns, view_column);
+ }
+ }
+ }
+
+ view_columns = g_list_reverse (view_columns);
+
+ /* hide columns that are not present in the configuration */
+ for (guint i = 0; i < g_list_model_get_n_items (old_view_columns); i++)
+ {
+ g_autoptr (GtkColumnViewColumn) view_column = NULL;
+
+ view_column = g_list_model_get_item (old_view_columns, i);
+ if (g_list_find (view_columns, view_column) == NULL)
+ {
+ gtk_column_view_remove_column (self->view_ui, view_column);
+ }
+ }
+
+ /* place columns in the correct order */
+ for (GList *l = view_columns; l != NULL; l = l->next, column_i++)
+ {
+ gtk_column_view_insert_column (self->view_ui, column_i, l->data);
+ }
+}
+
+static void
+real_scroll_to_item (NautilusListBase *list_base_view,
+ guint position)
+{
+ NautilusListView *self = NAUTILUS_LIST_VIEW (list_base_view);
+ GtkWidget *child;
+
+ child = gtk_widget_get_last_child (GTK_WIDGET (self->view_ui));
+
+ while (child != NULL && !GTK_IS_LIST_VIEW (child))
+ {
+ child = gtk_widget_get_prev_sibling (child);
+ }
+
+ if (child != NULL)
+ {
+ gtk_widget_activate_action (child, "list.scroll-to-item", "u", position);
+ }
+}
+
+typedef struct
+{
+ GQuark attribute;
+ NautilusListView *view;
+} SortData;
+
+static gint
+nautilus_list_view_sort (gconstpointer a,
+ gconstpointer b,
+ gpointer user_data)
+{
+ SortData *data = user_data;
+ NautilusListView *self = data->view;
+ GQuark attribute_q = data->attribute;
+ NautilusFile *file_a = nautilus_view_item_get_file (NAUTILUS_VIEW_ITEM ((gpointer) a));
+ NautilusFile *file_b = nautilus_view_item_get_file (NAUTILUS_VIEW_ITEM ((gpointer) b));
+
+ /* Hack: We don't know what column is being sorted on when the column
+ * headers are clicked. So let's just look at what attribute was most
+ * recently used for sorting.
+ * https://gitlab.gnome.org/GNOME/gtk/-/issues/4833 */
+ if (self->clicked_column_attribute_q == 0 && self->column_header_was_clicked)
+ {
+ self->clicked_column_attribute_q = attribute_q;
+ }
+
+ g_return_val_if_fail (file_a != NULL && file_b != NULL, GTK_ORDERING_EQUAL);
+
+ /* The reversed argument is FALSE because the columnview sorter handles that
+ * itself and if we don't want to reverse the reverse. The directories_first
+ * argument is also FALSE for the same reason: we don't want the columnview
+ * sorter to reverse it (it would display directories last!); instead we
+ * handle directories_first in a separate sorter. */
+ return nautilus_file_compare_for_sort_by_attribute_q (file_a, file_b,
+ attribute_q,
+ FALSE /* directories_first */,
+ FALSE /* reversed */);
+}
+
+static gint
+sort_directories_func (gconstpointer a,
+ gconstpointer b,
+ gpointer user_data)
+{
+ gboolean *directories_first = user_data;
+
+ if (*directories_first)
+ {
+ NautilusFile *file_a = nautilus_view_item_get_file (NAUTILUS_VIEW_ITEM ((gpointer) a));
+ NautilusFile *file_b = nautilus_view_item_get_file (NAUTILUS_VIEW_ITEM ((gpointer) b));
+ gboolean a_is_directory = nautilus_file_is_directory (file_a);
+ gboolean b_is_directory = nautilus_file_is_directory (file_b);
+
+ if (a_is_directory && !b_is_directory)
+ {
+ return GTK_ORDERING_SMALLER;
+ }
+ if (b_is_directory && !a_is_directory)
+ {
+ return GTK_ORDERING_LARGER;
+ }
+ }
+ return GTK_ORDERING_EQUAL;
+}
+
+static char **
+get_default_visible_columns (NautilusListView *self)
+{
+ NautilusFile *file;
+
+ file = nautilus_files_view_get_directory_as_file (NAUTILUS_FILES_VIEW (self));
+
+ if (nautilus_file_is_in_trash (file))
+ {
+ return g_strdupv ((gchar **) default_columns_for_trash);
+ }
+
+ if (nautilus_file_is_in_recent (file))
+ {
+ return g_strdupv ((gchar **) default_columns_for_recent);
+ }
+
+ return g_settings_get_strv (nautilus_list_view_preferences,
+ NAUTILUS_PREFERENCES_LIST_VIEW_DEFAULT_VISIBLE_COLUMNS);
+}
+
+static char **
+get_visible_columns (NautilusListView *self)
+{
+ NautilusFile *file;
+ g_autofree gchar **visible_columns = NULL;
+
+ file = nautilus_files_view_get_directory_as_file (NAUTILUS_FILES_VIEW (self));
+
+ visible_columns = nautilus_file_get_metadata_list (file,
+ NAUTILUS_METADATA_KEY_LIST_VIEW_VISIBLE_COLUMNS);
+ if (visible_columns == NULL || visible_columns[0] == NULL)
+ {
+ return get_default_visible_columns (self);
+ }
+
+ return g_steal_pointer (&visible_columns);
+}
+
+static char **
+get_default_column_order (NautilusListView *self)
+{
+ NautilusFile *file;
+
+ file = nautilus_files_view_get_directory_as_file (NAUTILUS_FILES_VIEW (self));
+
+ if (nautilus_file_is_in_trash (file))
+ {
+ return g_strdupv ((gchar **) default_columns_for_trash);
+ }
+
+ if (nautilus_file_is_in_recent (file))
+ {
+ return g_strdupv ((gchar **) default_columns_for_recent);
+ }
+
+ return g_settings_get_strv (nautilus_list_view_preferences,
+ NAUTILUS_PREFERENCES_LIST_VIEW_DEFAULT_COLUMN_ORDER);
+}
+
+static char **
+get_column_order (NautilusListView *self)
+{
+ NautilusFile *file;
+ g_autofree gchar **column_order = NULL;
+
+ file = nautilus_files_view_get_directory_as_file (NAUTILUS_FILES_VIEW (self));
+
+ column_order = nautilus_file_get_metadata_list (file,
+ NAUTILUS_METADATA_KEY_LIST_VIEW_COLUMN_ORDER);
+
+ if (column_order != NULL && column_order[0] != NULL)
+ {
+ return g_steal_pointer (&column_order);
+ }
+
+ return get_default_column_order (self);
+}
+static void
+update_columns_settings_from_metadata_and_preferences (NautilusListView *self)
+{
+ g_auto (GStrv) column_order = get_column_order (self);
+ g_auto (GStrv) visible_columns = get_visible_columns (self);
+
+ apply_columns_settings (self, column_order, visible_columns);
+}
+
+static GFile *
+get_base_location (NautilusListView *self)
+{
+ NautilusDirectory *directory;
+ GFile *base_location = NULL;
+
+ directory = nautilus_files_view_get_model (NAUTILUS_FILES_VIEW (self));
+ if (NAUTILUS_IS_SEARCH_DIRECTORY (directory))
+ {
+ g_autoptr (NautilusQuery) query = NULL;
+ g_autoptr (GFile) location = NULL;
+
+ query = nautilus_search_directory_get_query (NAUTILUS_SEARCH_DIRECTORY (directory));
+ location = nautilus_query_get_location (query);
+
+ if (!nautilus_is_recent_directory (location) &&
+ !nautilus_is_starred_directory (location) &&
+ !nautilus_is_trash_directory (location))
+ {
+ base_location = g_steal_pointer (&location);
+ }
+ }
+
+ return base_location;
+}
+
+static void
+on_column_view_item_activated (GtkGridView *grid_view,
+ guint position,
+ gpointer user_data)
+{
+ NautilusListView *self = NAUTILUS_LIST_VIEW (user_data);
+
+ nautilus_files_view_activate_selection (NAUTILUS_FILES_VIEW (self));
+}
+
+static GtkColumnView *
+create_view_ui (NautilusListView *self)
+{
+ NautilusViewModel *model;
+ GtkWidget *widget;
+
+ model = nautilus_list_base_get_model (NAUTILUS_LIST_BASE (self));
+ widget = gtk_column_view_new (GTK_SELECTION_MODEL (model));
+
+ gtk_widget_set_hexpand (widget, TRUE);
+
+
+ /* We don't use the built-in child activation feature for click because it
+ * doesn't fill all our needs nor does it match our expected behavior.
+ * Instead, we roll our own event handling and double/single click mode.
+ * However, GtkColumnView:single-click-activate has other effects besides
+ * activation, as it affects the selection behavior as well (e.g. selects on
+ * hover). Setting it to FALSE gives us the expected behavior. */
+ gtk_column_view_set_single_click_activate (GTK_COLUMN_VIEW (widget), FALSE);
+ gtk_column_view_set_enable_rubberband (GTK_COLUMN_VIEW (widget), TRUE);
+
+ /* While we don't want to use GTK's click activation, we'll let it handle
+ * the key activation part (with Enter).
+ */
+ g_signal_connect (widget, "activate", G_CALLBACK (on_column_view_item_activated), self);
+
+ return GTK_COLUMN_VIEW (widget);
+}
+
+static void
+column_chooser_changed_callback (NautilusColumnChooser *chooser,
+ NautilusListView *view)
+{
+ NautilusFile *file;
+ char **visible_columns;
+ char **column_order;
+
+ file = nautilus_files_view_get_directory_as_file (NAUTILUS_FILES_VIEW (view));
+
+ nautilus_column_chooser_get_settings (chooser,
+ &visible_columns,
+ &column_order);
+
+ nautilus_file_set_metadata_list (file,
+ NAUTILUS_METADATA_KEY_LIST_VIEW_VISIBLE_COLUMNS,
+ visible_columns);
+ nautilus_file_set_metadata_list (file,
+ NAUTILUS_METADATA_KEY_LIST_VIEW_COLUMN_ORDER,
+ column_order);
+
+ apply_columns_settings (view, column_order, visible_columns);
+
+ g_strfreev (visible_columns);
+ g_strfreev (column_order);
+}
+
+static void
+column_chooser_set_from_arrays (NautilusColumnChooser *chooser,
+ NautilusListView *view,
+ char **visible_columns,
+ char **column_order)
+{
+ g_signal_handlers_block_by_func
+ (chooser, G_CALLBACK (column_chooser_changed_callback), view);
+
+ nautilus_column_chooser_set_settings (chooser,
+ visible_columns,
+ column_order);
+
+ g_signal_handlers_unblock_by_func
+ (chooser, G_CALLBACK (column_chooser_changed_callback), view);
+}
+
+static void
+column_chooser_set_from_settings (NautilusColumnChooser *chooser,
+ NautilusListView *view)
+{
+ char **visible_columns;
+ char **column_order;
+
+ visible_columns = get_visible_columns (view);
+ column_order = get_column_order (view);
+
+ column_chooser_set_from_arrays (chooser, view,
+ visible_columns, column_order);
+
+ g_strfreev (visible_columns);
+ g_strfreev (column_order);
+}
+
+static void
+column_chooser_use_default_callback (NautilusColumnChooser *chooser,
+ NautilusListView *view)
+{
+ NautilusFile *file;
+ char **default_columns;
+ char **default_order;
+
+ file = nautilus_files_view_get_directory_as_file
+ (NAUTILUS_FILES_VIEW (view));
+
+ nautilus_file_set_metadata_list (file, NAUTILUS_METADATA_KEY_LIST_VIEW_COLUMN_ORDER, NULL);
+ nautilus_file_set_metadata_list (file, NAUTILUS_METADATA_KEY_LIST_VIEW_VISIBLE_COLUMNS, NULL);
+
+ /* set view values ourselves, as new metadata could not have been
+ * updated yet.
+ */
+ default_columns = get_default_visible_columns (view);
+ default_order = get_default_column_order (view);
+
+ apply_columns_settings (view, default_order, default_columns);
+ column_chooser_set_from_arrays (chooser, view,
+ default_columns, default_order);
+
+ g_strfreev (default_columns);
+ g_strfreev (default_order);
+}
+
+static GtkWidget *
+create_column_editor (NautilusListView *view)
+{
+ g_autoptr (GtkBuilder) builder = NULL;
+ GtkWidget *window;
+ AdwWindowTitle *window_title;
+ GtkWidget *box;
+ GtkWidget *column_chooser;
+ NautilusFile *file;
+ char *name;
+
+ builder = gtk_builder_new_from_resource ("/org/gnome/nautilus/ui/nautilus-list-view-column-editor.ui");
+
+ window = GTK_WIDGET (gtk_builder_get_object (builder, "window"));
+ gtk_window_set_transient_for (GTK_WINDOW (window),
+ GTK_WINDOW (gtk_widget_get_root (GTK_WIDGET (view))));
+
+ file = nautilus_files_view_get_directory_as_file (NAUTILUS_FILES_VIEW (view));
+ name = nautilus_file_get_display_name (file);
+ window_title = ADW_WINDOW_TITLE (gtk_builder_get_object (builder, "window_title"));
+ adw_window_title_set_subtitle (window_title, name);
+ g_free (name);
+
+ box = GTK_WIDGET (gtk_builder_get_object (builder, "box"));
+
+ column_chooser = nautilus_column_chooser_new (file);
+ gtk_widget_set_vexpand (column_chooser, TRUE);
+ gtk_box_append (GTK_BOX (box), column_chooser);
+
+ g_signal_connect (column_chooser, "changed",
+ G_CALLBACK (column_chooser_changed_callback),
+ view);
+ g_signal_connect (column_chooser, "use-default",
+ G_CALLBACK (column_chooser_use_default_callback),
+ view);
+
+ column_chooser_set_from_settings
+ (NAUTILUS_COLUMN_CHOOSER (column_chooser), view);
+
+ return window;
+}
+
+static void
+action_visible_columns (GSimpleAction *action,
+ GVariant *state,
+ gpointer user_data)
+{
+ NautilusListView *self = NAUTILUS_LIST_VIEW (user_data);
+
+ if (self->column_editor)
+ {
+ gtk_widget_show (self->column_editor);
+ }
+ else
+ {
+ self->column_editor = create_column_editor (self);
+ g_object_add_weak_pointer (G_OBJECT (self->column_editor),
+ (gpointer *) &self->column_editor);
+
+ gtk_widget_show (self->column_editor);
+ }
+}
+
+static void
+action_sort_order_changed (GSimpleAction *action,
+ GVariant *value,
+ gpointer user_data)
+{
+ const gchar *target_name;
+ gboolean reversed;
+ NautilusFileSortType sort_type;
+ NautilusListView *self;
+ GListModel *view_columns;
+ NautilusViewModel *model;
+ g_autoptr (GtkColumnViewColumn) sort_column = NULL;
+ GtkSorter *sorter;
+
+ /* This array makes the #NautilusFileSortType values correspond to the
+ * respective column attribute.
+ */
+ const char *attributes[] =
+ {
+ "name",
+ "size",
+ "type",
+ "date_modified",
+ "date_accessed",
+ "date_created",
+ "starred",
+ "trashed_on",
+ "search_relevance",
+ "recency",
+ NULL
+ };
+
+ /* Don't resort if the action is in the same state as before */
+ if (g_variant_equal (value, g_action_get_state (G_ACTION (action))))
+ {
+ return;
+ }
+
+ self = NAUTILUS_LIST_VIEW (user_data);
+ g_variant_get (value, "(&sb)", &target_name, &reversed);
+
+ if (g_strcmp0 (target_name, "unknown") == 0)
+ {
+ /* Sort order has been changed without using this action. */
+ g_simple_action_set_state (action, value);
+ return;
+ }
+
+ sort_type = get_sorts_type_from_metadata_text (target_name);
+
+ view_columns = gtk_column_view_get_columns (self->view_ui);
+ for (guint i = 0; i < g_list_model_get_n_items (view_columns); i++)
+ {
+ g_autoptr (GtkColumnViewColumn) view_column = NULL;
+ GtkListItemFactory *factory;
+ NautilusColumn *nautilus_column;
+ gchar *attribute;
+
+ view_column = g_list_model_get_item (view_columns, i);
+ factory = gtk_column_view_column_get_factory (view_column);
+ nautilus_column = g_hash_table_lookup (self->factory_to_column_map, factory);
+ if (nautilus_column == NULL)
+ {
+ continue;
+ }
+ g_object_get (nautilus_column, "attribute", &attribute, NULL);
+ if (g_strcmp0 (attributes[sort_type], attribute) == 0)
+ {
+ sort_column = g_steal_pointer (&view_column);
+ break;
+ }
+ }
+
+ model = nautilus_list_base_get_model (NAUTILUS_LIST_BASE (self));
+ sorter = nautilus_view_model_get_sorter (model);
+
+ /* Ask the column view to sort by column if it hasn't just done so already. */
+ if (!self->column_header_was_clicked)
+ {
+ g_signal_handlers_block_by_func (sorter, on_sorter_changed, self);
+ /* FIXME: Set NULL to stop drawing the arrow on previous sort column
+ * to workaround https://gitlab.gnome.org/GNOME/gtk/-/issues/4696 */
+ gtk_column_view_sort_by_column (self->view_ui, NULL, FALSE);
+ gtk_column_view_sort_by_column (self->view_ui, sort_column, reversed);
+ g_signal_handlers_unblock_by_func (sorter, on_sorter_changed, self);
+ }
+
+ self->column_header_was_clicked = FALSE;
+
+ set_directory_sort_metadata (nautilus_files_view_get_directory_as_file (NAUTILUS_FILES_VIEW (self)),
+ target_name,
+ reversed);
+
+ g_simple_action_set_state (action, value);
+}
+
+static void
+set_zoom_level (NautilusListView *self,
+ guint new_level)
+{
+ self->zoom_level = new_level;
+
+ nautilus_list_base_set_icon_size (NAUTILUS_LIST_BASE (self),
+ get_icon_size_for_zoom_level (new_level));
+
+ if (self->zoom_level == NAUTILUS_LIST_ZOOM_LEVEL_SMALL)
+ {
+ gtk_widget_add_css_class (GTK_WIDGET (self), "compact");
+ }
+ else
+ {
+ gtk_widget_remove_css_class (GTK_WIDGET (self), "compact");
+ }
+
+ nautilus_files_view_update_toolbar_menus (NAUTILUS_FILES_VIEW (self));
+}
+
+static void
+action_zoom_to_level (GSimpleAction *action,
+ GVariant *state,
+ gpointer user_data)
+{
+ NautilusListView *self = NAUTILUS_LIST_VIEW (user_data);
+ int zoom_level;
+
+ zoom_level = g_variant_get_int32 (state);
+ set_zoom_level (self, zoom_level);
+ g_simple_action_set_state (G_SIMPLE_ACTION (action), state);
+
+ if (g_settings_get_enum (nautilus_list_view_preferences,
+ NAUTILUS_PREFERENCES_LIST_VIEW_DEFAULT_ZOOM_LEVEL) != zoom_level)
+ {
+ g_settings_set_enum (nautilus_list_view_preferences,
+ NAUTILUS_PREFERENCES_LIST_VIEW_DEFAULT_ZOOM_LEVEL,
+ zoom_level);
+ }
+}
+
+const GActionEntry list_view_entries[] =
+{
+ { "visible-columns", action_visible_columns },
+ { "sort", NULL, "(sb)", "('invalid',false)", action_sort_order_changed },
+ { "zoom-to-level", NULL, NULL, "1", action_zoom_to_level }
+};
+
+static void
+real_begin_loading (NautilusFilesView *files_view)
+{
+ NautilusListView *self = NAUTILUS_LIST_VIEW (files_view);
+ NautilusFile *file;
+
+ /* We need to setup the columns before chaining up */
+ update_columns_settings_from_metadata_and_preferences (self);
+
+ NAUTILUS_FILES_VIEW_CLASS (nautilus_list_view_parent_class)->begin_loading (files_view);
+
+ self->clicked_column_attribute_q = 0;
+
+ self->path_attribute_q = 0;
+ g_clear_object (&self->file_path_base_location);
+ file = nautilus_files_view_get_directory_as_file (files_view);
+ if (nautilus_file_is_in_trash (file))
+ {
+ self->path_attribute_q = g_quark_from_string ("trash_orig_path");
+ self->file_path_base_location = get_base_location (self);
+ }
+ else if (nautilus_file_is_in_search (file) ||
+ nautilus_file_is_in_recent (file) ||
+ nautilus_file_is_in_starred (file))
+ {
+ self->path_attribute_q = g_quark_from_string ("where");
+ self->file_path_base_location = get_base_location (self);
+ }
+}
+
+static void
+real_bump_zoom_level (NautilusFilesView *files_view,
+ int zoom_increment)
+{
+ NautilusListView *self = NAUTILUS_LIST_VIEW (files_view);
+ NautilusListZoomLevel new_level;
+
+ new_level = self->zoom_level + zoom_increment;
+
+ if (new_level >= NAUTILUS_LIST_ZOOM_LEVEL_SMALL &&
+ new_level <= NAUTILUS_LIST_ZOOM_LEVEL_LARGE)
+ {
+ g_action_group_change_action_state (self->action_group,
+ "zoom-to-level",
+ g_variant_new_int32 (new_level));
+ }
+}
+
+static gint
+get_default_zoom_level (void)
+{
+ NautilusListZoomLevel default_zoom_level;
+
+ default_zoom_level = g_settings_get_enum (nautilus_list_view_preferences,
+ NAUTILUS_PREFERENCES_LIST_VIEW_DEFAULT_ZOOM_LEVEL);
+
+ /* Sanitize preference value. */
+ return CLAMP (default_zoom_level,
+ NAUTILUS_LIST_ZOOM_LEVEL_SMALL,
+ NAUTILUS_LIST_ZOOM_LEVEL_LARGE);
+}
+
+static void
+real_restore_standard_zoom_level (NautilusFilesView *files_view)
+{
+ NautilusListView *self;
+
+ self = NAUTILUS_LIST_VIEW (files_view);
+ g_action_group_change_action_state (self->action_group,
+ "zoom-to-level",
+ g_variant_new_int32 (NAUTILUS_LIST_ZOOM_LEVEL_MEDIUM));
+}
+
+static gboolean
+real_can_zoom_in (NautilusFilesView *files_view)
+{
+ NautilusListView *self = NAUTILUS_LIST_VIEW (files_view);
+
+ return self->zoom_level < NAUTILUS_LIST_ZOOM_LEVEL_LARGE;
+}
+
+static gboolean
+real_can_zoom_out (NautilusFilesView *files_view)
+{
+ NautilusListView *self = NAUTILUS_LIST_VIEW (files_view);
+
+ return self->zoom_level > NAUTILUS_LIST_ZOOM_LEVEL_SMALL;
+}
+
+static gboolean
+real_is_zoom_level_default (NautilusFilesView *files_view)
+{
+ NautilusListView *self;
+ guint icon_size;
+
+ self = NAUTILUS_LIST_VIEW (files_view);
+ icon_size = get_icon_size_for_zoom_level (self->zoom_level);
+
+ return icon_size == NAUTILUS_LIST_ICON_SIZE_MEDIUM;
+}
+
+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));
+
+ /* 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));
+}
+
+static void
+on_sorter_changed (GtkSorter *sorter,
+ GtkSorterChange change,
+ gpointer user_data)
+{
+ NautilusListView *self = NAUTILUS_LIST_VIEW (user_data);
+ NautilusViewModel *model = nautilus_list_base_get_model (NAUTILUS_LIST_BASE (self));
+
+ /* Set the conditions to capture the sort attribute the first time that
+ * nautilus_list_view_sort() is called. */
+ self->column_header_was_clicked = TRUE;
+ self->clicked_column_attribute_q = 0;
+
+ /* If there is only one file, enforce a comparison against a dummy item, to
+ * ensure nautilus_list_view_sort() gets called at least once. */
+ if (g_list_model_get_n_items (G_LIST_MODEL (model)) == 1)
+ {
+ NautilusViewItem *item = g_list_model_get_item (G_LIST_MODEL (model), 0);
+ g_autoptr (NautilusViewItem) dummy_item = NULL;
+
+ dummy_item = nautilus_view_item_new (nautilus_view_item_get_file (item),
+ NAUTILUS_LIST_ICON_SIZE_SMALL);
+
+ gtk_sorter_compare (sorter, item, dummy_item);
+ }
+}
+
+static void
+on_after_sorter_changed (GtkSorter *sorter,
+ GtkSorterChange change,
+ gpointer user_data)
+{
+ NautilusListView *self = NAUTILUS_LIST_VIEW (user_data);
+ GActionGroup *action_group = nautilus_files_view_get_action_group (NAUTILUS_FILES_VIEW (self));
+ g_autoptr (GVariant) state = NULL;
+ const gchar *new_sort_text;
+ gboolean reversed;
+ const gchar *current_sort_text;
+
+ if (!self->column_header_was_clicked || self->clicked_column_attribute_q == 0)
+ {
+ return;
+ }
+
+ state = g_action_group_get_action_state (action_group, "sort");
+ g_variant_get (state, "(&sb)", &current_sort_text, &reversed);
+
+ new_sort_text = g_quark_to_string (self->clicked_column_attribute_q);
+
+ if (g_strcmp0 (new_sort_text, current_sort_text) == 0)
+ {
+ reversed = !reversed;
+ }
+ else
+ {
+ reversed = FALSE;
+ }
+
+ g_action_group_change_action_state (action_group, "sort",
+ g_variant_new ("(sb)", new_sort_text, reversed));
+}
+
+static guint
+real_get_view_id (NautilusFilesView *files_view)
+{
+ return NAUTILUS_VIEW_LIST_ID;
+}
+
+static void
+on_item_click_released_workaround (GtkGestureClick *gesture,
+ gint n_press,
+ gdouble x,
+ gdouble y,
+ gpointer user_data)
+{
+ NautilusViewCell *cell = user_data;
+ NautilusListView *self = NAUTILUS_LIST_VIEW (nautilus_view_cell_get_view (cell));
+ GdkModifierType modifiers;
+
+ modifiers = gtk_event_controller_get_current_event_state (GTK_EVENT_CONTROLLER (gesture));
+ if (n_press == 1 &&
+ modifiers & (GDK_CONTROL_MASK | GDK_SHIFT_MASK))
+ {
+ NautilusViewModel *model;
+ g_autoptr (NautilusViewItem) item = NULL;
+ guint i;
+
+ model = nautilus_list_base_get_model (NAUTILUS_LIST_BASE (self));
+ item = nautilus_view_cell_get_item (cell);
+ g_return_if_fail (item != NULL);
+ i = nautilus_view_model_get_index (model, item);
+
+ gtk_widget_activate_action (GTK_WIDGET (cell),
+ "list.select-item",
+ "(ubb)",
+ i,
+ modifiers & GDK_CONTROL_MASK,
+ modifiers & GDK_SHIFT_MASK);
+ }
+}
+
+/* This whole event handler is a workaround to a GtkColumnView bug: it
+ * activates the list|select-item action twice, which may cause the
+ * second activation to reverse the effects of the first:
+ * https://gitlab.gnome.org/GNOME/gtk/-/issues/4819
+ *
+ * As a workaround, we are going to activate the action a 3rd time.
+ * The third time is the charm, as the saying goes. */
+static void
+setup_selection_click_workaround (NautilusViewCell *cell)
+{
+ GtkEventController *controller;
+
+ controller = GTK_EVENT_CONTROLLER (gtk_gesture_click_new ());
+ gtk_widget_add_controller (GTK_WIDGET (cell), controller);
+ gtk_event_controller_set_propagation_phase (controller, GTK_PHASE_BUBBLE);
+ gtk_gesture_single_set_button (GTK_GESTURE_SINGLE (controller), GDK_BUTTON_PRIMARY);
+ g_signal_connect (controller, "released", G_CALLBACK (on_item_click_released_workaround), cell);
+}
+
+static void
+setup_name_cell (GtkSignalListItemFactory *factory,
+ GtkListItem *listitem,
+ gpointer user_data)
+{
+ NautilusListView *self = NAUTILUS_LIST_VIEW (user_data);
+ NautilusViewCell *cell;
+
+ cell = nautilus_name_cell_new (NAUTILUS_LIST_BASE (self));
+ setup_cell_common (listitem, cell);
+
+ nautilus_name_cell_set_path (NAUTILUS_NAME_CELL (cell),
+ self->path_attribute_q,
+ self->file_path_base_location);
+ if (NAUTILUS_IS_SEARCH_DIRECTORY (nautilus_files_view_get_model (NAUTILUS_FILES_VIEW (self))))
+ {
+ nautilus_name_cell_show_snippet (NAUTILUS_NAME_CELL (cell));
+ }
+
+ setup_selection_click_workaround (cell);
+}
+
+static void
+bind_name_cell (GtkSignalListItemFactory *factory,
+ GtkListItem *listitem,
+ gpointer user_data)
+{
+ GtkWidget *cell;
+ NautilusViewItem *item;
+
+ cell = gtk_list_item_get_child (listitem);
+ item = NAUTILUS_VIEW_ITEM (gtk_list_item_get_item (listitem));
+
+ nautilus_view_item_set_item_ui (item, gtk_list_item_get_child (listitem));
+
+ if (nautilus_view_cell_once (NAUTILUS_VIEW_CELL (cell)))
+ {
+ GtkWidget *row_widget;
+
+ /* At the time of ::setup emission, the item ui has got no parent yet,
+ * that's why we need to complete the widget setup process here, on the
+ * first time ::bind is emitted. */
+ row_widget = gtk_widget_get_parent (gtk_widget_get_parent (cell));
+
+ gtk_accessible_update_relation (GTK_ACCESSIBLE (row_widget),
+ GTK_ACCESSIBLE_RELATION_LABELLED_BY, cell, NULL,
+ -1);
+ }
+}
+
+static void
+unbind_name_cell (GtkSignalListItemFactory *factory,
+ GtkListItem *listitem,
+ gpointer user_data)
+{
+ NautilusViewItem *item;
+
+ item = NAUTILUS_VIEW_ITEM (gtk_list_item_get_item (listitem));
+ g_return_if_fail (NAUTILUS_IS_VIEW_ITEM (item));
+
+ nautilus_view_item_set_item_ui (item, NULL);
+}
+
+static void
+setup_star_cell (GtkSignalListItemFactory *factory,
+ GtkListItem *listitem,
+ gpointer user_data)
+{
+ NautilusViewCell *cell;
+
+ cell = nautilus_star_cell_new (NAUTILUS_LIST_BASE (user_data));
+ setup_cell_common (listitem, cell);
+ setup_selection_click_workaround (cell);
+}
+
+static void
+setup_label_cell (GtkSignalListItemFactory *factory,
+ GtkListItem *listitem,
+ gpointer user_data)
+{
+ NautilusListView *self = user_data;
+ NautilusColumn *nautilus_column;
+ NautilusViewCell *cell;
+
+ nautilus_column = g_hash_table_lookup (self->factory_to_column_map, factory);
+
+ cell = nautilus_label_cell_new (NAUTILUS_LIST_BASE (user_data), nautilus_column);
+ setup_cell_common (listitem, cell);
+ setup_selection_click_workaround (cell);
+}
+
+static void
+setup_view_columns (NautilusListView *self)
+{
+ GtkListItemFactory *factory;
+ g_autolist (NautilusColumn) nautilus_columns = NULL;
+
+ nautilus_columns = nautilus_get_all_columns ();
+
+ self->factory_to_column_map = g_hash_table_new_full (g_direct_hash,
+ g_direct_equal,
+ NULL,
+ g_object_unref);
+ self->all_view_columns_hash = g_hash_table_new_full (g_str_hash,
+ g_str_equal,
+ (GDestroyNotify) g_free,
+ g_object_unref);
+
+ for (GList *l = nautilus_columns; l != NULL; l = l->next)
+ {
+ NautilusColumn *nautilus_column = NAUTILUS_COLUMN (l->data);
+ SortData *data;
+ g_autofree gchar *name = NULL;
+ g_autofree gchar *label = NULL;
+ GQuark attribute_q = 0;
+ GtkSortType sort_order;
+ g_autoptr (GtkCustomSorter) sorter = NULL;
+ g_autoptr (GtkColumnViewColumn) view_column = NULL;
+
+ g_object_get (nautilus_column,
+ "name", &name,
+ "label", &label,
+ "attribute_q", &attribute_q,
+ "default-sort-order", &sort_order,
+ NULL);
+
+ data = g_new0 (SortData, 1);
+ data->attribute = attribute_q;
+ data->view = self;
+
+ sorter = gtk_custom_sorter_new (nautilus_list_view_sort,
+ data,
+ g_free);
+
+ factory = gtk_signal_list_item_factory_new ();
+ view_column = gtk_column_view_column_new (NULL, factory);
+ gtk_column_view_column_set_expand (view_column, FALSE);
+ gtk_column_view_column_set_resizable (view_column, TRUE);
+ gtk_column_view_column_set_title (view_column, label);
+ gtk_column_view_column_set_sorter (view_column, GTK_SORTER (sorter));
+
+ if (!strcmp (name, "name"))
+ {
+ g_signal_connect (factory, "setup", G_CALLBACK (setup_name_cell), self);
+ g_signal_connect (factory, "bind", G_CALLBACK (bind_name_cell), self);
+ g_signal_connect (factory, "unbind", G_CALLBACK (unbind_name_cell), self);
+
+ gtk_column_view_column_set_expand (view_column, TRUE);
+ }
+ else if (g_strcmp0 (name, "starred") == 0)
+ {
+ g_signal_connect (factory, "setup", G_CALLBACK (setup_star_cell), self);
+
+ gtk_column_view_column_set_title (view_column, "");
+ gtk_column_view_column_set_resizable (view_column, FALSE);
+
+ self->star_column = view_column;
+ }
+ else
+ {
+ g_signal_connect (factory, "setup", G_CALLBACK (setup_label_cell), self);
+ }
+
+ g_hash_table_insert (self->factory_to_column_map,
+ factory,
+ g_object_ref (nautilus_column));
+ g_hash_table_insert (self->all_view_columns_hash,
+ g_steal_pointer (&name),
+ g_steal_pointer (&view_column));
+ }
+}
+
+static void
+nautilus_list_view_init (NautilusListView *self)
+{
+ NautilusViewModel *model;
+ GtkWidget *content_widget;
+ g_autoptr (GtkCustomSorter) directories_sorter = NULL;
+ g_autoptr (GtkMultiSorter) sorter = NULL;
+
+ gtk_widget_add_css_class (GTK_WIDGET (self), "nautilus-list-view");
+
+ g_signal_connect_object (nautilus_list_view_preferences,
+ "changed::" NAUTILUS_PREFERENCES_LIST_VIEW_DEFAULT_VISIBLE_COLUMNS,
+ G_CALLBACK (update_columns_settings_from_metadata_and_preferences),
+ self,
+ G_CONNECT_SWAPPED);
+ g_signal_connect_object (nautilus_list_view_preferences,
+ "changed::" NAUTILUS_PREFERENCES_LIST_VIEW_DEFAULT_COLUMN_ORDER,
+ G_CALLBACK (update_columns_settings_from_metadata_and_preferences),
+ self,
+ G_CONNECT_SWAPPED);
+
+ content_widget = nautilus_files_view_get_content_widget (NAUTILUS_FILES_VIEW (self));
+
+ self->view_ui = create_view_ui (self);
+ nautilus_list_base_setup_gestures (NAUTILUS_LIST_BASE (self));
+
+ setup_view_columns (self);
+
+ self->directories_first = nautilus_files_view_should_sort_directories_first (NAUTILUS_FILES_VIEW (self));
+ directories_sorter = gtk_custom_sorter_new (sort_directories_func, &self->directories_first, NULL);
+
+ sorter = gtk_multi_sorter_new ();
+ gtk_multi_sorter_append (sorter, g_object_ref (GTK_SORTER (directories_sorter)));
+ gtk_multi_sorter_append (sorter, g_object_ref (gtk_column_view_get_sorter (self->view_ui)));
+ g_signal_connect_object (sorter, "changed", G_CALLBACK (on_sorter_changed), self, 0);
+ g_signal_connect_object (sorter, "changed", G_CALLBACK (on_after_sorter_changed), self, G_CONNECT_AFTER);
+
+ model = nautilus_list_base_get_model (NAUTILUS_LIST_BASE (self));
+ nautilus_view_model_set_sorter (model, GTK_SORTER (sorter));
+
+ gtk_scrolled_window_set_child (GTK_SCROLLED_WINDOW (content_widget),
+ GTK_WIDGET (self->view_ui));
+
+ self->action_group = nautilus_files_view_get_action_group (NAUTILUS_FILES_VIEW (self));
+ g_action_map_add_action_entries (G_ACTION_MAP (self->action_group),
+ list_view_entries,
+ G_N_ELEMENTS (list_view_entries),
+ self);
+
+ self->zoom_level = get_default_zoom_level ();
+ g_action_group_change_action_state (nautilus_files_view_get_action_group (NAUTILUS_FILES_VIEW (self)),
+ "zoom-to-level", g_variant_new_int32 (self->zoom_level));
+}
+
+static void
+nautilus_list_view_dispose (GObject *object)
+{
+ NautilusListView *self = NAUTILUS_LIST_VIEW (object);
+ NautilusViewModel *model;
+
+ model = nautilus_list_base_get_model (NAUTILUS_LIST_BASE (self));
+ nautilus_view_model_set_sorter (model, NULL);
+
+ g_clear_object (&self->file_path_base_location);
+ g_clear_pointer (&self->factory_to_column_map, g_hash_table_destroy);
+ g_clear_pointer (&self->all_view_columns_hash, g_hash_table_destroy);
+
+ G_OBJECT_CLASS (nautilus_list_view_parent_class)->dispose (object);
+}
+
+static void
+nautilus_list_view_finalize (GObject *object)
+{
+ G_OBJECT_CLASS (nautilus_list_view_parent_class)->finalize (object);
+}
+
+static void
+nautilus_list_view_class_init (NautilusListViewClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ NautilusFilesViewClass *files_view_class = NAUTILUS_FILES_VIEW_CLASS (klass);
+ NautilusListBaseClass *list_base_view_class = NAUTILUS_LIST_BASE_CLASS (klass);
+
+ object_class->dispose = nautilus_list_view_dispose;
+ object_class->finalize = nautilus_list_view_finalize;
+
+ files_view_class->begin_loading = real_begin_loading;
+ files_view_class->bump_zoom_level = real_bump_zoom_level;
+ files_view_class->can_zoom_in = real_can_zoom_in;
+ files_view_class->can_zoom_out = real_can_zoom_out;
+ files_view_class->sort_directories_first_changed = real_sort_directories_first_changed;
+ files_view_class->get_view_id = real_get_view_id;
+ files_view_class->restore_standard_zoom_level = real_restore_standard_zoom_level;
+ files_view_class->is_zoom_level_default = real_is_zoom_level_default;
+
+ list_base_view_class->get_icon_size = real_get_icon_size;
+ list_base_view_class->get_view_ui = real_get_view_ui;
+ list_base_view_class->scroll_to_item = real_scroll_to_item;
+}
+
+NautilusListView *
+nautilus_list_view_new (NautilusWindowSlot *slot)
+{
+ return g_object_new (NAUTILUS_TYPE_LIST_VIEW,
+ "window-slot", slot,
+ NULL);
+}