[nautilus/wip/antoniof/new-list-view: 6/6] --WIP-- New list view --WIP--




commit 5536e7f1f771bf5adf9a2b88b9b06af79ca0e911
Author: António Fernandes <antoniof gnome org>
Date:   Thu Feb 10 02:03:53 2022 +0000

    --WIP-- New list view --WIP--

 src/meson.build                                  |    4 +
 src/nautilus-column-view-item-ui.c               |  331 ++++
 src/nautilus-column-view-item-ui.h               |   22 +
 src/nautilus-column-view.c                       | 1989 ++++++++++++++++++++++
 src/nautilus-column-view.h                       |   18 +
 src/nautilus-files-view.c                        |    3 +-
 src/nautilus-view-item-model.c                   |    2 +-
 src/resources/css/Adwaita.css                    |   39 +
 src/resources/nautilus.gresource.xml             |    1 +
 src/resources/ui/nautilus-column-view-item-ui.ui |   99 ++
 10 files changed, 2506 insertions(+), 2 deletions(-)
---
diff --git a/src/meson.build b/src/meson.build
index 4c171bd6d..a1fd3a2f2 100644
--- a/src/meson.build
+++ b/src/meson.build
@@ -69,6 +69,10 @@ libnautilus_sources = [
   'nautilus-application.h',
   'nautilus-bookmark-list.c',
   'nautilus-bookmark-list.h',
+  'nautilus-column-view.c',
+  'nautilus-column-view.h',
+  'nautilus-column-view-item-ui.c',
+  'nautilus-column-view-item-ui.h',
   'nautilus-dbus-manager.c',
   'nautilus-dbus-manager.h',
   'nautilus-error-reporting.c',
diff --git a/src/nautilus-column-view-item-ui.c b/src/nautilus-column-view-item-ui.c
new file mode 100644
index 000000000..6c53e9c48
--- /dev/null
+++ b/src/nautilus-column-view-item-ui.c
@@ -0,0 +1,331 @@
+#include "nautilus-column-view-item-ui.h"
+#include "nautilus-view-item-model.h"
+#include "nautilus-file.h"
+#include "nautilus-thumbnails.h"
+
+struct _NautilusColumnViewItemUi
+{
+    GtkBox parent_instance;
+
+    NautilusViewItemModel *model;
+    GQuark *caption_attributes;
+
+    GtkWidget *fixed_height_box;
+    GtkWidget *icon;
+    GtkWidget *label;
+    GtkWidget *snippet;
+    GtkWidget *path;
+    GtkWidget *third_caption;
+
+    gboolean called_once;
+};
+
+G_DEFINE_TYPE (NautilusColumnViewItemUi, nautilus_column_view_item_ui, GTK_TYPE_BOX)
+
+enum
+{
+    PROP_0,
+    PROP_MODEL,
+    N_PROPS
+};
+
+static void
+update_icon (NautilusColumnViewItemUi *self)
+{
+    NautilusFileIconFlags flags;
+    g_autoptr (GdkPaintable) icon_paintable = NULL;
+    GtkStyleContext *style_context;
+    NautilusFile *file;
+    guint icon_size;
+    int icon_height;
+    int extra_margin;
+    g_autofree gchar *thumbnail_path = NULL;
+
+    file = nautilus_view_item_model_get_file (self->model);
+    icon_size = nautilus_view_item_model_get_icon_size (self->model);
+    flags = NAUTILUS_FILE_ICON_FLAGS_USE_THUMBNAILS |
+            NAUTILUS_FILE_ICON_FLAGS_FORCE_THUMBNAIL_SIZE |
+            NAUTILUS_FILE_ICON_FLAGS_USE_EMBLEMS |
+            NAUTILUS_FILE_ICON_FLAGS_USE_ONE_EMBLEM;
+
+    icon_paintable = nautilus_file_get_icon_paintable (file, icon_size, 1, flags);
+    gtk_picture_set_paintable (GTK_PICTURE (self->icon), icon_paintable);
+
+    /* Set the same width for all icons regardless of aspect ratio.
+     * Don't set the width here because it would get GtkPicture w4h confused.
+     */
+    gtk_widget_set_size_request (self->fixed_height_box, icon_size, -1);
+
+    /* Give all items the same minimum width. This cannot be done by setting the
+     * width request directly, as above, because it would get mess up with
+     * height for width calculations.
+     *
+     * Instead we must add margins on both sides of the icon which, summed up
+     * with the icon's actual width, equal the desired item width. */
+    icon_height = gdk_paintable_get_intrinsic_height (icon_paintable);
+    extra_margin = (icon_size - icon_height) / 2;
+    gtk_widget_set_margin_top (self->fixed_height_box, extra_margin);
+    gtk_widget_set_margin_bottom (self->fixed_height_box, extra_margin);
+
+    style_context = gtk_widget_get_style_context (self->icon);
+    thumbnail_path = nautilus_file_get_thumbnail_path (file);
+    if (icon_size >= NAUTILUS_THUMBNAIL_MINIMUM_ICON_SIZE &&
+        thumbnail_path != NULL &&
+        nautilus_file_should_show_thumbnail (file))
+    {
+        gtk_style_context_add_class (style_context, "thumbnail");
+    }
+    else
+    {
+        gtk_style_context_remove_class (style_context, "thumbnail");
+    }
+}
+
+static void
+update_captions (NautilusColumnViewItemUi *self)
+{
+    NautilusFile *file;
+    g_autofree gchar *path = NULL;
+    const gchar *snippet;
+
+    file = nautilus_view_item_model_get_file (self->model);
+    if (self->caption_attributes[0] != 0)
+    {
+        g_autofree gchar *string = NULL;
+        path = nautilus_file_get_string_attribute_q (file, self->caption_attributes[0]);
+        gtk_label_set_text (GTK_LABEL (self->path), path);
+        gtk_widget_show (self->path);
+    }
+    else
+    {
+        gtk_widget_hide (self->path);
+    }
+
+    snippet = nautilus_file_get_search_fts_snippet (file);
+    if (snippet != NULL)
+    {
+        g_autoptr (GRegex) regex = NULL;
+        g_autofree gchar *flattened_text = NULL;
+
+        regex = g_regex_new ("\\R+", 0, G_REGEX_MATCH_NEWLINE_ANY, NULL);
+        flattened_text = g_regex_replace (regex,
+                                          snippet,
+                                          -1,
+                                          0,
+                                          " ",
+                                          G_REGEX_MATCH_NEWLINE_ANY,
+                                          NULL);
+        gtk_label_set_text (GTK_LABEL (self->snippet), flattened_text);
+        gtk_widget_show (self->snippet);
+    }
+    else
+    {
+        gtk_widget_hide (self->snippet);
+    }
+}
+
+static void
+on_file_changed (NautilusColumnViewItemUi *self)
+{
+    NautilusFile *file;
+
+    file = nautilus_view_item_model_get_file (self->model);
+
+    update_icon (self);
+
+    gtk_label_set_text (GTK_LABEL (self->label),
+                        nautilus_file_get_display_name (file));
+    update_captions (self);
+}
+
+static void
+on_view_item_size_changed (GObject    *object,
+                           GParamSpec *pspec,
+                           gpointer    user_data)
+{
+    NautilusColumnViewItemUi *self = NAUTILUS_COLUMN_VIEW_ITEM_UI (user_data);
+
+    update_icon (self);
+    update_captions (self);
+}
+
+static void
+set_model (NautilusColumnViewItemUi *self,
+           NautilusViewItemModel  *model);
+
+static void
+finalize (GObject *object)
+{
+    NautilusColumnViewItemUi *self = (NautilusColumnViewItemUi *) object;
+
+    set_model (self, NULL);
+    G_OBJECT_CLASS (nautilus_column_view_item_ui_parent_class)->finalize (object);
+}
+
+static void
+get_property (GObject    *object,
+              guint       prop_id,
+              GValue     *value,
+              GParamSpec *pspec)
+{
+    NautilusColumnViewItemUi *self = NAUTILUS_COLUMN_VIEW_ITEM_UI (object);
+
+    switch (prop_id)
+    {
+        case PROP_MODEL:
+        {
+            g_value_set_object (value, self->model);
+        }
+        break;
+
+        default:
+            G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+set_model (NautilusColumnViewItemUi *self,
+           NautilusViewItemModel  *model)
+{
+    NautilusFile *file;
+
+    if (self->model == model)
+    {
+        return;
+    }
+
+    if (self->model != NULL)
+    {
+        g_signal_handlers_disconnect_by_data (self->model, self);
+        g_clear_object (&self->model);
+    }
+
+    if (model == NULL)
+    {
+        return;
+    }
+
+    self->model = g_object_ref (model);
+
+    file = nautilus_view_item_model_get_file (self->model);
+
+    update_icon (self);
+    gtk_label_set_text (GTK_LABEL (self->label),
+                        nautilus_file_get_display_name (file));
+    update_captions (self);
+
+    g_signal_connect (self->model, "notify::icon-size",
+                      (GCallback) on_view_item_size_changed, self);
+    g_signal_connect_swapped (self->model, "file-changed",
+                              (GCallback) on_file_changed, self);
+}
+
+static void
+set_property (GObject      *object,
+              guint         prop_id,
+              const GValue *value,
+              GParamSpec   *pspec)
+{
+    NautilusColumnViewItemUi *self = NAUTILUS_COLUMN_VIEW_ITEM_UI (object);
+
+    switch (prop_id)
+    {
+        case PROP_MODEL:
+        {
+            set_model (self, g_value_get_object (value));
+        }
+        break;
+
+        default:
+            G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+nautilus_column_view_item_ui_class_init (NautilusColumnViewItemUiClass *klass)
+{
+    GObjectClass *object_class = G_OBJECT_CLASS (klass);
+    GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+
+    object_class->finalize = finalize;
+    object_class->get_property = get_property;
+    object_class->set_property = set_property;
+
+    g_object_class_install_property (object_class,
+                                     PROP_MODEL,
+                                     g_param_spec_object ("model",
+                                                          "Item model",
+                                                          "The item model that this UI reprensents",
+                                                          NAUTILUS_TYPE_VIEW_ITEM_MODEL,
+                                                          G_PARAM_READWRITE));
+
+    gtk_widget_class_set_template_from_resource (widget_class, 
"/org/gnome/nautilus/ui/nautilus-column-view-item-ui.ui");
+
+    gtk_widget_class_bind_template_child (widget_class, NautilusColumnViewItemUi, fixed_height_box);
+    gtk_widget_class_bind_template_child (widget_class, NautilusColumnViewItemUi, icon);
+    gtk_widget_class_bind_template_child (widget_class, NautilusColumnViewItemUi, label);
+    gtk_widget_class_bind_template_child (widget_class, NautilusColumnViewItemUi, snippet);
+    gtk_widget_class_bind_template_child (widget_class, NautilusColumnViewItemUi, path);
+    gtk_widget_class_bind_template_child (widget_class, NautilusColumnViewItemUi, third_caption);
+}
+
+static void
+nautilus_column_view_item_ui_init (NautilusColumnViewItemUi *self)
+{
+    gtk_widget_init_template (GTK_WIDGET (self));
+
+#if PANGO_VERSION_CHECK (1, 44, 4)
+    {
+        PangoAttrList *attr_list;
+
+        /* GTK4 TODO: This attribute is set in the UI file but GTK 3 ignores it.
+         * Remove this block after the switch to GTK 4. */
+        attr_list = pango_attr_list_new ();
+        pango_attr_list_insert (attr_list, pango_attr_insert_hyphens_new (FALSE));
+        gtk_label_set_attributes (GTK_LABEL (self->label), attr_list);
+        pango_attr_list_unref (attr_list);
+    }
+#endif
+}
+
+NautilusColumnViewItemUi *
+nautilus_column_view_item_ui_new (void)
+{
+    return g_object_new (NAUTILUS_TYPE_COLUMN_VIEW_ITEM_UI, NULL);
+}
+
+void
+nautilus_column_view_item_ui_set_model (NautilusColumnViewItemUi *self,
+                                      NautilusViewItemModel  *model)
+{
+    g_object_set (self, "model", model, NULL);
+}
+
+NautilusViewItemModel *
+nautilus_column_view_item_ui_get_model (NautilusColumnViewItemUi *self)
+{
+    NautilusViewItemModel *model = NULL;
+
+    g_object_get (self, "model", &model, NULL);
+
+    return model;
+}
+
+void
+nautilus_column_view_item_ui_set_caption_attributes (NautilusColumnViewItemUi *self,
+                                                     GQuark                   *attrs)
+{
+    self->caption_attributes = attrs;
+}
+
+gboolean
+nautilus_column_view_item_ui_once (NautilusColumnViewItemUi *self)
+{
+    if (self->called_once)
+    {
+        return FALSE;
+    }
+
+    self->called_once = TRUE;
+    return TRUE;
+}
diff --git a/src/nautilus-column-view-item-ui.h b/src/nautilus-column-view-item-ui.h
new file mode 100644
index 000000000..621a7f677
--- /dev/null
+++ b/src/nautilus-column-view-item-ui.h
@@ -0,0 +1,22 @@
+#pragma once
+
+#include <glib.h>
+#include <gtk/gtk.h>
+
+#include "nautilus-view-item-model.h"
+
+G_BEGIN_DECLS
+
+#define NAUTILUS_TYPE_COLUMN_VIEW_ITEM_UI (nautilus_column_view_item_ui_get_type())
+
+G_DECLARE_FINAL_TYPE (NautilusColumnViewItemUi, nautilus_column_view_item_ui, NAUTILUS, COLUMN_VIEW_ITEM_UI, 
GtkBox)
+
+NautilusColumnViewItemUi * nautilus_column_view_item_ui_new (void);
+void nautilus_column_view_item_ui_set_model (NautilusColumnViewItemUi *self,
+                                             NautilusViewItemModel  *model);
+NautilusViewItemModel *nautilus_column_view_item_ui_get_model (NautilusColumnViewItemUi *self);
+void nautilus_column_view_item_ui_set_caption_attributes (NautilusColumnViewItemUi *self,
+                                                          GQuark                   *attr);
+gboolean nautilus_column_view_item_ui_once (NautilusColumnViewItemUi *self);
+
+G_END_DECLS
diff --git a/src/nautilus-column-view.c b/src/nautilus-column-view.c
new file mode 100644
index 000000000..dbce49228
--- /dev/null
+++ b/src/nautilus-column-view.c
@@ -0,0 +1,1989 @@
+#include <glib/gi18n.h>
+
+/* Needed for NautilusColumn. */
+#include <nautilus-extension.h>
+
+#include "nautilus-column-utilities.h"
+#include "nautilus-column-view.h"
+#include "nautilus-column-view-item-ui.h"
+#include "nautilus-view-item-model.h"
+#include "nautilus-view-model.h"
+#include "nautilus-files-view.h"
+#include "nautilus-file.h"
+#include "nautilus-metadata.h"
+#include "nautilus-window-slot.h"
+#include "nautilus-directory.h"
+#include "nautilus-global-preferences.h"
+#include "nautilus-tag-manager.h"
+#include "nautilus-thumbnails.h"
+
+/* The star icon itself is 16px, plus 12px padding on each side.
+ */
+#define STAR_COLUMN_WIDTH 40
+
+#define GUTTER_WIDTH 12
+
+struct _NautilusColumnView
+{
+    NautilusFilesView parent_instance;
+
+    GtkColumnView *view_ui;
+    NautilusViewModel *model;
+
+    GIcon *view_icon;
+    GActionGroup *action_group;
+    gint zoom_level;
+    GQuark caption_attributes[3];
+
+    gboolean single_click_mode;
+    gboolean activate_on_release;
+
+    guint scroll_to_file_handle_id;
+    guint prioritize_thumbnailing_handle_id;
+    GtkAdjustment *vadjustment;
+
+    NautilusFileSortType sort_type;
+    gboolean directories_first;
+    gboolean reversed;
+};
+
+G_DEFINE_TYPE (NautilusColumnView, nautilus_column_view, NAUTILUS_TYPE_FILES_VIEW)
+
+typedef struct
+{
+    const NautilusFileSortType sort_type;
+    const gchar *metadata_name;
+    const gchar *action_target_name;
+    gboolean reversed;
+} SortConstants;
+
+static const SortConstants sorts_constants[] =
+{
+    {
+        NAUTILUS_FILE_SORT_BY_DISPLAY_NAME,
+        "name",
+        "name",
+        FALSE,
+    },
+    {
+        NAUTILUS_FILE_SORT_BY_DISPLAY_NAME,
+        "name",
+        "name-desc",
+        TRUE,
+    },
+    {
+        NAUTILUS_FILE_SORT_BY_SIZE,
+        "size",
+        "size",
+        TRUE,
+    },
+    {
+        NAUTILUS_FILE_SORT_BY_TYPE,
+        "type",
+        "type",
+        FALSE,
+    },
+    {
+        NAUTILUS_FILE_SORT_BY_MTIME,
+        "modification date",
+        "modification-date",
+        FALSE,
+    },
+    {
+        NAUTILUS_FILE_SORT_BY_MTIME,
+        "modification date",
+        "modification-date-desc",
+        TRUE,
+    },
+    {
+        NAUTILUS_FILE_SORT_BY_ATIME,
+        "access date",
+        "access-date",
+        FALSE,
+    },
+    {
+        NAUTILUS_FILE_SORT_BY_ATIME,
+        "access date",
+        "access-date-desc",
+        TRUE,
+    },
+    {
+        NAUTILUS_FILE_SORT_BY_BTIME,
+        "creation date",
+        "creation-date",
+        FALSE,
+    },
+    {
+        NAUTILUS_FILE_SORT_BY_BTIME,
+        "creation date",
+        "creation-date-desc",
+        TRUE,
+    },
+    {
+        NAUTILUS_FILE_SORT_BY_TRASHED_TIME,
+        "trashed",
+        "trash-time",
+        TRUE,
+    },
+    {
+        NAUTILUS_FILE_SORT_BY_SEARCH_RELEVANCE,
+        NULL,
+        "search-relevance",
+        TRUE,
+    }
+};
+
+static guint get_icon_size_for_zoom_level (NautilusListZoomLevel zoom_level);
+
+static const SortConstants *
+get_sorts_constants_from_action_target_name (const gchar *action_target_name)
+{
+    int i;
+
+    for (i = 0; i < G_N_ELEMENTS (sorts_constants); i++)
+    {
+        if (g_strcmp0 (sorts_constants[i].action_target_name, action_target_name) == 0)
+        {
+            return &sorts_constants[i];
+        }
+    }
+
+    return &sorts_constants[0];
+}
+
+static const SortConstants *
+get_sorts_constants_from_sort_type (NautilusFileSortType sort_type,
+                                    gboolean             reversed)
+{
+    guint i;
+
+    for (i = 0; i < G_N_ELEMENTS (sorts_constants); i++)
+    {
+        if (sort_type == sorts_constants[i].sort_type
+            && reversed == sorts_constants[i].reversed)
+        {
+            return &sorts_constants[i];
+        }
+    }
+
+    return &sorts_constants[0];
+}
+
+static const SortConstants *
+get_sorts_constants_from_metadata_text (const char *metadata_name,
+                                        gboolean    reversed)
+{
+    guint i;
+
+    for (i = 0; i < G_N_ELEMENTS (sorts_constants); i++)
+    {
+        if (g_strcmp0 (sorts_constants[i].metadata_name, metadata_name) == 0
+            && reversed == sorts_constants[i].reversed)
+        {
+            return &sorts_constants[i];
+        }
+    }
+
+    return &sorts_constants[0];
+}
+
+static const SortConstants *
+get_default_sort_order (NautilusFile *file)
+{
+    NautilusFileSortType sort_type;
+    gboolean reversed;
+
+    sort_type = nautilus_file_get_default_sort_type (file, &reversed);
+
+    return get_sorts_constants_from_sort_type (sort_type, reversed);
+}
+
+static const SortConstants *
+get_directory_sort_by (NautilusFile *file)
+{
+    const SortConstants *default_sort;
+    g_autofree char *sort_by = NULL;
+    gboolean reversed;
+
+    default_sort = get_default_sort_order (file);
+    g_return_val_if_fail (default_sort != NULL, NULL);
+
+    sort_by = nautilus_file_get_metadata (file,
+                                          NAUTILUS_METADATA_KEY_ICON_VIEW_SORT_BY,
+                                          default_sort->metadata_name);
+
+    reversed = nautilus_file_get_boolean_metadata (file,
+                                                   NAUTILUS_METADATA_KEY_ICON_VIEW_SORT_REVERSED,
+                                                   default_sort->reversed);
+
+    return get_sorts_constants_from_metadata_text (sort_by, reversed);
+}
+
+static void
+set_directory_sort_metadata (NautilusFile        *file,
+                             const SortConstants *sort)
+{
+    const SortConstants *default_sort;
+
+    default_sort = get_default_sort_order (file);
+
+    nautilus_file_set_metadata (file,
+                                NAUTILUS_METADATA_KEY_ICON_VIEW_SORT_BY,
+                                default_sort->metadata_name,
+                                sort->metadata_name);
+    nautilus_file_set_boolean_metadata (file,
+                                        NAUTILUS_METADATA_KEY_ICON_VIEW_SORT_REVERSED,
+                                        default_sort->reversed,
+                                        sort->reversed);
+}
+
+static void
+update_sort_order_from_metadata_and_preferences (NautilusColumnView *self)
+{
+    const SortConstants *default_directory_sort;
+    GActionGroup *view_action_group;
+
+    default_directory_sort = get_directory_sort_by (nautilus_files_view_get_directory_as_file 
(NAUTILUS_FILES_VIEW (self)));
+    view_action_group = nautilus_files_view_get_action_group (NAUTILUS_FILES_VIEW (self));
+    g_action_group_change_action_state (view_action_group,
+                                        "sort",
+                                        g_variant_new_string (get_sorts_constants_from_sort_type 
(default_directory_sort->sort_type, default_directory_sort->reversed)->action_target_name));
+}
+
+G_DEFINE_AUTOPTR_CLEANUP_FUNC (GtkColumnViewColumn, g_object_unref)
+static void
+apply_columns_settings (NautilusColumnView  *self,
+                        char               **column_order,
+                        char               **visible_columns)
+{
+    GList *all_columns;
+    NautilusFile *file;
+    g_autoptr (GFile) location = NULL;
+    GList *view_columns;
+    GListModel *old_view_columns;
+    GHashTable *visible_columns_hash;
+    GHashTable *old_view_columns_hash;
+    GList *l;
+    int i;
+    g_autoptr (GtkColumnViewColumn) dummy_column = NULL;
+
+    file = nautilus_files_view_get_directory_as_file (NAUTILUS_FILES_VIEW (self));
+    location = nautilus_file_get_location (file);
+
+    /* prepare ordered list of view columns using column_order and visible_columns */
+    view_columns = NULL;
+
+    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"));
+    if (nautilus_tag_manager_can_star_contents (nautilus_tag_manager_get (), location))
+    {
+        g_hash_table_insert (visible_columns_hash, g_strdup ("starred"), g_strdup ("starred"));
+    }
+
+    if (visible_columns != NULL)
+    {
+        for (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_hash = g_hash_table_new_full (g_str_hash,
+                                                  g_str_equal,
+                                                  (GDestroyNotify) g_free,
+                                                  NULL);
+    old_view_columns = gtk_column_view_get_columns (self->view_ui);
+    for (i = 0; i < g_list_model_get_n_items (old_view_columns); i++)
+    {
+        g_autoptr (GtkColumnViewColumn) view_column = NULL;
+        GtkListItemFactory *factory;
+        NautilusColumn *nautilus_column;
+        gchar *name;
+
+        view_column = g_list_model_get_item (old_view_columns, i);
+        factory = gtk_column_view_column_get_factory (view_column);
+        nautilus_column = g_object_get_data (G_OBJECT (factory), "nautilus-column");
+        if (nautilus_column == NULL)
+        {
+            continue;
+        }
+        g_object_get (nautilus_column, "name", &name, NULL);
+        g_hash_table_insert (old_view_columns_hash, name, view_column);
+    }
+
+    for (l = all_columns; l != NULL; l = l->next)
+    {
+        char *name;
+        char *lowercase;
+
+        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)
+        {
+            g_autoptr (GtkColumnViewColumn) view_column = NULL;
+
+            view_column = g_hash_table_lookup (old_view_columns_hash, name);
+            if (view_column != NULL)
+            {
+                view_columns = g_list_prepend (view_columns, view_column);
+            }
+        }
+
+        g_free (name);
+        g_free (lowercase);
+    }
+
+    g_hash_table_destroy (visible_columns_hash);
+    g_hash_table_destroy (old_view_columns_hash);
+    nautilus_column_list_free (all_columns);
+
+    view_columns = g_list_reverse (view_columns);
+
+    /* hide columns that are not present in the configuration */
+    for (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_column_set_visible (view_column, FALSE);
+        }
+        else
+        {
+            gtk_column_view_column_set_visible (view_column, TRUE);
+        }
+    }
+
+    /* place columns in the correct order */
+    for (l = view_columns, i = 0; l != NULL; l = l->next, i++)
+    {
+        gtk_column_view_insert_column (self->view_ui, i, l->data);
+    }
+    g_list_free (view_columns);
+
+    /* Dummy gutter compensation column. */
+    dummy_column = g_list_model_get_item (old_view_columns,
+                                         g_list_model_get_n_items (old_view_columns) - 1);
+    gtk_column_view_column_set_visible (dummy_column, TRUE);
+}
+
+static void
+activate_scroll_to_item (NautilusColumnView *self,
+                         guint               position)
+{
+    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
+{
+    NautilusColumnView *self;
+    GQuark attribute_q;
+} NautilusColumnViewSortData;
+
+static gint
+nautilus_column_view_sort (gconstpointer a,
+                           gconstpointer b,
+                           gpointer      user_data)
+{
+    NautilusColumnViewSortData *sort_data = user_data;
+    NautilusColumnView *self = sort_data->self;
+    NautilusFile *file_a;
+    NautilusFile *file_b;
+
+    file_a = nautilus_view_item_model_get_file (NAUTILUS_VIEW_ITEM_MODEL ((gpointer) a));
+    file_b = nautilus_view_item_model_get_file (NAUTILUS_VIEW_ITEM_MODEL ((gpointer) b));
+
+    return nautilus_file_compare_for_sort_by_attribute_q (file_a, file_b,
+                                                          sort_data->attribute_q,
+                                                          self->directories_first,
+                                                          self->reversed);
+}
+
+static void
+real_begin_loading (NautilusFilesView *files_view)
+{
+    NautilusColumnView *self = NAUTILUS_COLUMN_VIEW (files_view);
+    NautilusFile *file;
+
+    /* TODO: This calls sort once, and update_context_menus calls update_actions which calls
+     * the action again
+     */
+    update_sort_order_from_metadata_and_preferences (self);
+
+    /*TODO move this to the files view class begin_loading and hook up? */
+
+    /* We could have changed to the trash directory or to searching, and then
+     * we need to update the menus */
+    nautilus_files_view_update_context_menus (files_view);
+    nautilus_files_view_update_toolbar_menus (files_view);
+
+    memset (&self->caption_attributes, 0, sizeof (self->caption_attributes));
+    file = nautilus_files_view_get_directory_as_file (files_view);
+    if (nautilus_file_is_in_trash (file))
+    {
+        self->caption_attributes[0] = g_quark_from_string ("trash_orig_path");
+    }
+    else if (nautilus_file_is_in_search (file) ||
+             nautilus_file_is_in_recent (file) ||
+             nautilus_file_is_in_starred (file))
+    {
+        self->caption_attributes[0] = g_quark_from_string ("where");
+    }
+}
+
+static void
+real_clear (NautilusFilesView *files_view)
+{
+    NautilusColumnView *self = NAUTILUS_COLUMN_VIEW (files_view);
+
+    nautilus_view_model_remove_all_items (self->model);
+}
+
+static void
+real_file_changed (NautilusFilesView *files_view,
+                   NautilusFile      *file,
+                   NautilusDirectory *directory)
+{
+    NautilusColumnView *self;
+    NautilusViewItemModel *item_model;
+
+    self = NAUTILUS_COLUMN_VIEW (files_view);
+    item_model = nautilus_view_model_get_item_from_file (self->model, file);
+    nautilus_view_item_model_file_changed (item_model);
+}
+
+static GList *
+real_get_selection (NautilusFilesView *files_view)
+{
+    NautilusColumnView *self;
+    g_autoptr (GtkSelectionFilterModel) selection = NULL;
+    guint n_selected;
+    GList *selected_files = NULL;
+
+    self = NAUTILUS_COLUMN_VIEW (files_view);
+    selection = gtk_selection_filter_model_new (GTK_SELECTION_MODEL (self->model));
+    n_selected = g_list_model_get_n_items (G_LIST_MODEL (selection));
+    for (guint i = 0; i < n_selected; i++)
+    {
+        g_autoptr (NautilusViewItemModel) item_model = NULL;
+
+        item_model = g_list_model_get_item (G_LIST_MODEL (selection), i);
+        selected_files = g_list_prepend (selected_files,
+                                         g_object_ref (nautilus_view_item_model_get_file (item_model)));
+    }
+
+    return selected_files;
+}
+
+static gboolean
+real_is_empty (NautilusFilesView *files_view)
+{
+    NautilusColumnView *self = NAUTILUS_COLUMN_VIEW (files_view);
+
+    return g_list_model_get_n_items (G_LIST_MODEL (self->model)) == 0;
+}
+
+static void
+real_end_file_changes (NautilusFilesView *files_view)
+{
+}
+
+static void
+real_remove_file (NautilusFilesView *files_view,
+                  NautilusFile      *file,
+                  NautilusDirectory *directory)
+{
+    NautilusColumnView *self = NAUTILUS_COLUMN_VIEW (files_view);
+    NautilusViewItemModel *item_model;
+
+    item_model = nautilus_view_model_get_item_from_file (self->model, file);
+    if (item_model != NULL)
+    {
+        nautilus_view_model_remove_item (self->model, item_model);
+    }
+}
+
+static GQueue *
+convert_glist_to_queue (GList *list)
+{
+    GList *l;
+    GQueue *queue;
+
+    queue = g_queue_new ();
+    for (l = list; l != NULL; l = l->next)
+    {
+        g_queue_push_tail (queue, l->data);
+    }
+
+    return queue;
+}
+
+static GQueue *
+convert_files_to_item_models (NautilusColumnView *self,
+                              GQueue                     *files)
+{
+    GList *l;
+    GQueue *models;
+
+    models = g_queue_new ();
+    for (l = g_queue_peek_head_link (files); l != NULL; l = l->next)
+    {
+        NautilusViewItemModel *item_model;
+
+        item_model = nautilus_view_item_model_new (NAUTILUS_FILE (l->data),
+                                                   get_icon_size_for_zoom_level (self->zoom_level));
+        g_queue_push_tail (models, item_model);
+    }
+
+    return models;
+}
+
+static void
+real_set_selection (NautilusFilesView *files_view,
+                    GList             *selection)
+{
+    NautilusColumnView *self = NAUTILUS_COLUMN_VIEW (files_view);
+    g_autoptr (GQueue) selection_files = NULL;
+    g_autoptr (GQueue) selection_item_models = NULL;
+    g_autoptr (GtkBitset) update_set = NULL;
+    g_autoptr (GtkBitset) selection_set = NULL;
+
+    update_set = gtk_selection_model_get_selection (GTK_SELECTION_MODEL (self->model));
+    selection_set = gtk_bitset_new_empty ();
+
+    /* Convert file list into set of model indices */
+    selection_files = convert_glist_to_queue (selection);
+    selection_item_models = nautilus_view_model_get_items_from_files (self->model, selection_files);
+    for (GList *l = g_queue_peek_head_link (selection_item_models); l != NULL ; l = l->next)
+    {
+        gtk_bitset_add (selection_set,
+                        nautilus_view_model_get_index (self->model, l->data));
+    }
+
+    gtk_bitset_union (update_set, selection_set);
+    gtk_selection_model_set_selection (GTK_SELECTION_MODEL (self->model),
+                                       selection_set,
+                                       update_set);
+}
+
+static void
+real_select_all (NautilusFilesView *files_view)
+{
+    NautilusColumnView *self = NAUTILUS_COLUMN_VIEW (files_view);
+    gtk_selection_model_select_all (GTK_SELECTION_MODEL (self->model));
+}
+
+static void
+real_invert_selection (NautilusFilesView *files_view)
+{
+    NautilusColumnView *self = NAUTILUS_COLUMN_VIEW (files_view);
+    GtkSelectionModel *selection_model = GTK_SELECTION_MODEL (self->model);
+    g_autoptr (GtkBitset) selected = NULL;
+    g_autoptr (GtkBitset) all = NULL;
+    g_autoptr (GtkBitset) new_selected = NULL;
+
+    selected = gtk_selection_model_get_selection (selection_model);
+
+    /* We are going to flip the selection state of every item in the model. */
+    all = gtk_bitset_new_range (0, g_list_model_get_n_items (G_LIST_MODEL (self->model)));
+
+    /* The new selection is all items minus the ones currently selected. */
+    new_selected = gtk_bitset_copy (all);
+    gtk_bitset_subtract (new_selected, selected);
+
+    gtk_selection_model_set_selection (selection_model, new_selected, all);
+}
+
+static guint
+get_first_selected_item (NautilusColumnView *self)
+{
+    g_autolist (NautilusFile) selection = NULL;
+    NautilusFile *file;
+    NautilusViewItemModel *item_model;
+
+    selection = nautilus_view_get_selection (NAUTILUS_VIEW (self));
+    if (selection == NULL)
+    {
+        return G_MAXUINT;
+    }
+
+    file = NAUTILUS_FILE (selection->data);
+    item_model = nautilus_view_model_get_item_from_file (self->model, file);
+
+    return nautilus_view_model_get_index (self->model, item_model);
+}
+
+static void
+real_reveal_selection (NautilusFilesView *files_view)
+{
+    NautilusColumnView *self = NAUTILUS_COLUMN_VIEW (files_view);
+
+    activate_scroll_to_item (self, get_first_selected_item (self));
+}
+
+static gboolean
+showing_recent_directory (NautilusFilesView *view)
+{
+    NautilusFile *file;
+
+    file = nautilus_files_view_get_directory_as_file (view);
+    if (file != NULL)
+    {
+        return nautilus_file_is_in_recent (file);
+    }
+    return FALSE;
+}
+
+static gboolean
+showing_search_directory (NautilusFilesView *view)
+{
+    NautilusFile *file;
+
+    file = nautilus_files_view_get_directory_as_file (view);
+    if (file != NULL)
+    {
+        return nautilus_file_is_in_search (file);
+    }
+    return FALSE;
+}
+
+static void
+real_update_actions_state (NautilusFilesView *files_view)
+{
+    GAction *action;
+    GActionGroup *view_action_group;
+
+    NAUTILUS_FILES_VIEW_CLASS (nautilus_column_view_parent_class)->update_actions_state (files_view);
+
+    view_action_group = nautilus_files_view_get_action_group (files_view);
+    action = g_action_map_lookup_action (G_ACTION_MAP (view_action_group), "sort");
+    g_simple_action_set_enabled (G_SIMPLE_ACTION (action),
+                                 !showing_recent_directory (files_view) &&
+                                 !showing_search_directory (files_view));
+}
+
+static void
+real_bump_zoom_level (NautilusFilesView *files_view,
+                      int                zoom_increment)
+{
+    NautilusColumnView *self = NAUTILUS_COLUMN_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_LARGER)
+    {
+        g_action_group_change_action_state (self->action_group,
+                                            "zoom-to-level",
+                                            g_variant_new_int32 (new_level));
+    }
+}
+
+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_STANDARD:
+        {
+            return NAUTILUS_LIST_ICON_SIZE_STANDARD;
+        }
+        break;
+
+        case NAUTILUS_LIST_ZOOM_LEVEL_LARGE:
+        {
+            return NAUTILUS_LIST_ICON_SIZE_LARGE;
+        }
+        break;
+
+        case NAUTILUS_LIST_ZOOM_LEVEL_LARGER:
+        {
+            return NAUTILUS_LIST_ICON_SIZE_LARGER;
+        }
+        break;
+    }
+    g_return_val_if_reached (NAUTILUS_LIST_ICON_SIZE_STANDARD);
+}
+
+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);
+
+    return default_zoom_level;
+}
+
+static void
+set_icon_size (NautilusColumnView *self,
+               gint                        icon_size)
+{
+    guint n_items;
+
+    n_items = g_list_model_get_n_items (G_LIST_MODEL (self->model));
+    for (guint i = 0; i < n_items; i++)
+    {
+        g_autoptr (NautilusViewItemModel) current_item_model = NULL;
+
+        current_item_model = g_list_model_get_item (G_LIST_MODEL (self->model), i);
+        nautilus_view_item_model_set_icon_size (current_item_model,
+                                                get_icon_size_for_zoom_level (self->zoom_level));
+    }
+}
+
+static void
+set_zoom_level (NautilusColumnView *self,
+                guint               new_level)
+{
+    self->zoom_level = new_level;
+
+    set_icon_size (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
+real_restore_standard_zoom_level (NautilusFilesView *files_view)
+{
+    NautilusColumnView *self;
+
+    self = NAUTILUS_COLUMN_VIEW (files_view);
+    g_action_group_change_action_state (self->action_group,
+                                        "zoom-to-level",
+                                        g_variant_new_int32 (NAUTILUS_LIST_ZOOM_LEVEL_STANDARD));
+}
+
+static gfloat
+real_get_zoom_level_percentage (NautilusFilesView *files_view)
+{
+    NautilusColumnView *self = NAUTILUS_COLUMN_VIEW (files_view);
+
+    return (gfloat) get_icon_size_for_zoom_level (self->zoom_level) /
+           NAUTILUS_LIST_ICON_SIZE_STANDARD;
+}
+
+static gboolean
+real_is_zoom_level_default (NautilusFilesView *files_view)
+{
+    NautilusColumnView *self;
+    guint icon_size;
+
+    self = NAUTILUS_COLUMN_VIEW (files_view);
+    icon_size = get_icon_size_for_zoom_level (self->zoom_level);
+
+    return icon_size == NAUTILUS_LIST_ICON_SIZE_STANDARD;
+}
+
+static gboolean
+real_can_zoom_in (NautilusFilesView *files_view)
+{
+    NautilusColumnView *self = NAUTILUS_COLUMN_VIEW (files_view);
+
+    return self->zoom_level < NAUTILUS_LIST_ZOOM_LEVEL_LARGER;
+}
+
+static gboolean
+real_can_zoom_out (NautilusFilesView *files_view)
+{
+    NautilusColumnView *self = NAUTILUS_COLUMN_VIEW (files_view);
+
+    return self->zoom_level > NAUTILUS_LIST_ZOOM_LEVEL_SMALL;
+}
+
+static GdkRectangle *
+get_rectangle_for_item_ui (NautilusColumnView *self,
+                           GtkWidget                  *item_ui)
+{
+    GdkRectangle *rectangle;
+    GtkWidget *content_widget;
+    gdouble view_x;
+    gdouble view_y;
+
+    rectangle = g_new0 (GdkRectangle, 1);
+    gtk_widget_get_allocation (item_ui, rectangle);
+
+    content_widget = nautilus_files_view_get_content_widget (NAUTILUS_FILES_VIEW (self));
+    gtk_widget_translate_coordinates (GTK_WIDGET (self->view_ui), content_widget,
+                                      rectangle->x, rectangle->y,
+                                      &view_x, &view_y);
+    rectangle->x = view_x;
+    rectangle->y = view_y;
+
+    return rectangle;
+}
+
+static GdkRectangle *
+real_compute_rename_popover_pointing_to (NautilusFilesView *files_view)
+{
+    NautilusColumnView *self = NAUTILUS_COLUMN_VIEW (files_view);
+    g_autoptr (NautilusViewItemModel) item = NULL;
+    GtkWidget *item_ui;
+
+    /* We only allow one item to be renamed with a popover */
+    item = g_list_model_get_item (G_LIST_MODEL (self->model),
+                                  get_first_selected_item (self));
+    item_ui = nautilus_view_item_model_get_item_ui (item);
+    g_return_val_if_fail (item_ui != NULL, NULL);
+
+    return get_rectangle_for_item_ui (self, item_ui);
+}
+
+static GdkRectangle *
+real_reveal_for_selection_context_menu (NautilusFilesView *files_view)
+{
+    NautilusColumnView *self = NAUTILUS_COLUMN_VIEW (files_view);
+    g_autoptr (GtkSelectionFilterModel) selection = NULL;
+    guint n_selected;
+    GtkWidget *focus_child;
+    guint i;
+    GtkWidget *item_ui;
+
+    selection = gtk_selection_filter_model_new (GTK_SELECTION_MODEL (self->model));
+    n_selected = g_list_model_get_n_items (G_LIST_MODEL (selection));
+    g_return_val_if_fail (n_selected > 0, NULL);
+
+    /* Get the focused item_ui, if selected.
+     * Otherwise, get the selected item_ui which is sorted the lowest.*/
+    focus_child = gtk_widget_get_focus_child (GTK_WIDGET (self->view_ui));
+    for (i = 0; i < n_selected; i++)
+    {
+        g_autoptr (NautilusViewItemModel) item = NULL;
+
+        item = g_list_model_get_item (G_LIST_MODEL (selection), i);
+        item_ui = nautilus_view_item_model_get_item_ui (item);
+        if (item_ui != NULL && gtk_widget_get_parent (item_ui) == focus_child)
+        {
+            break;
+        }
+    }
+    activate_scroll_to_item (self, i);
+
+    return get_rectangle_for_item_ui (self, item_ui);
+}
+
+static void
+set_click_mode_from_settings (NautilusColumnView *self)
+{
+    int click_policy;
+
+    click_policy = g_settings_get_enum (nautilus_preferences,
+                                        NAUTILUS_PREFERENCES_CLICK_POLICY);
+
+    self->single_click_mode = (click_policy == NAUTILUS_CLICK_POLICY_SINGLE);
+}
+
+static void
+real_click_policy_changed (NautilusFilesView *files_view)
+{
+    NautilusColumnView *self = NAUTILUS_COLUMN_VIEW (files_view);
+    set_click_mode_from_settings (self);
+}
+
+static void
+activate_selection_on_click (NautilusColumnView *self,
+                             gboolean                    open_in_new_tab)
+{
+    g_autolist (NautilusFile) selection = NULL;
+    NautilusOpenFlags flags = 0;
+    NautilusFilesView *files_view = NAUTILUS_FILES_VIEW (self);
+
+    selection = nautilus_view_get_selection (NAUTILUS_VIEW (self));
+    if (open_in_new_tab)
+    {
+        flags |= NAUTILUS_OPEN_FLAG_NEW_TAB;
+        flags |= NAUTILUS_OPEN_FLAG_DONT_MAKE_ACTIVE;
+    }
+    nautilus_files_view_activate_files (files_view, selection, flags, TRUE);
+}
+
+static void
+on_click_pressed (GtkGestureClick *gesture,
+                  gint             n_press,
+                  gdouble          x,
+                  gdouble          y,
+                  gpointer         user_data)
+{
+    NautilusColumnView *self;
+    GtkWidget *event_widget;
+    guint button;
+    GdkModifierType modifiers;
+    gboolean selection_mode;
+    gdouble view_x;
+    gdouble view_y;
+    NautilusViewItemModel *item_model;
+
+    self = NAUTILUS_COLUMN_VIEW (user_data);
+    event_widget = gtk_event_controller_get_widget (GTK_EVENT_CONTROLLER (gesture));
+    button = gtk_gesture_single_get_current_button (GTK_GESTURE_SINGLE (gesture));
+    modifiers = gtk_event_controller_get_current_event_state (GTK_EVENT_CONTROLLER (gesture));
+
+    selection_mode = (modifiers & (GDK_CONTROL_MASK | GDK_SHIFT_MASK));
+
+    gtk_widget_translate_coordinates (event_widget, GTK_WIDGET (self),
+                                      x, y,
+                                      &view_x, &view_y);
+
+    item_model = g_object_get_data (G_OBJECT (event_widget), "nautilus-view-model-item");
+    if (item_model != NULL)
+    {
+        self->activate_on_release = (self->single_click_mode &&
+                                     button == GDK_BUTTON_PRIMARY &&
+                                     n_press == 1 &&
+                                     !selection_mode);
+
+        /* GtkColumnView changes selection only with the primary button, but we
+         * need that to happen with all buttons, otherwise e.g. opening context
+         * menus would require two clicks: a primary click to select the item,
+         * followed by a secondary click to open the menu.
+         * When holding Ctrl and Shift, GtkColumnView does a good job, let's not
+         * interfere in that case. */
+        if (!selection_mode && button != GDK_BUTTON_PRIMARY)
+        {
+            GtkSelectionModel *selection_model = GTK_SELECTION_MODEL (self->model);
+            guint position;
+
+            position = nautilus_view_model_get_index (self->model, item_model);
+            if (!gtk_selection_model_is_selected (selection_model, position))
+            {
+                gtk_selection_model_select_item (selection_model, position, TRUE);
+            }
+        }
+
+        if (button == GDK_BUTTON_PRIMARY && n_press == 2)
+        {
+            activate_selection_on_click (self, modifiers & GDK_SHIFT_MASK);
+            gtk_gesture_set_state (GTK_GESTURE (gesture), GTK_EVENT_SEQUENCE_CLAIMED);
+            self->activate_on_release = FALSE;
+        }
+        else if (button == GDK_BUTTON_MIDDLE && n_press == 1 && !selection_mode)
+        {
+            activate_selection_on_click (self, TRUE);
+            gtk_gesture_set_state (GTK_GESTURE (gesture), GTK_EVENT_SEQUENCE_CLAIMED);
+        }
+        else if (button == GDK_BUTTON_SECONDARY)
+        {
+            nautilus_files_view_pop_up_selection_context_menu (NAUTILUS_FILES_VIEW (self),
+                                                               view_x, view_y);
+            gtk_gesture_set_state (GTK_GESTURE (gesture), GTK_EVENT_SEQUENCE_CLAIMED);
+        }
+    }
+    else
+    {
+        /* Don't interfere with GtkColumnView default selection handling when
+         * holding Ctrl and Shift. */
+        if (!selection_mode && !self->activate_on_release)
+        {
+            nautilus_view_set_selection (NAUTILUS_VIEW (self), NULL);
+        }
+
+        if (button == GDK_BUTTON_SECONDARY)
+        {
+            nautilus_files_view_pop_up_background_context_menu (NAUTILUS_FILES_VIEW (self),
+                                                                view_x, view_y);
+        }
+    }
+}
+
+static void
+on_click_released (GtkGestureClick *gesture,
+                   gint             n_press,
+                   gdouble          x,
+                   gdouble          y,
+                   gpointer         user_data)
+{
+    NautilusColumnView *self = NAUTILUS_COLUMN_VIEW (user_data);
+
+    if (self->activate_on_release)
+    {
+        activate_selection_on_click (self, FALSE);
+        gtk_gesture_set_state (GTK_GESTURE (gesture), GTK_EVENT_SEQUENCE_CLAIMED);
+    }
+    self->activate_on_release = FALSE;
+}
+
+static void
+on_click_stopped (GtkGestureClick *gesture,
+                  gpointer         user_data)
+{
+    NautilusColumnView *self = NAUTILUS_COLUMN_VIEW (user_data);
+
+    self->activate_on_release = FALSE;
+}
+
+static void
+on_longpress_gesture_pressed_callback (GtkGestureLongPress *gesture,
+                                       gdouble              x,
+                                       gdouble              y,
+                                       gpointer             user_data)
+{
+    NautilusColumnView *self;
+    GtkWidget *event_widget;
+    gdouble view_x;
+    gdouble view_y;
+    NautilusViewItemModel *item_model;
+
+    self = NAUTILUS_COLUMN_VIEW (user_data);
+    event_widget = gtk_event_controller_get_widget (GTK_EVENT_CONTROLLER (gesture));
+
+    gtk_widget_translate_coordinates (event_widget,
+                                      GTK_WIDGET (self),
+                                      x, y, &view_x, &view_y);
+
+    item_model = g_object_get_data (G_OBJECT (event_widget), "nautilus-view-model-item");
+    if (item_model != NULL)
+    {
+        nautilus_files_view_pop_up_selection_context_menu (NAUTILUS_FILES_VIEW (self),
+                                                           view_x, view_y);
+        gtk_gesture_set_state (GTK_GESTURE (gesture), GTK_EVENT_SEQUENCE_CLAIMED);
+    }
+    else
+    {
+        nautilus_view_set_selection (NAUTILUS_VIEW (self), NULL);
+        nautilus_files_view_pop_up_background_context_menu (NAUTILUS_FILES_VIEW (self),
+                                                            view_x, view_y);
+    }
+}
+
+static int
+real_compare_files (NautilusFilesView *files_view,
+                    NautilusFile      *file1,
+                    NautilusFile      *file2)
+{
+    GActionGroup *view_action_group;
+    GAction *action;
+    const gchar *target_name;
+    const SortConstants *sort_constants;
+    gboolean directories_first;
+
+    view_action_group = nautilus_files_view_get_action_group (files_view);
+    action = g_action_map_lookup_action (G_ACTION_MAP (view_action_group), "sort");
+    target_name = g_variant_get_string (g_action_get_state (action), NULL);
+    sort_constants = get_sorts_constants_from_action_target_name (target_name);
+    directories_first = nautilus_files_view_should_sort_directories_first (files_view);
+
+    return nautilus_file_compare_for_sort (file1, file2,
+                                           sort_constants->sort_type,
+                                           directories_first,
+                                           sort_constants->reversed);
+}
+
+static void
+real_end_loading (NautilusFilesView *files_view,
+                  gboolean           all_files_seen)
+{
+    NautilusColumnView *self = NAUTILUS_COLUMN_VIEW (files_view);
+
+    activate_scroll_to_item (self, 0);
+}
+
+static guint
+get_first_visible_item (NautilusColumnView *self)
+{
+    guint n_items;
+    gdouble scrolled_y;
+
+    n_items = g_list_model_get_n_items (G_LIST_MODEL (self->model));
+    scrolled_y = gtk_adjustment_get_value (self->vadjustment);
+    for (guint i = 0; i < n_items; i++)
+    {
+        g_autoptr (NautilusViewItemModel) item = NULL;
+        GtkWidget *item_ui;
+
+        item = g_list_model_get_item (G_LIST_MODEL (self->model), i);
+        item_ui = nautilus_view_item_model_get_item_ui (item);
+        if (item_ui != NULL)
+        {
+            gdouble y;
+
+            gtk_widget_translate_coordinates (item_ui, GTK_WIDGET (self->view_ui),
+                                              0, 0, NULL, &y);
+            if (gtk_widget_is_visible (item_ui) && y >= scrolled_y)
+            {
+                return i;
+            }
+        }
+    }
+
+    return G_MAXUINT;
+}
+
+static char *
+real_get_first_visible_file (NautilusFilesView *files_view)
+{
+    NautilusColumnView *self = NAUTILUS_COLUMN_VIEW (files_view);
+    guint i;
+    g_autoptr (NautilusViewItemModel) item = NULL;
+    gchar *uri = NULL;
+
+    i = get_first_visible_item (self);
+    if (i < G_MAXUINT)
+    {
+        item = g_list_model_get_item (G_LIST_MODEL (self->model), i);
+        uri = nautilus_file_get_uri (nautilus_view_item_model_get_file (item));
+    }
+    return uri;
+}
+
+typedef struct
+{
+    NautilusColumnView *view;
+    char *uri;
+} ScrollToFileData;
+
+static void
+scroll_to_file_data_free (ScrollToFileData *data)
+{
+    g_free (data->uri);
+    g_free (data);
+}
+
+static gboolean
+scroll_to_file_on_idle (ScrollToFileData *data)
+{
+    NautilusColumnView *self = data->view;
+    g_autoptr (NautilusFile) file = NULL;
+    NautilusViewItemModel *item;
+    guint i;
+
+    file = nautilus_file_get_existing_by_uri (data->uri);
+    item = nautilus_view_model_get_item_from_file (self->model, file);
+    i = nautilus_view_model_get_index (self->model, item);
+
+    activate_scroll_to_item (self, i);
+
+    self->scroll_to_file_handle_id = 0;
+    return G_SOURCE_REMOVE;
+}
+
+static void
+real_scroll_to_file (NautilusFilesView *files_view,
+                     const char        *uri)
+{
+    NautilusColumnView *self = NAUTILUS_COLUMN_VIEW (files_view);
+    ScrollToFileData *data;
+    guint handle_id;
+
+    data = g_new (ScrollToFileData, 1);
+    data->view = self;
+    data->uri = g_strdup (uri);
+    handle_id = g_idle_add_full (G_PRIORITY_LOW,
+                                 (GSourceFunc) scroll_to_file_on_idle,
+                                 data,
+                                 (GDestroyNotify) scroll_to_file_data_free);
+    self->scroll_to_file_handle_id = handle_id;
+}
+
+static void
+real_sort_directories_first_changed (NautilusFilesView *files_view)
+{
+    NautilusColumnView *self;
+
+    self = NAUTILUS_COLUMN_VIEW (files_view);
+    self->directories_first = nautilus_files_view_should_sort_directories_first (NAUTILUS_FILES_VIEW (self));
+
+    nautilus_view_model_set_sorter (self->model, gtk_column_view_get_sorter (self->view_ui));
+}
+
+static void
+action_sort_order_changed (GSimpleAction *action,
+                           GVariant      *value,
+                           gpointer       user_data)
+{
+    const gchar *target_name;
+    const SortConstants *sort_constants;
+    NautilusColumnView *self;
+    GListModel *view_columns;
+
+    /* 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_strcmp0 (g_variant_get_string (value, NULL), g_variant_get_string (g_action_get_state (G_ACTION 
(action)), NULL)) == 0)
+    {
+        return;
+    }
+
+    self = NAUTILUS_COLUMN_VIEW (user_data);
+    target_name = g_variant_get_string (value, NULL);
+    sort_constants = get_sorts_constants_from_action_target_name (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_object_get_data (G_OBJECT (factory), "nautilus-column");
+        if (nautilus_column == NULL)
+        {
+            continue;
+        }
+        g_object_get (nautilus_column, "attribute", &attribute, NULL);
+        if (g_strcmp0 (attributes[sort_constants->sort_type], attribute) == 0)
+        {
+            gtk_column_view_sort_by_column (self->view_ui,
+                                            view_column,
+                                            sort_constants->reversed);
+            break;
+        }
+    }
+
+    nautilus_view_model_set_sorter (self->model, gtk_column_view_get_sorter (self->view_ui));
+    set_directory_sort_metadata (nautilus_files_view_get_directory_as_file (NAUTILUS_FILES_VIEW (self)),
+                                 sort_constants);
+
+    g_simple_action_set_state (action, value);
+}
+
+static void
+real_add_files (NautilusFilesView *files_view,
+                GList             *files)
+{
+    NautilusColumnView *self = NAUTILUS_COLUMN_VIEW (files_view);
+    g_autoptr (GQueue) files_queue = NULL;
+    g_autoptr (GQueue) item_models = NULL;
+    gdouble adjustment_value;
+
+    files_queue = convert_glist_to_queue (files);
+    item_models = convert_files_to_item_models (self, files_queue);
+    nautilus_view_model_add_items (self->model, item_models);
+
+    /* GtkListBase anchoring doesn't cope well with our lazy loading.
+     * Assuming that GtkListBase|list.scroll-to-item resets the anchor to 0, use
+     * that as a workaround to prevent scrolling while we are at the top. */
+    adjustment_value = gtk_adjustment_get_value (self->vadjustment);
+    if (G_APPROX_VALUE (adjustment_value, 0.0, DBL_EPSILON))
+    {
+        activate_scroll_to_item (self, 0);
+    }
+}
+
+static guint
+real_get_view_id (NautilusFilesView *files_view)
+{
+    return NAUTILUS_VIEW_LIST_ID;
+}
+
+static GIcon *
+real_get_icon (NautilusFilesView *files_view)
+{
+    NautilusColumnView *self = NAUTILUS_COLUMN_VIEW (files_view);
+
+    return self->view_icon;
+}
+
+static void
+real_select_first (NautilusFilesView *files_view)
+{
+    NautilusColumnView *self = NAUTILUS_COLUMN_VIEW (files_view);
+    g_autoptr (NautilusViewItemModel) item = NULL;
+    NautilusFile *file;
+    g_autoptr (GList) selection = NULL;
+
+    item = NAUTILUS_VIEW_ITEM_MODEL (g_list_model_get_item (G_LIST_MODEL (self->model), 0));
+    if (item == NULL)
+    {
+        return;
+    }
+    file = nautilus_view_item_model_get_file (item);
+    selection = g_list_prepend (selection, file);
+    nautilus_view_set_selection (NAUTILUS_VIEW (files_view), selection);
+}
+
+static void
+real_preview_selection_event (NautilusFilesView *files_view,
+                              GtkDirectionType   direction)
+{
+    NautilusColumnView *self = NAUTILUS_COLUMN_VIEW (files_view);
+    GtkMovementStep step;
+    gint count;
+    gboolean handled;
+
+    step = (direction == GTK_DIR_UP || direction == GTK_DIR_DOWN) ?
+           GTK_MOVEMENT_DISPLAY_LINES : GTK_MOVEMENT_VISUAL_POSITIONS;
+    count = (direction == GTK_DIR_RIGHT || direction == GTK_DIR_DOWN) ?
+            1 : -1;
+
+    g_signal_emit_by_name (self->view_ui, "move-cursor", step, count, &handled);
+}
+
+static void
+action_zoom_to_level (GSimpleAction *action,
+                      GVariant      *state,
+                      gpointer       user_data)
+{
+    NautilusColumnView *self = NAUTILUS_COLUMN_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);
+    }
+}
+
+static void
+on_default_sort_order_changed (NautilusColumnView *self)
+{
+    update_sort_order_from_metadata_and_preferences (self);
+}
+
+static void
+dispose (GObject *object)
+{
+    NautilusColumnView *self;
+
+    self = NAUTILUS_COLUMN_VIEW (object);
+
+    g_clear_handle_id (&self->scroll_to_file_handle_id, g_source_remove);
+    g_clear_handle_id (&self->prioritize_thumbnailing_handle_id, g_source_remove);
+
+    g_signal_handlers_disconnect_by_data (nautilus_preferences, self);
+
+    G_OBJECT_CLASS (nautilus_column_view_parent_class)->dispose (object);
+}
+
+static void
+finalize (GObject *object)
+{
+    G_OBJECT_CLASS (nautilus_column_view_parent_class)->finalize (object);
+}
+
+static void
+prioritize_thumbnailing_on_idle (NautilusColumnView *self)
+{
+    gdouble page_size;
+    GtkWidget *first_visible_child;
+    GtkWidget *next_child;
+    guint first_index;
+    guint next_index;
+    gdouble y;
+    guint last_index;
+    g_autoptr (NautilusViewItemModel) first_item = NULL;
+    NautilusFile *file;
+
+    self->prioritize_thumbnailing_handle_id = 0;
+
+    page_size = gtk_adjustment_get_page_size (self->vadjustment);
+    first_index = get_first_visible_item (self);
+    if (first_index == G_MAXUINT)
+    {
+        return;
+    }
+
+    first_item = g_list_model_get_item (G_LIST_MODEL (self->model), first_index);
+
+    first_visible_child = nautilus_view_item_model_get_item_ui (first_item);
+
+    for (next_index = first_index + 1; next_index < g_list_model_get_n_items (G_LIST_MODEL (self->model)); 
next_index++)
+    {
+        g_autoptr (NautilusViewItemModel) next_item = NULL;
+
+        next_item = g_list_model_get_item (G_LIST_MODEL (self->model), next_index);
+        next_child = nautilus_view_item_model_get_item_ui (next_item);
+        if (next_child == NULL)
+        {
+            break;
+        }
+        if (gtk_widget_translate_coordinates (next_child, first_visible_child,
+                                              0, 0, NULL, &y))
+        {
+            if (y > page_size)
+            {
+                break;
+            }
+        }
+    }
+    last_index = next_index - 1;
+
+    /* Do the iteration in reverse to give higher priority to the top */
+    for (gint i = 0; i <= last_index - first_index; i++)
+    {
+        g_autoptr (NautilusViewItemModel) item = NULL;
+
+        item = g_list_model_get_item (G_LIST_MODEL (self->model), last_index - i);
+        g_return_if_fail (item != NULL);
+
+        file = nautilus_view_item_model_get_file (NAUTILUS_VIEW_ITEM_MODEL (item));
+        if (file != NULL && nautilus_file_is_thumbnailing (file))
+        {
+            g_autofree gchar *uri = nautilus_file_get_uri (file);
+            nautilus_thumbnail_prioritize (uri);
+        }
+    }
+}
+
+static void
+on_vadjustment_changed (GtkAdjustment *adjustment,
+                        gpointer       user_data)
+{
+    NautilusColumnView *self = NAUTILUS_COLUMN_VIEW (user_data);
+    guint handle_id;
+
+    /* Schedule on idle to rate limit and to avoid delaying scrolling. */
+    if (self->prioritize_thumbnailing_handle_id == 0)
+    {
+        handle_id = g_idle_add ((GSourceFunc) prioritize_thumbnailing_on_idle, self);
+        self->prioritize_thumbnailing_handle_id = handle_id;
+    }
+}
+
+/* Hack: makes assumptions about the internal structure of GtkColumnView */
+static inline GtkWidget *
+list_item_get_row_widget (GtkListItem *listitem)
+{
+    return gtk_widget_get_parent (gtk_widget_get_parent (gtk_list_item_get_child (listitem)));
+}
+
+static void
+bind_item_ui (GtkSignalListItemFactory *factory,
+              GtkListItem              *listitem,
+              gpointer                  user_data)
+{
+    NautilusColumnView *self = NAUTILUS_COLUMN_VIEW (user_data);
+    NautilusColumnViewItemUi *item_ui;
+    NautilusViewItemModel *item_model;
+    GtkWidget *row_widget;
+
+    item_ui = NAUTILUS_COLUMN_VIEW_ITEM_UI (gtk_list_item_get_child (listitem));
+    item_model = NAUTILUS_VIEW_ITEM_MODEL (gtk_list_item_get_item (listitem));
+
+    nautilus_column_view_item_ui_set_model (item_ui, item_model);
+    nautilus_view_item_model_set_item_ui (item_model, GTK_WIDGET (item_ui));
+
+    row_widget = list_item_get_row_widget (listitem);
+    g_object_set_data (G_OBJECT (row_widget), "nautilus-view-model-item", item_model);
+
+    /* 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. */
+    if (nautilus_column_view_item_ui_once (NAUTILUS_COLUMN_VIEW_ITEM_UI (item_ui)))
+    {
+        GtkEventController *controller;
+
+        gtk_widget_set_margin_start (row_widget, 2 * GUTTER_WIDTH);
+        gtk_widget_set_margin_end (row_widget, GUTTER_WIDTH);
+
+        /* Set controllers on the whole row */
+        controller = GTK_EVENT_CONTROLLER (gtk_gesture_click_new ());
+        gtk_widget_add_controller (row_widget, controller);
+        /* GTK sets a controller on the row_widget first, which claims the
+         * double click in the bubble phase. As a workaround, let's use the
+         * capture phase to capture it first for ourselves. */
+        gtk_event_controller_set_propagation_phase (controller, GTK_PHASE_CAPTURE);
+        gtk_gesture_single_set_button (GTK_GESTURE_SINGLE (controller), 0);
+        g_signal_connect (controller, "pressed", G_CALLBACK (on_click_pressed), self);
+        g_signal_connect (controller, "stopped", G_CALLBACK (on_click_stopped), self);
+        g_signal_connect (controller, "released", G_CALLBACK (on_click_released), self);
+
+        controller = GTK_EVENT_CONTROLLER (gtk_gesture_long_press_new ());
+        gtk_widget_add_controller (row_widget, controller);
+        gtk_event_controller_set_propagation_phase (controller, GTK_PHASE_BUBBLE);
+        gtk_gesture_single_set_touch_only (GTK_GESTURE_SINGLE (controller), TRUE);
+        g_signal_connect (controller, "pressed", G_CALLBACK (on_longpress_gesture_pressed_callback), self);
+    }
+}
+
+static void
+unbind_item_ui (GtkSignalListItemFactory *factory,
+                GtkListItem              *listitem,
+                gpointer                  user_data)
+{
+    NautilusColumnViewItemUi *item_ui;
+    NautilusViewItemModel *item_model;
+
+    item_ui = NAUTILUS_COLUMN_VIEW_ITEM_UI (gtk_list_item_get_child (listitem));
+    item_model = NAUTILUS_VIEW_ITEM_MODEL (gtk_list_item_get_item (listitem));
+
+    nautilus_column_view_item_ui_set_model (item_ui, NULL);
+    nautilus_view_item_model_set_item_ui (item_model, NULL);
+
+    g_object_set_data (G_OBJECT (list_item_get_row_widget (listitem)),
+                       "nautilus-view-model-item",
+                       NULL);
+}
+
+static void
+setup_item_ui (GtkSignalListItemFactory *factory,
+               GtkListItem              *listitem,
+               gpointer                  user_data)
+{
+    NautilusColumnView *self = NAUTILUS_COLUMN_VIEW (user_data);
+    NautilusColumnViewItemUi *item_ui;
+
+    item_ui = nautilus_column_view_item_ui_new ();
+    nautilus_column_view_item_ui_set_caption_attributes (item_ui, self->caption_attributes);
+    gtk_list_item_set_child (listitem, GTK_WIDGET (item_ui));
+}
+
+static void
+update_star (NautilusViewItemModel *item,
+             GtkImage              *star)
+{
+    g_autofree gchar *file_uri = NULL;
+    gboolean is_starred;
+
+    file_uri = nautilus_file_get_uri (nautilus_view_item_model_get_file (item));
+    is_starred = nautilus_tag_manager_file_is_starred (nautilus_tag_manager_get (), file_uri);
+
+    gtk_image_set_from_icon_name (star, is_starred ? "starred-symbolic" : "non-starred-symbolic");
+}
+
+static void
+bind_star (GtkSignalListItemFactory *factory,
+           GtkListItem              *listitem,
+           gpointer                  user_data)
+{
+    NautilusViewItemModel *item;
+    GtkImage *star;
+
+    item = NAUTILUS_VIEW_ITEM_MODEL (gtk_list_item_get_item (listitem));
+    star = GTK_IMAGE (gtk_list_item_get_child (listitem));
+
+    update_star (item, star);
+    g_signal_connect (item, "file-changed", G_CALLBACK (update_star), star);
+}
+
+static void
+unbind_star (GtkSignalListItemFactory *factory,
+             GtkListItem              *listitem,
+             gpointer                  user_data)
+{
+
+    NautilusViewItemModel *item;
+    GtkImage *star;
+
+    item = NAUTILUS_VIEW_ITEM_MODEL (gtk_list_item_get_item (listitem));
+    star = GTK_IMAGE (gtk_list_item_get_child (listitem));
+
+    g_signal_handlers_disconnect_by_func (item, G_CALLBACK (update_star), star);
+}
+
+static void
+setup_star (GtkSignalListItemFactory *factory,
+            GtkListItem              *listitem,
+            gpointer                  user_data)
+{
+    GtkWidget *star;
+
+    star = gtk_image_new_from_icon_name ("non-starred-symbolic");
+    gtk_widget_set_halign (star, GTK_ALIGN_CENTER);
+    gtk_widget_set_valign (star, GTK_ALIGN_CENTER);
+    gtk_widget_add_css_class (star, "dim-label");
+
+    gtk_list_item_set_child (listitem, star);
+}
+
+static void
+update_label (NautilusViewItemModel *item,
+              GtkLabel              *label)
+{
+    GQuark attribute_q;
+    NautilusFile *file;
+    g_autofree gchar *string = NULL;
+
+    g_object_get (g_object_get_data (G_OBJECT (label), "nautilus-column"),
+                  "attribute_q", &attribute_q,
+                  NULL);
+    file = nautilus_view_item_model_get_file (item);
+    string = nautilus_file_get_string_attribute_q (file, attribute_q);
+
+    gtk_label_set_text (label, string);
+}
+
+static void
+bind_label (GtkSignalListItemFactory *factory,
+            GtkListItem              *listitem,
+            gpointer                  user_data)
+{
+    NautilusViewItemModel *item;
+    GtkLabel *label;
+
+    item = NAUTILUS_VIEW_ITEM_MODEL (gtk_list_item_get_item (listitem));
+    label = GTK_LABEL (gtk_list_item_get_child (listitem));
+
+    update_label (item, label);
+    g_signal_connect (item, "file-changed", G_CALLBACK (update_label), label);
+}
+
+static void
+unbind_label (GtkSignalListItemFactory *factory,
+              GtkListItem              *listitem,
+              gpointer                  user_data)
+{
+    NautilusViewItemModel *item;
+    GtkLabel *label;
+
+    item = NAUTILUS_VIEW_ITEM_MODEL (gtk_list_item_get_item (listitem));
+
+    label = GTK_LABEL (gtk_list_item_get_child (listitem));
+
+    g_signal_handlers_disconnect_by_func (item, G_CALLBACK (update_star), label);
+}
+
+static void
+setup_label (GtkSignalListItemFactory *factory,
+             GtkListItem              *listitem,
+             gpointer                  user_data)
+{
+    NautilusColumn *nautilus_column;
+    gfloat xalign;
+    g_autofree gchar *column_name = NULL;
+    GtkWidget *label;
+
+    nautilus_column = g_object_get_data (G_OBJECT (factory), "nautilus-column");
+    g_object_get (nautilus_column,
+                  "xalign", &xalign,
+                  "name", &column_name,
+                  NULL);
+
+    label = gtk_label_new (NULL);
+    gtk_widget_set_valign (label, GTK_ALIGN_CENTER);
+    gtk_label_set_xalign (GTK_LABEL (label), xalign);
+    gtk_widget_add_css_class (label, "dim-label");
+    if (g_strcmp0 (column_name, "permissions") == 0)
+    {
+        gtk_widget_add_css_class (label, "monospace");
+    }
+    else
+    {
+        gtk_widget_add_css_class (label, "numeric");
+    }
+
+    gtk_list_item_set_child (listitem, label);
+
+    g_object_set_data (G_OBJECT (label), "nautilus-column", nautilus_column);
+}
+
+static void
+setup_view_columns (NautilusColumnView *self)
+{
+    GtkListItemFactory *factory;
+    g_autolist (NautilusColumn) nautilus_columns = NULL;
+    gchar **default_column_order, **default_visible_columns;
+    g_autoptr (GtkColumnViewColumn) dummy_column = NULL;
+
+#if 0
+    g_settings_bind (nautilus_list_view_preferences, NAUTILUS_PREFERENCES_LIST_VIEW_USE_TREE,
+                     view->details->tree_view, "show-expanders",
+                     G_SETTINGS_BIND_DEFAULT);
+#endif
+
+    nautilus_columns = nautilus_get_all_columns ();
+
+    for (GList *l = nautilus_columns; l != NULL; l = l->next)
+    {
+        NautilusColumn *nautilus_column = NAUTILUS_COLUMN (l->data);
+        g_autofree gchar *name = NULL;
+        g_autofree gchar *label = NULL;
+        GQuark attribute_q = 0;
+        GtkSortType sort_order;
+        NautilusColumnViewSortData *sort_data;
+        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);
+
+        sort_data = g_new0 (NautilusColumnViewSortData, 1);
+        sort_data->self = self;
+        sort_data->attribute_q = attribute_q;
+        sorter = gtk_custom_sorter_new (nautilus_column_view_sort, 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_item_ui), self);
+            g_signal_connect (factory, "bind", G_CALLBACK (bind_item_ui), self);
+            g_signal_connect (factory, "unbind", G_CALLBACK (unbind_item_ui), 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), self);
+            g_signal_connect (factory, "bind", G_CALLBACK (bind_star), self);
+            g_signal_connect (factory, "unbind", G_CALLBACK (unbind_star), self);
+
+            gtk_column_view_column_set_title (view_column, "");
+            gtk_column_view_column_set_resizable (view_column, FALSE);
+            gtk_column_view_column_set_fixed_width (view_column, STAR_COLUMN_WIDTH);
+        }
+        else
+        {
+            g_signal_connect (factory, "setup", G_CALLBACK (setup_label), self);
+            g_signal_connect (factory, "bind", G_CALLBACK (bind_label), self);
+            g_signal_connect (factory, "unbind", G_CALLBACK (unbind_label), self);
+        }
+
+        gtk_column_view_append_column (self->view_ui, view_column);
+
+        g_object_set_data_full (G_OBJECT (factory),
+                                "nautilus-column",
+                                g_object_ref (nautilus_column),
+                                g_object_unref);
+    }
+
+    default_visible_columns = g_settings_get_strv (nautilus_list_view_preferences,
+                                                   NAUTILUS_PREFERENCES_LIST_VIEW_DEFAULT_VISIBLE_COLUMNS);
+    default_column_order = g_settings_get_strv (nautilus_list_view_preferences,
+                                                NAUTILUS_PREFERENCES_LIST_VIEW_DEFAULT_COLUMN_ORDER);
+
+    /* HACK: Workaround to avoid cutting the end of the row because of the
+     * gutters. Always keep this dummy column visible and at the end. */
+    dummy_column = gtk_column_view_column_new ("", gtk_signal_list_item_factory_new ());
+    gtk_column_view_column_set_fixed_width (dummy_column, 3 * GUTTER_WIDTH);
+    gtk_column_view_append_column (self->view_ui, dummy_column);
+
+    /* Apply the default column order and visible columns, to get it
+     * right most of the time. The metadata will be checked when a
+     * folder is loaded */
+    apply_columns_settings (self,
+                            default_column_order,
+                            default_visible_columns);
+}
+
+static GtkColumnView *
+create_view_ui (NautilusColumnView *self)
+{
+    GtkWidget *widget;
+
+    widget = gtk_column_view_new (GTK_SELECTION_MODEL (self->model));
+
+    gtk_widget_set_valign (widget, GTK_ALIGN_START);
+
+    /* We don't use the built-in child activation feature 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);
+
+    return GTK_COLUMN_VIEW (widget);
+}
+
+const GActionEntry view_column_actions[] =
+{
+    { "sort", NULL, "s", "'invalid'", action_sort_order_changed },
+    { "zoom-to-level", NULL, NULL, "1", action_zoom_to_level }
+};
+
+static void
+constructed (GObject *object)
+{
+    NautilusColumnView *self = NAUTILUS_COLUMN_VIEW (object);
+    GtkWidget *content_widget;
+    GtkAdjustment *vadjustment;
+    GActionGroup *view_action_group;
+    GtkEventController *controller;
+
+    content_widget = nautilus_files_view_get_content_widget (NAUTILUS_FILES_VIEW (self));
+    vadjustment = gtk_scrolled_window_get_vadjustment (GTK_SCROLLED_WINDOW (content_widget));
+
+    self->vadjustment = vadjustment;
+    g_signal_connect (vadjustment, "changed", (GCallback) on_vadjustment_changed, self);
+    g_signal_connect (vadjustment, "value-changed", (GCallback) on_vadjustment_changed, self);
+
+    self->model = nautilus_view_model_new ();
+
+    self->view_ui = create_view_ui (self);
+    setup_view_columns (self);
+    nautilus_view_model_set_sorter (self->model, gtk_column_view_get_sorter (self->view_ui));
+
+    g_signal_connect_swapped (GTK_SELECTION_MODEL (self->model),
+                              "selection-changed",
+                              G_CALLBACK (nautilus_files_view_notify_selection_changed),
+                              NAUTILUS_FILES_VIEW (self));
+
+    self->view_icon = g_themed_icon_new ("view-list-symbolic");
+
+    controller = GTK_EVENT_CONTROLLER (gtk_gesture_click_new ());
+    gtk_widget_add_controller (GTK_WIDGET (content_widget), controller);
+    gtk_event_controller_set_propagation_phase (controller, GTK_PHASE_BUBBLE);
+    gtk_gesture_single_set_button (GTK_GESTURE_SINGLE (controller), 0);
+    g_signal_connect (controller, "pressed",
+                      G_CALLBACK (on_click_pressed), self);
+    g_signal_connect (controller, "stopped",
+                      G_CALLBACK (on_click_stopped), self);
+    g_signal_connect (controller, "released",
+                      G_CALLBACK (on_click_released), self);
+
+    controller = GTK_EVENT_CONTROLLER (gtk_gesture_long_press_new ());
+    gtk_widget_add_controller (GTK_WIDGET (self->view_ui), controller);
+    gtk_event_controller_set_propagation_phase (controller, GTK_PHASE_BUBBLE);
+    gtk_gesture_single_set_touch_only (GTK_GESTURE_SINGLE (controller), TRUE);
+    g_signal_connect (controller, "pressed",
+                      (GCallback) on_longpress_gesture_pressed_callback, self);
+
+    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),
+                                     view_column_actions,
+                                     G_N_ELEMENTS (view_column_actions),
+                                     self);
+
+    gtk_widget_show (GTK_WIDGET (self));
+
+    view_action_group = nautilus_files_view_get_action_group (NAUTILUS_FILES_VIEW (self));
+    g_action_map_add_action_entries (G_ACTION_MAP (view_action_group),
+                                     view_column_actions,
+                                     G_N_ELEMENTS (view_column_actions),
+                                     self);
+    self->zoom_level = get_default_zoom_level ();
+    /* Keep the action synced with the actual value, so the toolbar can poll it */
+    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_column_view_class_init (NautilusColumnViewClass *klass)
+{
+    GObjectClass *object_class = G_OBJECT_CLASS (klass);
+    NautilusFilesViewClass *files_view_class = NAUTILUS_FILES_VIEW_CLASS (klass);
+
+    object_class->dispose = dispose;
+    object_class->finalize = finalize;
+    object_class->constructed = constructed;
+
+    files_view_class->add_files = real_add_files;
+    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->click_policy_changed = real_click_policy_changed;
+    files_view_class->clear = real_clear;
+    files_view_class->file_changed = real_file_changed;
+    files_view_class->get_selection = real_get_selection;
+    /* TODO: remove this get_selection_for_file_transfer, this doesn't even
+     * take into account we could us the view for recursive search :/
+     * CanvasView has the same issue. */
+    files_view_class->get_selection_for_file_transfer = real_get_selection;
+    files_view_class->is_empty = real_is_empty;
+    files_view_class->remove_file = real_remove_file;
+    files_view_class->update_actions_state = real_update_actions_state;
+    files_view_class->reveal_selection = real_reveal_selection;
+    files_view_class->select_all = real_select_all;
+    files_view_class->invert_selection = real_invert_selection;
+    files_view_class->set_selection = real_set_selection;
+    files_view_class->compare_files = real_compare_files;
+    files_view_class->sort_directories_first_changed = real_sort_directories_first_changed;
+    files_view_class->end_file_changes = real_end_file_changes;
+    files_view_class->end_loading = real_end_loading;
+    files_view_class->get_view_id = real_get_view_id;
+    files_view_class->get_first_visible_file = real_get_first_visible_file;
+    files_view_class->scroll_to_file = real_scroll_to_file;
+    files_view_class->get_icon = real_get_icon;
+    files_view_class->select_first = real_select_first;
+    files_view_class->restore_standard_zoom_level = real_restore_standard_zoom_level;
+    files_view_class->get_zoom_level_percentage = real_get_zoom_level_percentage;
+    files_view_class->is_zoom_level_default = real_is_zoom_level_default;
+    files_view_class->compute_rename_popover_pointing_to = real_compute_rename_popover_pointing_to;
+    files_view_class->reveal_for_selection_context_menu = real_reveal_for_selection_context_menu;
+    files_view_class->preview_selection_event = real_preview_selection_event;
+}
+
+static void
+nautilus_column_view_init (NautilusColumnView *self)
+{
+    gtk_widget_add_css_class (GTK_WIDGET (self), "view");
+    gtk_widget_add_css_class (GTK_WIDGET (self), "nautilus-column-view");
+    set_click_mode_from_settings (self);
+
+    g_signal_connect_swapped (nautilus_preferences,
+                              "changed::" NAUTILUS_PREFERENCES_DEFAULT_SORT_ORDER,
+                              G_CALLBACK (on_default_sort_order_changed),
+                              self);
+    g_signal_connect_swapped (nautilus_preferences,
+                              "changed::" NAUTILUS_PREFERENCES_DEFAULT_SORT_IN_REVERSE_ORDER,
+                              G_CALLBACK (on_default_sort_order_changed),
+                              self);
+}
+
+NautilusColumnView *
+nautilus_column_view_new (NautilusWindowSlot *slot)
+{
+    return g_object_new (NAUTILUS_TYPE_COLUMN_VIEW,
+                         "window-slot", slot,
+                         NULL);
+}
diff --git a/src/nautilus-column-view.h b/src/nautilus-column-view.h
new file mode 100644
index 000000000..b0831151c
--- /dev/null
+++ b/src/nautilus-column-view.h
@@ -0,0 +1,18 @@
+#pragma once
+
+#include <glib.h>
+#include <gtk/gtk.h>
+
+#include "nautilus-files-view.h"
+#include "nautilus-window-slot.h"
+#include "nautilus-view-model.h"
+
+G_BEGIN_DECLS
+
+#define NAUTILUS_TYPE_COLUMN_VIEW (nautilus_column_view_get_type())
+
+G_DECLARE_FINAL_TYPE (NautilusColumnView, nautilus_column_view, NAUTILUS, COLUMN_VIEW, NautilusFilesView)
+
+NautilusColumnView *nautilus_column_view_new (NautilusWindowSlot *slot);
+
+G_END_DECLS
diff --git a/src/nautilus-files-view.c b/src/nautilus-files-view.c
index eb07d9f95..b70509d6b 100644
--- a/src/nautilus-files-view.c
+++ b/src/nautilus-files-view.c
@@ -51,6 +51,7 @@
 #include "nautilus-batch-rename-dialog.h"
 #include "nautilus-batch-rename-utilities.h"
 #include "nautilus-clipboard.h"
+#include "nautilus-column-view.h"
 #include "nautilus-compress-dialog-controller.h"
 #include "nautilus-directory.h"
 #include "nautilus-dnd.h"
@@ -9745,7 +9746,7 @@ nautilus_files_view_new (guint               id,
 
         case NAUTILUS_VIEW_LIST_ID:
         {
-            view = nautilus_list_view_new (slot);
+            view = NAUTILUS_FILES_VIEW (nautilus_column_view_new (slot));
         }
         break;
     }
diff --git a/src/nautilus-view-item-model.c b/src/nautilus-view-item-model.c
index 40e3ce9f1..9c8ac57d9 100644
--- a/src/nautilus-view-item-model.c
+++ b/src/nautilus-view-item-model.c
@@ -138,7 +138,7 @@ nautilus_view_item_model_class_init (NautilusViewItemModelClass *klass)
                                      g_param_spec_int ("icon-size",
                                                        "Icon size",
                                                        "The size in pixels of the icon",
-                                                       NAUTILUS_GRID_ICON_SIZE_SMALL,
+                                                       NAUTILUS_LIST_ICON_SIZE_SMALL,
                                                        NAUTILUS_GRID_ICON_SIZE_LARGEST,
                                                        NAUTILUS_GRID_ICON_SIZE_LARGE,
                                                        G_PARAM_READWRITE | G_PARAM_CONSTRUCT));
diff --git a/src/resources/css/Adwaita.css b/src/resources/css/Adwaita.css
index f0657f5e5..f070a1765 100644
--- a/src/resources/css/Adwaita.css
+++ b/src/resources/css/Adwaita.css
@@ -166,3 +166,42 @@
               0px 2px 0px 0px @shade_color;
   border-radius: 2px;
 }
+
+/* Column view */
+.nautilus-column-view columnview > *:first-child {
+  padding-left: 24px;
+}
+
+.nautilus-column-view columnview > listview {
+  padding-top: 6px;
+  padding-bottom: 24px;
+}
+
+.nautilus-column-view.compact columnview > listview > row {
+  border-radius: 3px;
+  margin-top: 2px;
+  margin-bottom: 2px;
+}
+
+.nautilus-column-view columnview > listview > row {
+  border-radius: 6px;
+  margin-top: 4px;
+  margin-bottom: 4px;
+}
+
+.nautilus-column-view columnview > listview > row > * {
+  padding: 6px;
+}
+
+.nautilus-column-view.compact columnview > listview > row > * {
+  padding-top: 3px;
+  padding-bottom: 3px;
+}
+
+.nautilus-column-view .thumbnail {
+  background-color: @shade_color;
+  box-shadow: 0px 1px 2px 0px @shade_color,
+              0px 0px 0px 1px @shade_color,
+              0px 2px 0px 0px @shade_color;
+  border-radius: 2px;
+}
diff --git a/src/resources/nautilus.gresource.xml b/src/resources/nautilus.gresource.xml
index d27f7b5ff..5bd750303 100644
--- a/src/resources/nautilus.gresource.xml
+++ b/src/resources/nautilus.gresource.xml
@@ -26,6 +26,7 @@
     <file>ui/nautilus-files-view-select-items.ui</file>
     <file>ui/nautilus-operations-ui-manager-request-passphrase.ui</file>
     <file>ui/nautilus-view-icon-item-ui.ui</file>
+    <file>ui/nautilus-column-view-item-ui.ui</file>
     <file alias="gtk/ui/nautilusgtksidebarrow.ui">../gtk/nautilusgtksidebarrow.ui</file>
     <file alias="gtk/ui/nautilusgtkplacesview.ui">../gtk/nautilusgtkplacesview.ui</file>
     <file alias="gtk/ui/nautilusgtkplacesviewrow.ui">../gtk/nautilusgtkplacesviewrow.ui</file>
diff --git a/src/resources/ui/nautilus-column-view-item-ui.ui 
b/src/resources/ui/nautilus-column-view-item-ui.ui
new file mode 100644
index 000000000..3f02e9972
--- /dev/null
+++ b/src/resources/ui/nautilus-column-view-item-ui.ui
@@ -0,0 +1,99 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+  <requires lib="gtk" version="4.0"/>
+  <template class="NautilusColumnViewItemUi" parent="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="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="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>
+        <child>
+          <object class="GtkLabel" id="path">
+            <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-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="third_caption">
+            <property name="visible">False</property>
+            <property name="ellipsize">end</property>
+            <property name="justify">center</property>
+            <property name="lines">2</property>
+            <property name="max-width-chars">0</property>
+            <property name="wrap">True</property>
+            <property name="wrap-mode">word-char</property>
+            <property name="valign">start</property>
+            <style>
+              <class name="caption"/>
+            </style>
+          </object>
+        </child>
+      </object>
+    </child>
+  </template>
+</interface>


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