summaryrefslogtreecommitdiffstats
path: root/src/nautilus-list-view.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/nautilus-list-view.c')
-rw-r--r--src/nautilus-list-view.c4177
1 files changed, 4177 insertions, 0 deletions
diff --git a/src/nautilus-list-view.c b/src/nautilus-list-view.c
new file mode 100644
index 0000000..66e3373
--- /dev/null
+++ b/src/nautilus-list-view.c
@@ -0,0 +1,4177 @@
+/* fm-list-view.c - implementation of list view of directory.
+ *
+ * Copyright (C) 2000 Eazel, Inc.
+ * Copyright (C) 2001, 2002 Anders Carlsson <andersca@gnu.org>
+ *
+ * The Gnome Library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * The Gnome Library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with the Gnome Library; see the file COPYING.LIB. If not,
+ * see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: John Sullivan <sullivan@eazel.com>
+ * Anders Carlsson <andersca@gnu.org>
+ * David Emory Watson <dwatson@cs.ucr.edu>
+ */
+
+#include "nautilus-list-view.h"
+#include "nautilus-list-view-private.h"
+
+#include <eel/eel-vfs-extensions.h>
+#include <gdk/gdk.h>
+#include <gdk/gdkkeysyms.h>
+#include <glib/gi18n.h>
+#include <gtk/gtk.h>
+#include <libgd/gd.h>
+#include <string.h>
+
+#define DEBUG_FLAG NAUTILUS_DEBUG_LIST_VIEW
+#include "nautilus-debug.h"
+
+#include "nautilus-clipboard.h"
+#include "nautilus-column-chooser.h"
+#include "nautilus-column-utilities.h"
+#include "nautilus-dnd.h"
+#include "nautilus-enums.h"
+#include "nautilus-error-reporting.h"
+#include "nautilus-file-utilities.h"
+#include "nautilus-files-view-dnd.h"
+#include "nautilus-global-preferences.h"
+#include "nautilus-list-model.h"
+#include "nautilus-list-view-dnd.h"
+#include "nautilus-metadata.h"
+#include "nautilus-search-directory.h"
+#include "nautilus-tag-manager.h"
+#include "nautilus-toolbar.h"
+#include "nautilus-tree-view-drag-dest.h"
+#include "nautilus-ui-utilities.h"
+#include "nautilus-view.h"
+#include "nautilus-tracker-utilities.h"
+
+struct SelectionForeachData
+{
+ GList *list;
+ GtkTreeSelection *selection;
+};
+
+/*
+ * The row height should be large enough to not clip emblems.
+ * Computing this would be costly, so we just choose a number
+ * that works well with the set of emblems we've designed.
+ */
+#define LIST_VIEW_MINIMUM_ROW_HEIGHT 28
+
+/* We wait two seconds after row is collapsed to unload the subdirectory */
+#define COLLAPSE_TO_UNLOAD_DELAY 2
+
+static GdkCursor *hand_cursor = NULL;
+
+static GList *nautilus_list_view_get_selection (NautilusFilesView *view);
+static GList *nautilus_list_view_get_selection_for_file_transfer (NautilusFilesView *view);
+static void nautilus_list_view_set_zoom_level (NautilusListView *view,
+ NautilusListZoomLevel new_level);
+static void nautilus_list_view_scroll_to_file (NautilusListView *view,
+ NautilusFile *file);
+static void nautilus_list_view_sort_directories_first_changed (NautilusFilesView *view);
+
+static void apply_columns_settings (NautilusListView *list_view,
+ char **column_order,
+ char **visible_columns);
+static char **get_visible_columns (NautilusListView *list_view);
+static char **get_default_visible_columns (NautilusListView *list_view);
+static char **get_column_order (NautilusListView *list_view);
+static char **get_default_column_order (NautilusListView *list_view);
+static void on_clipboard_owner_changed (GtkClipboard *clipboard,
+ GdkEvent *event,
+ gpointer user_data);
+
+
+G_DEFINE_TYPE (NautilusListView, nautilus_list_view, NAUTILUS_TYPE_FILES_VIEW);
+
+static const char *default_search_visible_columns[] =
+{
+ "name", "size", "where", NULL
+};
+
+static const char *default_search_columns_order[] =
+{
+ "name", "size", "where", NULL
+};
+
+static const char *default_recent_visible_columns[] =
+{
+ "name", "where", "recency", NULL
+};
+
+static const char *default_recent_columns_order[] =
+{
+ "name", "where", "recency", NULL
+};
+
+static const char *default_trash_visible_columns[] =
+{
+ "name", "size", "trash_orig_path", "trashed_on", NULL
+};
+
+static const char *default_trash_columns_order[] =
+{
+ "name", "size", "trash_orig_path", "trashed_on", NULL
+};
+
+static const gchar *
+get_default_sort_order (NautilusFile *file,
+ gboolean *reversed)
+{
+ NautilusFileSortType sort_type;
+
+ /* This array makes the #NautilusFileSortType values correspond to the
+ * respective column attribute.
+ */
+ const char *attributes[] =
+ {
+ "name",
+ "size",
+ "type",
+ "date_modified",
+ "date_accessed",
+ "starred",
+ "trashed_on",
+ "search_relevance",
+ "recency",
+ NULL
+ };
+
+ sort_type = nautilus_file_get_default_sort_type (file, reversed);
+
+ return attributes[sort_type];
+}
+
+static void
+list_selection_changed_callback (GtkTreeSelection *selection,
+ gpointer user_data)
+{
+ NautilusFilesView *view;
+
+ view = NAUTILUS_FILES_VIEW (user_data);
+
+ nautilus_files_view_notify_selection_changed (view);
+}
+
+static void
+preview_selected_items (NautilusListView *view)
+{
+ GList *file_list;
+
+ file_list = nautilus_list_view_get_selection (NAUTILUS_FILES_VIEW (view));
+
+ if (file_list != NULL)
+ {
+ nautilus_files_view_preview_files (NAUTILUS_FILES_VIEW (view),
+ file_list, NULL);
+ nautilus_file_list_free (file_list);
+ }
+}
+
+static void
+activate_selected_items (NautilusListView *view)
+{
+ GList *file_list;
+
+ file_list = nautilus_list_view_get_selection (NAUTILUS_FILES_VIEW (view));
+ if (file_list != NULL)
+ {
+ nautilus_files_view_activate_files (NAUTILUS_FILES_VIEW (view),
+ file_list,
+ 0, TRUE);
+ nautilus_file_list_free (file_list);
+ }
+}
+
+static void
+activate_selected_items_alternate (NautilusListView *view,
+ NautilusFile *file,
+ gboolean open_in_tab)
+{
+ GList *file_list;
+ NautilusWindowOpenFlags flags;
+
+ flags = 0;
+
+ if (open_in_tab)
+ {
+ flags |= NAUTILUS_WINDOW_OPEN_FLAG_NEW_TAB;
+ flags |= NAUTILUS_WINDOW_OPEN_FLAG_DONT_MAKE_ACTIVE;
+ }
+ else
+ {
+ flags |= NAUTILUS_WINDOW_OPEN_FLAG_NEW_WINDOW;
+ }
+
+ if (file != NULL)
+ {
+ nautilus_file_ref (file);
+ file_list = g_list_prepend (NULL, file);
+ }
+ else
+ {
+ file_list = nautilus_list_view_get_selection (NAUTILUS_FILES_VIEW (view));
+ }
+ nautilus_files_view_activate_files (NAUTILUS_FILES_VIEW (view),
+ file_list,
+ flags,
+ TRUE);
+ nautilus_file_list_free (file_list);
+}
+
+static gboolean
+button_event_modifies_selection (const GdkEvent *event)
+{
+ GdkModifierType state;
+
+ gdk_event_get_state (event, &state);
+
+ return (state & (GDK_CONTROL_MASK | GDK_SHIFT_MASK)) != 0;
+}
+
+static int
+get_click_policy (void)
+{
+ return g_settings_get_enum (nautilus_preferences,
+ NAUTILUS_PREFERENCES_CLICK_POLICY);
+}
+
+static void
+nautilus_list_view_did_not_drag (NautilusListView *view,
+ const GdkEvent *event)
+{
+ GtkTreeView *tree_view;
+ GtkTreeSelection *selection;
+ gdouble x;
+ gdouble y;
+ GtkTreePath *path;
+ guint button;
+ GdkModifierType state;
+
+ tree_view = view->details->tree_view;
+ selection = gtk_tree_view_get_selection (tree_view);
+
+ if (!gdk_event_get_coords (event, &x, &y))
+ {
+ return;
+ }
+
+ if (!gtk_tree_view_get_path_at_pos (tree_view, x, y, &path, NULL, NULL, NULL))
+ {
+ return;
+ }
+
+ if (!gdk_event_get_button (event, &button))
+ {
+ return;
+ }
+
+ gdk_event_get_state (event, &state);
+
+ if ((button == GDK_BUTTON_PRIMARY || button == GDK_BUTTON_MIDDLE)
+ && ((state & GDK_CONTROL_MASK) != 0 ||
+ (state & GDK_SHIFT_MASK) == 0)
+ && view->details->row_selected_on_button_down)
+ {
+ if (!button_event_modifies_selection (event))
+ {
+ gtk_tree_selection_unselect_all (selection);
+ gtk_tree_selection_select_path (selection, path);
+ }
+ else
+ {
+ gtk_tree_selection_unselect_path (selection, path);
+ }
+ }
+
+ if ((get_click_policy () == NAUTILUS_CLICK_POLICY_SINGLE)
+ && !button_event_modifies_selection (event))
+ {
+ if (button == GDK_BUTTON_PRIMARY)
+ {
+ activate_selected_items (view);
+ }
+ else if (button == GDK_BUTTON_MIDDLE)
+ {
+ activate_selected_items_alternate (view, NULL, TRUE);
+ }
+ }
+ gtk_tree_path_free (path);
+}
+
+static gboolean
+on_motion_notify (GtkWidget *widget,
+ GdkEvent *event,
+ gpointer callback_data)
+{
+ NautilusListView *view;
+ gdouble x;
+ gdouble y;
+
+ view = NAUTILUS_LIST_VIEW (callback_data);
+
+ /* Remove after switching to GTK+ 4. */
+ if (gdk_event_get_window (event) != gtk_tree_view_get_bin_window (GTK_TREE_VIEW (widget)))
+ {
+ return GDK_EVENT_PROPAGATE;
+ }
+
+ g_assert (gdk_event_get_coords (event, &x, &y));
+
+ if (get_click_policy () == NAUTILUS_CLICK_POLICY_SINGLE)
+ {
+ GtkTreePath *old_hover_path;
+
+ old_hover_path = view->details->hover_path;
+ gtk_tree_view_get_path_at_pos (GTK_TREE_VIEW (widget),
+ x, y,
+ &view->details->hover_path,
+ NULL, NULL, NULL);
+
+ if ((old_hover_path != NULL) != (view->details->hover_path != NULL))
+ {
+ if (view->details->hover_path != NULL)
+ {
+ gdk_window_set_cursor (gtk_widget_get_window (widget), hand_cursor);
+ }
+ else
+ {
+ gdk_window_set_cursor (gtk_widget_get_window (widget), NULL);
+ }
+ }
+
+ if (old_hover_path != NULL)
+ {
+ gtk_tree_path_free (old_hover_path);
+ }
+ }
+
+ return GDK_EVENT_PROPAGATE;
+}
+
+static gboolean
+on_leave_notify (GtkWidget *widget,
+ GdkEvent *event,
+ gpointer callback_data)
+{
+ NautilusListView *view;
+
+ view = NAUTILUS_LIST_VIEW (callback_data);
+
+ if (get_click_policy () == NAUTILUS_CLICK_POLICY_SINGLE &&
+ view->details->hover_path != NULL)
+ {
+ gtk_tree_path_free (view->details->hover_path);
+ view->details->hover_path = NULL;
+ }
+
+ return GDK_EVENT_PROPAGATE;
+}
+
+static gboolean
+on_enter_notify (GtkWidget *widget,
+ GdkEvent *event,
+ gpointer callback_data)
+{
+ NautilusListView *view;
+
+ view = NAUTILUS_LIST_VIEW (callback_data);
+
+ if (get_click_policy () == NAUTILUS_CLICK_POLICY_SINGLE)
+ {
+ gdouble x;
+ gdouble y;
+
+ if (view->details->hover_path != NULL)
+ {
+ gtk_tree_path_free (view->details->hover_path);
+ }
+
+ g_assert (gdk_event_get_coords (event, &x, &y));
+
+ gtk_tree_view_get_path_at_pos (GTK_TREE_VIEW (widget),
+ x, y,
+ &view->details->hover_path,
+ NULL, NULL, NULL);
+
+ if (view->details->hover_path != NULL)
+ {
+ gdk_window_set_cursor (gtk_widget_get_window (widget), hand_cursor);
+ }
+ }
+
+ return GDK_EVENT_PROPAGATE;
+}
+
+static void
+row_activated_callback (GtkTreeView *treeview,
+ GtkTreePath *path,
+ GtkTreeViewColumn *column,
+ NautilusListView *view)
+{
+ activate_selected_items (view);
+}
+
+static gboolean
+check_starred_status (GtkTreeModel *model,
+ GtkTreePath *path,
+ GtkTreeIter *iter,
+ gpointer data)
+{
+ NautilusFile *file;
+ GList *l;
+ GList *changed_files;
+
+ changed_files = data;
+
+ gtk_tree_model_get (GTK_TREE_MODEL (model),
+ iter,
+ NAUTILUS_LIST_MODEL_FILE_COLUMN, &file,
+ -1);
+
+ if (!file)
+ {
+ return FALSE;
+ }
+
+ for (l = changed_files; l != NULL; l = l->next)
+ {
+ if (nautilus_file_compare_location (NAUTILUS_FILE (l->data), file) == 0)
+ {
+ gtk_tree_model_row_changed (model, path, iter);
+ }
+ }
+
+ nautilus_file_unref (file);
+
+ return FALSE;
+}
+
+static void
+on_starred_files_changed (NautilusTagManager *tag_manager,
+ GList *changed_files,
+ gpointer user_data)
+{
+ NautilusListView *list_view;
+
+ list_view = NAUTILUS_LIST_VIEW (user_data);
+
+ gtk_tree_model_foreach (GTK_TREE_MODEL (list_view->details->model),
+ check_starred_status,
+ changed_files);
+}
+
+static void
+on_star_cell_renderer_clicked (GtkTreePath *path,
+ NautilusListView *list_view)
+{
+ NautilusListModel *list_model;
+ NautilusFile *file;
+ g_autofree gchar *uri = NULL;
+ GList *selection;
+
+ list_model = list_view->details->model;
+
+ file = nautilus_list_model_file_for_path (list_model, path);
+
+ if (file == NULL)
+ {
+ /* This row is a label, not a file */
+ return;
+ }
+
+ uri = nautilus_file_get_uri (file);
+ selection = g_list_prepend (NULL, file);
+
+ if (nautilus_tag_manager_file_is_starred (list_view->details->tag_manager, uri))
+ {
+ nautilus_tag_manager_unstar_files (list_view->details->tag_manager,
+ G_OBJECT (list_view),
+ selection,
+ NULL,
+ list_view->details->starred_cancellable);
+ }
+ else
+ {
+ nautilus_tag_manager_star_files (list_view->details->tag_manager,
+ G_OBJECT (list_view),
+ selection,
+ NULL,
+ list_view->details->starred_cancellable);
+ }
+
+ nautilus_file_list_free (selection);
+}
+
+static void
+on_tree_view_multi_press_gesture_pressed (GtkGestureMultiPress *gesture,
+ gint n_press,
+ gdouble x,
+ gdouble y,
+ gpointer callback_data)
+{
+ NautilusListView *view;
+ GtkWidget *widget;
+ GtkTreeView *tree_view;
+ g_autoptr (GtkTreePath) path = NULL;
+ GtkTreeViewColumn *column;
+ GtkTreeSelection *selection;
+ GtkWidgetClass *tree_view_class;
+ guint button;
+ gint bin_x;
+ gint bin_y;
+ GdkEventSequence *sequence;
+ const GdkEvent *event;
+ gboolean call_parent, on_expander, show_expanders;
+ gboolean is_simple_click, path_selected;
+ NautilusFile *file;
+ gboolean on_star;
+
+ view = NAUTILUS_LIST_VIEW (callback_data);
+ widget = gtk_event_controller_get_widget (GTK_EVENT_CONTROLLER (gesture));
+ tree_view = GTK_TREE_VIEW (widget);
+ tree_view_class = GTK_WIDGET_GET_CLASS (tree_view);
+ selection = gtk_tree_view_get_selection (tree_view);
+ button = gtk_gesture_single_get_current_button (GTK_GESTURE_SINGLE (gesture));
+
+ gtk_tree_view_convert_widget_to_bin_window_coords (tree_view, x, y, &bin_x, &bin_y);
+
+ view->details->last_event_button_x = bin_x;
+ view->details->last_event_button_y = bin_y;
+
+ /* Don't handle extra mouse buttons here */
+ if (button > 5)
+ {
+ return;
+ }
+
+ sequence = gtk_gesture_single_get_current_sequence (GTK_GESTURE_SINGLE (gesture));
+ event = gtk_gesture_get_last_event (GTK_GESTURE (gesture), sequence);
+
+ /* Remove after switching to GTK+ 4. */
+ if (gdk_event_get_window (event) != gtk_tree_view_get_bin_window (tree_view))
+ {
+ return;
+ }
+
+ nautilus_list_model_set_drag_view
+ (NAUTILUS_LIST_MODEL (gtk_tree_view_get_model (tree_view)),
+ tree_view,
+ bin_x, bin_y);
+
+ /* Ignore double click if we are in single click mode */
+ if (get_click_policy () == NAUTILUS_CLICK_POLICY_SINGLE && n_press >= 2)
+ {
+ return;
+ }
+
+ view->details->ignore_button_release = FALSE;
+ is_simple_click = ((button == GDK_BUTTON_PRIMARY || button == GDK_BUTTON_MIDDLE) && (n_press == 1));
+
+ /* No item at this position */
+ if (!gtk_tree_view_get_path_at_pos (tree_view, bin_x, bin_y,
+ &path, &column, NULL, NULL))
+ {
+ if (is_simple_click)
+ {
+ g_clear_pointer (&view->details->first_click_path, gtk_tree_path_free);
+ }
+
+ gtk_tree_selection_unselect_all (gtk_tree_view_get_selection (tree_view));
+
+ if (button == GDK_BUTTON_SECONDARY)
+ {
+ nautilus_files_view_pop_up_background_context_menu (NAUTILUS_FILES_VIEW (view),
+ event);
+ }
+
+ return;
+ }
+
+ call_parent = TRUE;
+ on_expander = FALSE;
+ path_selected = gtk_tree_selection_path_is_selected (selection, path);
+ show_expanders = g_settings_get_boolean (nautilus_list_view_preferences,
+ NAUTILUS_PREFERENCES_LIST_VIEW_USE_TREE);
+
+ if (show_expanders)
+ {
+ GdkRectangle cell_area;
+
+ gtk_tree_view_get_cell_area (tree_view, path, column, &cell_area);
+
+ /* We assume that the cell area excludes the expander itself.
+ * Explanatory link for future reference:
+ * https://gitlab.gnome.org/GNOME/nautilus/merge_requests/97#note_58649 */
+
+ if (gtk_widget_get_direction (widget) == GTK_TEXT_DIR_RTL)
+ {
+ on_expander = bin_x > (cell_area.x + cell_area.width);
+ }
+ else
+ {
+ on_expander = bin_x < cell_area.x;
+ }
+ }
+
+ /* Keep track of path of last click so double clicks only happen
+ * on the same item */
+ if (is_simple_click)
+ {
+ g_clear_pointer (&view->details->first_click_path, gtk_tree_path_free);
+ view->details->first_click_path = gtk_tree_path_copy (path);
+ }
+
+ on_star = (g_strcmp0 (gtk_tree_view_column_get_title (column), "Star") == 0 &&
+ !gtk_tree_view_is_blank_at_pos (tree_view,
+ bin_x,
+ bin_y,
+ NULL,
+ NULL,
+ NULL,
+ NULL));
+
+ if (is_simple_click && on_star)
+ {
+ on_star_cell_renderer_clicked (path, view);
+ }
+ else if (n_press == 2 && !on_star)
+ {
+ /* Double clicking does not trigger a D&D action. */
+ view->details->drag_button = 0;
+
+ /* NOTE: Activation can actually destroy the view if we're switching */
+ if (!on_expander &&
+ view->details->first_click_path &&
+ gtk_tree_path_compare (path, view->details->first_click_path) == 0)
+ {
+ if ((button == GDK_BUTTON_PRIMARY) && button_event_modifies_selection (event))
+ {
+ file = nautilus_list_model_file_for_path (view->details->model, path);
+ if (file != NULL)
+ {
+ activate_selected_items_alternate (view, file, TRUE);
+ nautilus_file_unref (file);
+ }
+ }
+ else if ((button == GDK_BUTTON_PRIMARY || button == GDK_BUTTON_SECONDARY))
+ {
+ activate_selected_items (view);
+ }
+ }
+ else
+ {
+ return;
+ }
+ }
+ else
+ {
+ GdkModifierType state;
+ g_autoptr (GtkTreePath) cursor = NULL;
+ GList *selected_rows = NULL;
+
+ gdk_event_get_state (event, &state);
+
+ if (button == GDK_BUTTON_SECONDARY)
+ {
+ if (path_selected)
+ {
+ /* We're going to filter out some situations where
+ * we can't let the default code run because all
+ * but one row would be would be deselected. We don't
+ * want that; we want the right click menu or single
+ * click to apply to everything that's currently selected.
+ */
+ call_parent = FALSE;
+ }
+ else if ((state & GDK_CONTROL_MASK) != 0)
+ {
+ /* If CTRL is pressed, we don't allow the parent
+ * class to handle it, since GtkTreeView doesn't
+ * do it as intended currently.
+ */
+ call_parent = FALSE;
+ if ((state & GDK_SHIFT_MASK) != 0)
+ {
+ /* This is the CTRL+SHIFT selection mode which
+ * we handleourselves, as the parent class would
+ * otherwise do an unexpected selection.
+ */
+ gtk_tree_view_get_cursor (tree_view, &cursor, NULL);
+ if (cursor != NULL)
+ {
+ gtk_tree_selection_select_range (selection, cursor, path);
+ }
+ else
+ {
+ gtk_tree_selection_select_path (selection, path);
+ }
+ }
+ else
+ {
+ gtk_tree_selection_select_path (selection, path);
+ }
+ selected_rows = gtk_tree_selection_get_selected_rows (selection, NULL);
+
+ /* This unselects everything */
+ gtk_tree_view_set_cursor (tree_view, path, NULL, FALSE);
+
+ /* So select it again */
+ for (GList *l = selected_rows; l != NULL; l = l->next)
+ {
+ gtk_tree_selection_select_path (selection, l->data);
+ }
+ g_list_free_full (selected_rows, (GDestroyNotify) gtk_tree_path_free);
+ }
+ else if (on_expander)
+ {
+ /* If the right click happened on an expander, we should
+ * fully change the selection on that row solely.
+ */
+ gtk_tree_view_set_cursor (tree_view, path, NULL, FALSE);
+ }
+ }
+
+ if ((button == GDK_BUTTON_PRIMARY || button == GDK_BUTTON_MIDDLE) &&
+ ((state & GDK_CONTROL_MASK) != 0 || (state & GDK_SHIFT_MASK) == 0))
+ {
+ view->details->row_selected_on_button_down = path_selected;
+
+ if (path_selected)
+ {
+ call_parent = on_expander;
+ view->details->ignore_button_release = on_expander;
+ }
+ else if ((state & GDK_CONTROL_MASK) != 0)
+ {
+ call_parent = FALSE;
+ if ((state & GDK_SHIFT_MASK) != 0)
+ {
+ gtk_tree_view_get_cursor (tree_view, &cursor, NULL);
+ if (cursor != NULL)
+ {
+ gtk_tree_selection_select_range (selection, cursor, path);
+ }
+ else
+ {
+ gtk_tree_selection_select_path (selection, path);
+ }
+ }
+ else
+ {
+ gtk_tree_selection_select_path (selection, path);
+ }
+ selected_rows = gtk_tree_selection_get_selected_rows (selection, NULL);
+
+ /* This unselects everything */
+ gtk_tree_view_set_cursor (tree_view, path, NULL, FALSE);
+
+ /* So select it again */
+ for (GList *l = selected_rows; l != NULL; l = l->next)
+ {
+ gtk_tree_selection_select_path (selection, l->data);
+ }
+ g_list_free_full (selected_rows, (GDestroyNotify) gtk_tree_path_free);
+ }
+ else
+ {
+ view->details->ignore_button_release = on_expander;
+ }
+ }
+
+ if (is_simple_click && on_expander)
+ {
+ /* Need to let the event propagate down, since propagating up
+ * by chaining up to button_press_event() doesn’t expand the
+ * expander.
+ */
+ return;
+ }
+
+ /* Needed to select an item before popping up a menu. */
+ if (call_parent)
+ {
+ g_signal_handlers_block_by_func (tree_view, row_activated_callback, view);
+ /* GTK+ 4 TODO: replace with event(), at the very least. */
+ tree_view_class->button_press_event (widget, (GdkEventButton *) event);
+ g_signal_handlers_unblock_by_func (tree_view, row_activated_callback, view);
+ }
+ else if (path_selected)
+ {
+ gtk_widget_grab_focus (widget);
+ }
+
+ if (is_simple_click && !on_expander)
+ {
+ view->details->drag_started = FALSE;
+ view->details->drag_button = button;
+ view->details->drag_x = bin_x;
+ view->details->drag_y = bin_y;
+ }
+
+ if (button == GDK_BUTTON_SECONDARY)
+ {
+ nautilus_files_view_pop_up_selection_context_menu (NAUTILUS_FILES_VIEW (view),
+ event);
+ }
+
+ /* Don't open a new tab if we are in single click mode (this would open 2 tabs),
+ * or if CTRL or SHIFT is pressed.
+ */
+ if (button == GDK_BUTTON_MIDDLE &&
+ get_click_policy () != NAUTILUS_CLICK_POLICY_SINGLE &&
+ !button_event_modifies_selection (event))
+ {
+ gtk_tree_selection_unselect_all (selection);
+ gtk_tree_selection_select_path (selection, path);
+
+ activate_selected_items_alternate (view, NULL, TRUE);
+ }
+ }
+
+ gtk_gesture_set_state (GTK_GESTURE (gesture), GTK_EVENT_SEQUENCE_CLAIMED);
+}
+
+static void
+on_tree_view_multi_press_gesture_released (GtkGestureMultiPress *gesture,
+ gint n_press,
+ gdouble x,
+ gdouble y,
+ gpointer callback_data)
+{
+ NautilusListView *view;
+ guint button;
+
+ view = NAUTILUS_LIST_VIEW (callback_data);
+ button = gtk_gesture_single_get_current_button (GTK_GESTURE_SINGLE (gesture));
+ if (button != view->details->drag_button)
+ {
+ return;
+ }
+
+ view->details->drag_button = 0;
+ if (!view->details->drag_started && !view->details->ignore_button_release)
+ {
+ GdkEventSequence *sequence;
+ const GdkEvent *event;
+
+ sequence = gtk_gesture_single_get_current_sequence (GTK_GESTURE_SINGLE (gesture));
+ event = gtk_gesture_get_last_event (GTK_GESTURE (gesture), sequence);
+ /* Typically will only happen with GTK+ <= 3.22.30 and <= 3.93.0,
+ * where ::released is emitted after ::cancel, but can’t hurt to guard
+ * against it anyway.
+ */
+ if (event == NULL)
+ {
+ return;
+ }
+
+ nautilus_list_view_did_not_drag (view, event);
+ }
+}
+
+static gboolean
+key_press_callback (GtkWidget *widget,
+ GdkEvent *event,
+ gpointer callback_data)
+{
+ NautilusFilesView *view;
+ GtkTreeView *tree_view;
+ guint keyval;
+ GdkModifierType state;
+
+ view = NAUTILUS_FILES_VIEW (callback_data);
+ tree_view = GTK_TREE_VIEW (widget);
+
+ NAUTILUS_LIST_VIEW (view)->details->last_event_button_x = -1;
+ NAUTILUS_LIST_VIEW (view)->details->last_event_button_y = -1;
+
+ if (G_UNLIKELY (!gdk_event_get_keyval (event, &keyval)))
+ {
+ g_return_val_if_reached (GDK_EVENT_PROPAGATE);
+ }
+ gdk_event_get_state (event, &state);
+
+ if (keyval == GDK_KEY_F10)
+ {
+ if ((state & GDK_CONTROL_MASK) != 0)
+ {
+ nautilus_files_view_pop_up_background_context_menu (view, NULL);
+
+ return GDK_EVENT_STOP;
+ }
+ }
+
+ if (keyval == GDK_KEY_Right)
+ {
+ g_autoptr (GtkTreePath) path = NULL;
+
+ gtk_tree_view_get_cursor (tree_view, &path, NULL);
+
+ if (path != NULL)
+ {
+ gtk_tree_view_expand_row (tree_view, path, FALSE);
+ }
+
+ return GDK_EVENT_STOP;
+ }
+
+ if (keyval == GDK_KEY_Left)
+ {
+ g_autoptr (GtkTreePath) path = NULL;
+
+ gtk_tree_view_get_cursor (tree_view, &path, NULL);
+
+ if (path != NULL && !gtk_tree_view_collapse_row (tree_view, path))
+ {
+ /* if the row is already collapsed or doesn't have any children,
+ * jump to the parent row instead.
+ */
+ if ((gtk_tree_path_get_depth (path) > 1) && gtk_tree_path_up (path))
+ {
+ gtk_tree_view_set_cursor (tree_view, path, NULL, FALSE);
+ }
+ }
+
+ return GDK_EVENT_STOP;
+ }
+
+ if (keyval == GDK_KEY_space)
+ {
+ if ((state & GDK_CONTROL_MASK) != 0)
+ {
+ return GDK_EVENT_PROPAGATE;
+ }
+
+ if (!gtk_widget_has_focus (GTK_WIDGET (NAUTILUS_LIST_VIEW (view)->details->tree_view)))
+ {
+ return GDK_EVENT_PROPAGATE;
+ }
+
+ if ((state & GDK_SHIFT_MASK) != 0)
+ {
+ activate_selected_items_alternate (NAUTILUS_LIST_VIEW (view), NULL, TRUE);
+ }
+ else
+ {
+ preview_selected_items (NAUTILUS_LIST_VIEW (view));
+ }
+
+ return GDK_EVENT_STOP;
+ }
+
+ if (keyval == GDK_KEY_v)
+ {
+ /* Eat Control + v to not enable type ahead */
+ if ((state & GDK_CONTROL_MASK) != 0)
+ {
+ return GDK_EVENT_STOP;
+ }
+ }
+
+ return GDK_EVENT_PROPAGATE;
+}
+
+static gboolean
+on_event (GtkWidget *widget,
+ GdkEvent *event,
+ gpointer user_data)
+{
+ GdkEventType event_type;
+
+ event_type = gdk_event_get_event_type (event);
+
+ /* TODO: Replace motion events with motion controllers. */
+ if (event_type == GDK_MOTION_NOTIFY)
+ {
+ return on_motion_notify (widget, event, user_data);
+ }
+ else if (event_type == GDK_ENTER_NOTIFY)
+ {
+ return on_enter_notify (widget, event, user_data);
+ }
+ else if (event_type == GDK_LEAVE_NOTIFY)
+ {
+ return on_leave_notify (widget, event, user_data);
+ }
+ else if (event_type == GDK_KEY_PRESS)
+ {
+ return key_press_callback (widget, event, user_data);
+ }
+
+ return GDK_EVENT_PROPAGATE;
+}
+
+static void
+subdirectory_done_loading_callback (NautilusDirectory *directory,
+ NautilusListView *view)
+{
+ nautilus_list_model_subdirectory_done_loading (view->details->model, directory);
+}
+
+static void
+row_expanded_callback (GtkTreeView *treeview,
+ GtkTreeIter *iter,
+ GtkTreePath *path,
+ gpointer callback_data)
+{
+ NautilusListView *view;
+ NautilusDirectory *directory;
+ char *uri;
+
+ view = NAUTILUS_LIST_VIEW (callback_data);
+
+ if (!nautilus_list_model_load_subdirectory (view->details->model, path, &directory))
+ {
+ return;
+ }
+
+ uri = nautilus_directory_get_uri (directory);
+ DEBUG ("Row expanded callback for URI %s", uri);
+ g_free (uri);
+
+ nautilus_files_view_add_subdirectory (NAUTILUS_FILES_VIEW (view), directory);
+
+ if (nautilus_directory_are_all_files_seen (directory))
+ {
+ nautilus_list_model_subdirectory_done_loading (view->details->model,
+ directory);
+ }
+ else
+ {
+ g_signal_connect_object (directory, "done-loading",
+ G_CALLBACK (subdirectory_done_loading_callback),
+ view, 0);
+ }
+
+ nautilus_directory_unref (directory);
+}
+
+typedef struct
+{
+ NautilusFile *file;
+ NautilusDirectory *directory;
+ NautilusListView *view;
+} UnloadDelayData;
+
+static void
+unload_delay_data_free (UnloadDelayData *unload_data)
+{
+ if (unload_data->view != NULL)
+ {
+ g_object_remove_weak_pointer (G_OBJECT (unload_data->view),
+ (gpointer *) &unload_data->view);
+ }
+
+ nautilus_directory_unref (unload_data->directory);
+ nautilus_file_unref (unload_data->file);
+
+ g_slice_free (UnloadDelayData, unload_data);
+}
+
+static UnloadDelayData *
+unload_delay_data_new (NautilusFile *file,
+ NautilusDirectory *parent_directory,
+ NautilusListView *view)
+{
+ UnloadDelayData *unload_data;
+
+ unload_data = g_slice_new0 (UnloadDelayData);
+ unload_data->view = view;
+ unload_data->file = nautilus_file_ref (file);
+ unload_data->directory = nautilus_directory_ref (parent_directory);
+
+ g_object_add_weak_pointer (G_OBJECT (unload_data->view),
+ (gpointer *) &unload_data->view);
+
+ return unload_data;
+}
+
+static gboolean
+unload_file_timeout (gpointer data)
+{
+ UnloadDelayData *unload_data = data;
+ GtkTreeIter iter;
+ NautilusListModel *model;
+ GtkTreePath *path;
+
+ if (unload_data->view == NULL)
+ {
+ goto out;
+ }
+
+ model = unload_data->view->details->model;
+ if (nautilus_list_model_get_tree_iter_from_file (model,
+ unload_data->file,
+ unload_data->directory,
+ &iter))
+ {
+ path = gtk_tree_model_get_path (GTK_TREE_MODEL (model), &iter);
+ if (!gtk_tree_view_row_expanded (unload_data->view->details->tree_view,
+ path))
+ {
+ nautilus_list_model_unload_subdirectory (model, &iter);
+ }
+ gtk_tree_path_free (path);
+ }
+
+out:
+ unload_delay_data_free (unload_data);
+ return FALSE;
+}
+
+static void
+row_collapsed_callback (GtkTreeView *treeview,
+ GtkTreeIter *iter,
+ GtkTreePath *path,
+ gpointer callback_data)
+{
+ NautilusListView *view;
+ NautilusFile *file;
+ NautilusDirectory *directory;
+ GtkTreeIter parent;
+ UnloadDelayData *unload_data;
+ GtkTreeModel *model;
+ char *uri;
+
+ view = NAUTILUS_LIST_VIEW (callback_data);
+ model = GTK_TREE_MODEL (view->details->model);
+
+ gtk_tree_model_get (model, iter,
+ NAUTILUS_LIST_MODEL_FILE_COLUMN, &file,
+ -1);
+
+ uri = nautilus_file_get_uri (file);
+ DEBUG ("Row collapsed callback for uri %s", uri);
+ g_free (uri);
+
+ directory = NULL;
+ if (gtk_tree_model_iter_parent (model, &parent, iter))
+ {
+ gtk_tree_model_get (model, &parent,
+ NAUTILUS_LIST_MODEL_SUBDIRECTORY_COLUMN, &directory,
+ -1);
+ }
+
+ unload_data = unload_delay_data_new (file, directory, view);
+ g_timeout_add_seconds (COLLAPSE_TO_UNLOAD_DELAY,
+ unload_file_timeout,
+ unload_data);
+
+ nautilus_file_unref (file);
+ nautilus_directory_unref (directory);
+}
+
+static void
+subdirectory_unloaded_callback (NautilusListModel *model,
+ NautilusDirectory *directory,
+ gpointer callback_data)
+{
+ NautilusListView *view;
+
+ g_return_if_fail (NAUTILUS_IS_LIST_MODEL (model));
+ g_return_if_fail (NAUTILUS_IS_DIRECTORY (directory));
+
+ view = NAUTILUS_LIST_VIEW (callback_data);
+
+ g_signal_handlers_disconnect_by_func (directory,
+ G_CALLBACK (subdirectory_done_loading_callback),
+ view);
+ nautilus_files_view_remove_subdirectory (NAUTILUS_FILES_VIEW (view), directory);
+}
+
+static gboolean
+test_expand_row_callback (GtkTreeView *tree_view,
+ GtkTreeIter *iter,
+ GtkTreePath *path,
+ gboolean user_data)
+{
+ return !g_settings_get_boolean (nautilus_list_view_preferences,
+ NAUTILUS_PREFERENCES_LIST_VIEW_USE_TREE);
+}
+
+static void
+nautilus_list_view_reveal_selection (NautilusFilesView *view)
+{
+ g_autolist (NautilusFile) selection = NULL;
+
+ g_return_if_fail (NAUTILUS_IS_LIST_VIEW (view));
+
+ selection = nautilus_view_get_selection (NAUTILUS_VIEW (view));
+
+ /* Make sure at least one of the selected items is scrolled into view */
+ if (selection != NULL)
+ {
+ NautilusListView *list_view;
+ NautilusFile *file;
+ GtkTreeIter iter;
+ GtkTreePath *path;
+
+ list_view = NAUTILUS_LIST_VIEW (view);
+ file = selection->data;
+ if (nautilus_list_model_get_first_iter_for_file (list_view->details->model, file, &iter))
+ {
+ path = gtk_tree_model_get_path (GTK_TREE_MODEL (list_view->details->model), &iter);
+
+ gtk_tree_view_scroll_to_cell (list_view->details->tree_view, path, NULL, FALSE, 0.0, 0.0);
+
+ gtk_tree_path_free (path);
+ }
+ }
+}
+
+static gboolean
+sort_criterion_changes_due_to_user (GtkTreeView *tree_view)
+{
+ GList *columns, *p;
+ GtkTreeViewColumn *column;
+ GSignalInvocationHint *ihint;
+ gboolean ret;
+
+ ret = FALSE;
+
+ columns = gtk_tree_view_get_columns (tree_view);
+ for (p = columns; p != NULL; p = p->next)
+ {
+ column = p->data;
+ ihint = g_signal_get_invocation_hint (column);
+ if (ihint != NULL)
+ {
+ ret = TRUE;
+ break;
+ }
+ }
+ g_list_free (columns);
+
+ return ret;
+}
+
+static void
+sort_column_changed_callback (GtkTreeSortable *sortable,
+ NautilusListView *view)
+{
+ NautilusFile *file;
+ gint sort_column_id, default_sort_column_id;
+ GtkSortType reversed;
+ GQuark sort_attr, default_sort_attr;
+ char *reversed_attr, *default_reversed_attr;
+ gboolean default_sort_reversed;
+
+ file = nautilus_files_view_get_directory_as_file (NAUTILUS_FILES_VIEW (view));
+
+ gtk_tree_sortable_get_sort_column_id (sortable, &sort_column_id, &reversed);
+ sort_attr = nautilus_list_model_get_attribute_from_sort_column_id (view->details->model, sort_column_id);
+
+ default_sort_column_id = nautilus_list_model_get_sort_column_id_from_attribute (view->details->model,
+ g_quark_from_string (get_default_sort_order (file, &default_sort_reversed)));
+ default_sort_attr = nautilus_list_model_get_attribute_from_sort_column_id (view->details->model, default_sort_column_id);
+ nautilus_file_set_metadata (file, NAUTILUS_METADATA_KEY_LIST_VIEW_SORT_COLUMN,
+ g_quark_to_string (default_sort_attr), g_quark_to_string (sort_attr));
+
+ default_reversed_attr = (default_sort_reversed ? "true" : "false");
+
+ if (view->details->last_sort_attr != sort_attr &&
+ sort_criterion_changes_due_to_user (view->details->tree_view))
+ {
+ /* at this point, the sort order is always GTK_SORT_ASCENDING, if the sort column ID
+ * switched. Invert the sort order, if it's the default criterion with a reversed preference,
+ * or if it makes sense for the attribute (i.e. date). */
+ if (sort_attr == default_sort_attr)
+ {
+ /* use value from preferences */
+ reversed = g_settings_get_boolean (nautilus_preferences,
+ NAUTILUS_PREFERENCES_DEFAULT_SORT_IN_REVERSE_ORDER);
+ }
+ else
+ {
+ reversed = nautilus_file_is_date_sort_attribute_q (sort_attr);
+ }
+
+ if (reversed)
+ {
+ g_signal_handlers_block_by_func (sortable, sort_column_changed_callback, view);
+ gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (view->details->model),
+ sort_column_id,
+ GTK_SORT_DESCENDING);
+ g_signal_handlers_unblock_by_func (sortable, sort_column_changed_callback, view);
+ }
+ }
+
+
+ reversed_attr = (reversed ? "true" : "false");
+ nautilus_file_set_metadata (file, NAUTILUS_METADATA_KEY_LIST_VIEW_SORT_REVERSED,
+ default_reversed_attr, reversed_attr);
+
+ /* Make sure selected item(s) is visible after sort */
+ nautilus_list_view_reveal_selection (NAUTILUS_FILES_VIEW (view));
+
+ view->details->last_sort_attr = sort_attr;
+}
+
+static char *
+get_root_uri_callback (NautilusTreeViewDragDest *dest,
+ gpointer user_data)
+{
+ NautilusListView *view;
+
+ view = NAUTILUS_LIST_VIEW (user_data);
+
+ return nautilus_files_view_get_uri (NAUTILUS_FILES_VIEW (view));
+}
+
+static NautilusFile *
+get_file_for_path_callback (NautilusTreeViewDragDest *dest,
+ GtkTreePath *path,
+ gpointer user_data)
+{
+ NautilusListView *view;
+
+ view = NAUTILUS_LIST_VIEW (user_data);
+
+ return nautilus_list_model_file_for_path (view->details->model, path);
+}
+
+
+static void
+list_view_handle_uri_list (NautilusTreeViewDragDest *dest,
+ const char *item_uris,
+ const char *target_uri,
+ GdkDragAction action,
+ NautilusListView *view)
+{
+ nautilus_files_view_handle_uri_list_drop (NAUTILUS_FILES_VIEW (view),
+ item_uris, target_uri, action);
+}
+
+static void
+list_view_handle_text (NautilusTreeViewDragDest *dest,
+ const char *text,
+ const char *target_uri,
+ GdkDragAction action,
+ NautilusListView *view)
+{
+ nautilus_files_view_handle_text_drop (NAUTILUS_FILES_VIEW (view),
+ text, target_uri, action);
+}
+
+static void
+list_view_handle_raw (NautilusTreeViewDragDest *dest,
+ const char *raw_data,
+ int length,
+ const char *target_uri,
+ const char *direct_save_uri,
+ GdkDragAction action,
+ NautilusListView *view)
+{
+ nautilus_files_view_handle_raw_drop (NAUTILUS_FILES_VIEW (view),
+ raw_data, length, target_uri, direct_save_uri,
+ action);
+}
+
+static void
+list_view_handle_hover (NautilusTreeViewDragDest *dest,
+ const char *target_uri,
+ NautilusListView *view)
+{
+ nautilus_files_view_handle_hover (NAUTILUS_FILES_VIEW (view), target_uri);
+}
+
+static void
+move_copy_items_callback (NautilusTreeViewDragDest *dest,
+ const GList *item_uris,
+ const char *target_uri,
+ guint action,
+ gpointer user_data)
+{
+ NautilusFilesView *view = user_data;
+
+ nautilus_clipboard_clear_if_colliding_uris (GTK_WIDGET (view),
+ item_uris);
+ nautilus_files_view_move_copy_items (view,
+ item_uris,
+ target_uri,
+ action);
+}
+
+static void
+column_header_menu_toggled (GtkCheckMenuItem *menu_item,
+ NautilusListView *list_view)
+{
+ NautilusFile *file;
+ char **visible_columns;
+ char **column_order;
+ const char *column;
+ GList *list = NULL;
+ GList *l;
+ int i;
+
+ file = nautilus_files_view_get_directory_as_file (NAUTILUS_FILES_VIEW (list_view));
+ visible_columns = get_visible_columns (list_view);
+ column_order = get_column_order (list_view);
+ column = g_object_get_data (G_OBJECT (menu_item), "column-name");
+
+ for (i = 0; visible_columns[i] != NULL; ++i)
+ {
+ list = g_list_prepend (list, visible_columns[i]);
+ }
+
+ if (gtk_check_menu_item_get_active (menu_item))
+ {
+ list = g_list_prepend (list, g_strdup (column));
+ }
+ else
+ {
+ l = g_list_find_custom (list, column, (GCompareFunc) g_strcmp0);
+ list = g_list_delete_link (list, l);
+ }
+
+ list = g_list_reverse (list);
+ nautilus_file_set_metadata_list (file,
+ NAUTILUS_METADATA_KEY_LIST_VIEW_VISIBLE_COLUMNS,
+ list);
+
+ g_free (visible_columns);
+
+ visible_columns = g_new0 (char *, g_list_length (list) + 1);
+ for (i = 0, l = list; l != NULL; ++i, l = l->next)
+ {
+ visible_columns[i] = l->data;
+ }
+
+ /* set view values ourselves, as new metadata could not have been
+ * updated yet.
+ */
+ apply_columns_settings (list_view, column_order, visible_columns);
+
+ g_list_free (list);
+ g_strfreev (column_order);
+ g_strfreev (visible_columns);
+}
+
+static void
+column_header_menu_use_default (GtkMenuItem *menu_item,
+ NautilusListView *list_view)
+{
+ NautilusFile *file;
+ char **default_columns;
+ char **default_order;
+
+ file = nautilus_files_view_get_directory_as_file (NAUTILUS_FILES_VIEW (list_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);
+
+ default_columns = get_default_visible_columns (list_view);
+ default_order = get_default_column_order (list_view);
+
+ /* set view values ourselves, as new metadata could not have been
+ * updated yet.
+ */
+ apply_columns_settings (list_view, default_order, default_columns);
+
+ g_strfreev (default_columns);
+ g_strfreev (default_order);
+}
+
+static gboolean
+on_column_header_event (GtkWidget *widget,
+ GdkEvent *event,
+ gpointer user_data)
+{
+ NautilusListView *list_view;
+ guint button;
+ NautilusFile *file;
+ char **visible_columns;
+ char **column_order;
+ GList *all_columns;
+ GHashTable *visible_columns_hash;
+ int i;
+ GList *l;
+ GtkWidget *menu;
+ GtkWidget *menu_item;
+
+ list_view = NAUTILUS_LIST_VIEW (user_data);
+
+ if (gdk_event_get_event_type (event) != GDK_BUTTON_PRESS)
+ {
+ return GDK_EVENT_PROPAGATE;
+ }
+
+ g_assert (gdk_event_get_button (event, &button));
+
+ if (button != GDK_BUTTON_SECONDARY)
+ {
+ return GDK_EVENT_PROPAGATE;
+ }
+
+ file = nautilus_files_view_get_directory_as_file (NAUTILUS_FILES_VIEW (list_view));
+
+ visible_columns = get_visible_columns (list_view);
+ column_order = get_column_order (list_view);
+
+ 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 (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));
+ }
+ }
+
+ menu = gtk_menu_new ();
+
+ for (l = all_columns; l != NULL; l = l->next)
+ {
+ char *name;
+ char *label;
+ char *lowercase;
+
+ g_object_get (G_OBJECT (l->data),
+ "name", &name,
+ "label", &label,
+ NULL);
+ lowercase = g_ascii_strdown (name, -1);
+
+ menu_item = gtk_check_menu_item_new_with_label (label);
+ gtk_menu_shell_append (GTK_MENU_SHELL (menu), menu_item);
+
+ g_object_set_data_full (G_OBJECT (menu_item),
+ "column-name", name, g_free);
+
+ /* name is always visible */
+ if (strcmp (lowercase, "name") == 0)
+ {
+ gtk_widget_set_sensitive (menu_item, FALSE);
+ }
+
+ if (g_hash_table_lookup (visible_columns_hash, lowercase) != NULL)
+ {
+ gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (menu_item),
+ TRUE);
+ }
+
+ g_signal_connect (menu_item,
+ "toggled",
+ G_CALLBACK (column_header_menu_toggled),
+ list_view);
+
+ g_free (lowercase);
+ g_free (label);
+ }
+
+ menu_item = gtk_separator_menu_item_new ();
+ gtk_menu_shell_append (GTK_MENU_SHELL (menu), menu_item);
+
+ menu_item = gtk_menu_item_new_with_label (_("Use Default"));
+ gtk_menu_shell_append (GTK_MENU_SHELL (menu), menu_item);
+
+ g_signal_connect (menu_item,
+ "activate",
+ G_CALLBACK (column_header_menu_use_default),
+ list_view);
+
+ gtk_widget_show_all (menu);
+ gtk_menu_popup_at_pointer (GTK_MENU (menu), event);
+
+ g_hash_table_destroy (visible_columns_hash);
+ nautilus_column_list_free (all_columns);
+ g_strfreev (column_order);
+ g_strfreev (visible_columns);
+
+ return GDK_EVENT_STOP;
+}
+
+static void
+apply_columns_settings (NautilusListView *list_view,
+ char **column_order,
+ char **visible_columns)
+{
+ GList *all_columns;
+ NautilusFile *file;
+ GList *old_view_columns, *view_columns;
+ GHashTable *visible_columns_hash;
+ GtkTreeViewColumn *prev_view_column;
+ GList *l;
+ int i;
+
+ file = nautilus_files_view_get_directory_as_file (NAUTILUS_FILES_VIEW (list_view));
+
+ /* 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 (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));
+ }
+ }
+
+ 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)
+ {
+ GtkTreeViewColumn *view_column;
+
+ view_column = g_hash_table_lookup (list_view->details->columns, 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);
+ nautilus_column_list_free (all_columns);
+
+ view_columns = g_list_reverse (view_columns);
+
+ /* hide columns that are not present in the configuration */
+ old_view_columns = gtk_tree_view_get_columns (list_view->details->tree_view);
+ for (l = old_view_columns; l != NULL; l = l->next)
+ {
+ if (g_list_find (view_columns, l->data) == NULL)
+ {
+ gtk_tree_view_column_set_visible (l->data, FALSE);
+ }
+ }
+ g_list_free (old_view_columns);
+
+ /* show new columns from the configuration */
+ for (l = view_columns; l != NULL; l = l->next)
+ {
+ gtk_tree_view_column_set_visible (l->data, TRUE);
+ }
+
+ /* place columns in the correct order */
+ prev_view_column = NULL;
+ for (l = view_columns; l != NULL; l = l->next)
+ {
+ gtk_tree_view_move_column_after (list_view->details->tree_view, l->data, prev_view_column);
+ prev_view_column = l->data;
+ }
+ g_list_free (view_columns);
+}
+
+static void
+starred_cell_data_func (GtkTreeViewColumn *column,
+ GtkCellRenderer *renderer,
+ GtkTreeModel *model,
+ GtkTreeIter *iter,
+ NautilusListView *view)
+{
+ g_autofree gchar *text = NULL;
+ g_autofree gchar *uri = NULL;
+ NautilusFile *file;
+
+ gtk_tree_model_get (model, iter,
+ view->details->file_name_column_num, &text,
+ -1);
+
+ gtk_tree_model_get (GTK_TREE_MODEL (model),
+ iter,
+ NAUTILUS_LIST_MODEL_FILE_COLUMN, &file,
+ -1);
+
+ if (file == NULL)
+ {
+ /* This row is a label, not a file */
+ g_object_set (renderer,
+ "icon-name", NULL,
+ "mode", GTK_CELL_RENDERER_MODE_INERT,
+ NULL);
+ return;
+ }
+
+ uri = nautilus_file_get_uri (file);
+
+ if (nautilus_tag_manager_file_is_starred (view->details->tag_manager, uri))
+ {
+ g_object_set (renderer,
+ "icon-name", "starred-symbolic",
+ NULL);
+ }
+ else
+ {
+ g_object_set (renderer,
+ "icon-name", "non-starred-symbolic",
+ NULL);
+ }
+
+ nautilus_file_unref (file);
+}
+
+static void
+filename_cell_data_func (GtkTreeViewColumn *column,
+ GtkCellRenderer *renderer,
+ GtkTreeModel *model,
+ GtkTreeIter *iter,
+ NautilusListView *view)
+{
+ char *text;
+ g_autofree gchar *escaped_text = NULL;
+ g_autofree gchar *escaped_name = NULL;
+ g_autofree gchar *replaced_text = NULL;
+ GtkTreePath *path;
+ PangoUnderline underline;
+ GString *display_text;
+ NautilusDirectory *directory;
+ NautilusQuery *query = NULL;
+ NautilusFile *file;
+ const gchar *snippet;
+
+ gtk_tree_model_get (model, iter,
+ view->details->file_name_column_num, &text,
+ -1);
+
+ escaped_name = g_markup_escape_text (text, -1);
+ display_text = g_string_new (escaped_name);
+
+ directory = nautilus_files_view_get_model (NAUTILUS_FILES_VIEW (view));
+
+ if (NAUTILUS_IS_SEARCH_DIRECTORY (directory))
+ {
+ query = nautilus_search_directory_get_query (NAUTILUS_SEARCH_DIRECTORY (directory));
+ }
+
+ if (get_click_policy () == NAUTILUS_CLICK_POLICY_SINGLE)
+ {
+ path = gtk_tree_model_get_path (model, iter);
+
+ if (view->details->hover_path == NULL ||
+ gtk_tree_path_compare (path, view->details->hover_path))
+ {
+ underline = PANGO_UNDERLINE_NONE;
+ }
+ else
+ {
+ underline = PANGO_UNDERLINE_SINGLE;
+ }
+
+ gtk_tree_path_free (path);
+ }
+ else
+ {
+ underline = PANGO_UNDERLINE_NONE;
+ }
+
+ if (query &&
+ nautilus_query_get_search_content (query) == NAUTILUS_QUERY_SEARCH_CONTENT_FULL_TEXT)
+ {
+ gtk_tree_model_get (model, iter,
+ NAUTILUS_LIST_MODEL_FILE_COLUMN, &file,
+ -1);
+
+ /* Rule out dummy row */
+ if (file != NULL)
+ {
+ snippet = nautilus_file_get_search_fts_snippet (file);
+ if (snippet)
+ {
+ replaced_text = g_regex_replace (view->details->regex,
+ snippet,
+ -1,
+ 0,
+ " ",
+ G_REGEX_MATCH_NEWLINE_ANY,
+ NULL);
+
+ escaped_text = g_markup_escape_text (replaced_text, -1);
+
+ g_string_append_printf (display_text,
+ " <small><span alpha='50%%'><b>%s</b></span></small>",
+ escaped_text);
+ }
+ }
+ nautilus_file_unref (file);
+ }
+
+ g_object_set (G_OBJECT (renderer),
+ "markup", display_text->str,
+ "underline", underline,
+ NULL);
+
+ g_free (text);
+ g_string_free (display_text, TRUE);
+}
+
+static void
+location_cell_data_func (GtkTreeViewColumn *column,
+ GtkCellRenderer *renderer,
+ GtkTreeModel *model,
+ GtkTreeIter *iter,
+ NautilusListView *view,
+ gboolean show_trash_orig)
+{
+ NautilusDirectory *directory;
+ GFile *home_location;
+ NautilusFile *file;
+ GFile *dir_location;
+ GFile *base_location;
+ gchar *where = NULL;
+
+ directory = nautilus_files_view_get_model (NAUTILUS_FILES_VIEW (view));
+
+ home_location = g_file_new_for_path (g_get_home_dir ());
+
+ gtk_tree_model_get (model, iter,
+ NAUTILUS_LIST_MODEL_FILE_COLUMN, &file,
+ -1);
+
+ /* The file might be NULL if we just toggled an expander
+ * and we're still loading the subdirectory.
+ */
+ if (file == NULL)
+ {
+ return;
+ }
+
+ if (show_trash_orig && nautilus_file_is_in_trash (file))
+ {
+ NautilusFile *orig_file;
+
+ orig_file = nautilus_file_get_trash_original_file (file);
+
+ if (orig_file != NULL)
+ {
+ nautilus_file_unref (file);
+ file = orig_file;
+ }
+ }
+
+ if (!nautilus_file_is_in_recent (file))
+ {
+ dir_location = nautilus_file_get_parent_location (file);
+ }
+ else
+ {
+ GFile *activation_location;
+
+ activation_location = nautilus_file_get_activation_location (file);
+ dir_location = g_file_get_parent (activation_location);
+
+ g_object_unref (activation_location);
+ }
+
+ if (!NAUTILUS_IS_SEARCH_DIRECTORY (directory))
+ {
+ base_location = g_object_ref (home_location);
+ }
+ else
+ {
+ NautilusQuery *query;
+ NautilusFile *base;
+ GFile *location;
+
+ query = nautilus_search_directory_get_query (NAUTILUS_SEARCH_DIRECTORY (directory));
+ location = nautilus_query_get_location (query);
+ base = nautilus_file_get (location);
+
+ if (!nautilus_file_is_in_recent (base))
+ {
+ base_location = nautilus_file_get_location (base);
+ }
+ else
+ {
+ base_location = g_object_ref (home_location);
+ }
+
+ nautilus_file_unref (base);
+ g_object_unref (location);
+ g_object_unref (query);
+ }
+
+ if (g_file_equal (base_location, dir_location))
+ {
+ /* Only occurs when search result is
+ * a direct child of the base location
+ */
+ where = g_strdup ("");
+ }
+ else if (g_file_equal (home_location, dir_location))
+ {
+ where = g_strdup (_("Home"));
+ }
+ else if (g_file_has_prefix (dir_location, base_location))
+ {
+ gchar *relative_path;
+
+ relative_path = g_file_get_relative_path (base_location, dir_location);
+ where = g_filename_display_name (relative_path);
+
+ g_free (relative_path);
+ }
+ else
+ {
+ where = g_file_get_path (dir_location);
+ }
+
+ g_object_set (G_OBJECT (renderer),
+ "text", where,
+ NULL);
+
+ g_free (where);
+
+ g_object_unref (base_location);
+ g_object_unref (dir_location);
+ nautilus_file_unref (file);
+ g_object_unref (home_location);
+}
+
+
+static void
+where_cell_data_func (GtkTreeViewColumn *column,
+ GtkCellRenderer *renderer,
+ GtkTreeModel *model,
+ GtkTreeIter *iter,
+ NautilusListView *view)
+{
+ location_cell_data_func (column, renderer, model, iter, view, FALSE);
+}
+
+static void
+trash_orig_path_cell_data_func (GtkTreeViewColumn *column,
+ GtkCellRenderer *renderer,
+ GtkTreeModel *model,
+ GtkTreeIter *iter,
+ NautilusListView *view)
+{
+ location_cell_data_func (column, renderer, model, iter, view, TRUE);
+}
+
+#define SMALL_ZOOM_ICON_PADDING 0
+#define STANDARD_ZOOM_ICON_PADDING 6
+#define LARGE_ZOOM_ICON_PADDING 6
+#define LARGER_ZOOM_ICON_PADDING 6
+
+static gint
+nautilus_list_view_get_icon_padding_for_zoom_level (NautilusListZoomLevel zoom_level)
+{
+ switch (zoom_level)
+ {
+ case NAUTILUS_LIST_ZOOM_LEVEL_SMALL:
+ {
+ return SMALL_ZOOM_ICON_PADDING;
+ }
+
+ case NAUTILUS_LIST_ZOOM_LEVEL_STANDARD:
+ {
+ return STANDARD_ZOOM_ICON_PADDING;
+ }
+
+ case NAUTILUS_LIST_ZOOM_LEVEL_LARGE:
+ {
+ return LARGE_ZOOM_ICON_PADDING;
+ }
+
+ case NAUTILUS_LIST_ZOOM_LEVEL_LARGER:
+ {
+ return LARGER_ZOOM_ICON_PADDING;
+ }
+
+ default:
+ g_assert_not_reached ();
+ }
+}
+
+static void
+set_up_pixbuf_size (NautilusListView *view)
+{
+ int icon_size, icon_padding;
+
+ /* Make all rows the same size. */
+ icon_size = nautilus_list_model_get_icon_size_for_zoom_level (view->details->zoom_level);
+ icon_padding = nautilus_list_view_get_icon_padding_for_zoom_level (view->details->zoom_level);
+ gtk_cell_renderer_set_fixed_size (GTK_CELL_RENDERER (view->details->pixbuf_cell),
+ -1, icon_size + 2 * icon_padding);
+
+ /* FIXME: https://bugzilla.gnome.org/show_bug.cgi?id=641518 */
+ gtk_tree_view_columns_autosize (view->details->tree_view);
+}
+
+static gint
+get_icon_scale_callback (NautilusListModel *model,
+ NautilusListView *view)
+{
+ return gtk_widget_get_scale_factor (GTK_WIDGET (view->details->tree_view));
+}
+
+static void
+on_longpress_gesture_pressed_event (GtkGestureLongPress *gesture,
+ gdouble x,
+ gdouble y,
+ gpointer user_data)
+{
+ GdkEventSequence *event_sequence;
+ const GdkEvent *event;
+ NautilusListView *view = user_data;
+ g_autolist (NautilusFile) selection = NULL;
+
+ event_sequence = gtk_gesture_get_last_updated_sequence (GTK_GESTURE (gesture));
+ if (event_sequence == NULL)
+ {
+ return;
+ }
+
+ event = gtk_gesture_get_last_event (GTK_GESTURE (gesture), event_sequence);
+
+ selection = nautilus_view_get_selection (NAUTILUS_VIEW (view));
+ if (selection != NULL)
+ {
+ nautilus_files_view_pop_up_selection_context_menu (NAUTILUS_FILES_VIEW (view), event);
+ }
+ else
+ {
+ nautilus_files_view_pop_up_background_context_menu (NAUTILUS_FILES_VIEW (view), event);
+ }
+}
+
+static void
+on_tree_view_drag_gesture_drag_begin (GtkGestureDrag *gesture,
+ gdouble start_x,
+ gdouble start_y,
+ gpointer user_data)
+{
+ nautilus_list_view_dnd_init (NAUTILUS_LIST_VIEW (user_data));
+}
+
+static void
+on_tree_view_drag_gesture_drag_update (GtkGestureDrag *gesture,
+ gdouble offset_x,
+ gdouble offset_y,
+ gpointer user_data)
+{
+ GdkEventSequence *sequence;
+ const GdkEvent *event;
+ NautilusListView *list_view;
+
+ sequence = gtk_gesture_single_get_current_sequence (GTK_GESTURE_SINGLE (gesture));
+ event = gtk_gesture_get_last_event (GTK_GESTURE (gesture), sequence);
+ list_view = NAUTILUS_LIST_VIEW (user_data);
+
+ nautilus_list_view_dnd_drag_begin (list_view, offset_x, offset_y, event);
+}
+
+static void
+list_view_use_tree_changed_callback (gpointer callback_data)
+{
+ GtkTreeView *tree_view;
+
+ tree_view = GTK_TREE_VIEW (callback_data);
+
+ gtk_tree_view_collapse_all (tree_view);
+}
+
+
+static void
+create_and_set_up_tree_view (NautilusListView *view)
+{
+ GtkCellRenderer *cell;
+ GtkTreeViewColumn *column;
+ AtkObject *atk_obj;
+ GList *nautilus_columns;
+ GList *l;
+ gchar **default_column_order, **default_visible_columns;
+ GtkWidget *content_widget;
+ GtkGesture *longpress_gesture;
+
+ content_widget = nautilus_files_view_get_content_widget (NAUTILUS_FILES_VIEW (view));
+ view->details->tree_view = GTK_TREE_VIEW (gtk_tree_view_new ());
+ view->details->columns = g_hash_table_new_full (g_str_hash,
+ g_str_equal,
+ (GDestroyNotify) g_free,
+ NULL);
+ gtk_tree_view_set_enable_search (view->details->tree_view, FALSE);
+
+ view->details->drag_dest =
+ nautilus_tree_view_drag_dest_new (view->details->tree_view);
+
+ /* Stop the tree view from performing select-all actions.
+ * It is desireable that the action is disabled while directory
+ * is loading.
+ */
+ g_signal_connect (view->details->tree_view, "select-all",
+ G_CALLBACK (g_signal_stop_emission_by_name), "select-all");
+
+ g_signal_connect_object (view->details->drag_dest,
+ "get-root-uri",
+ G_CALLBACK (get_root_uri_callback),
+ view, 0);
+ g_signal_connect_object (view->details->drag_dest,
+ "get-file-for-path",
+ G_CALLBACK (get_file_for_path_callback),
+ view, 0);
+ g_signal_connect_object (view->details->drag_dest,
+ "move-copy-items",
+ G_CALLBACK (move_copy_items_callback),
+ view, 0);
+ g_signal_connect_object (view->details->drag_dest, "handle-uri-list",
+ G_CALLBACK (list_view_handle_uri_list), view, 0);
+ g_signal_connect_object (view->details->drag_dest, "handle-text",
+ G_CALLBACK (list_view_handle_text), view, 0);
+ g_signal_connect_object (view->details->drag_dest, "handle-raw",
+ G_CALLBACK (list_view_handle_raw), view, 0);
+ g_signal_connect_object (view->details->drag_dest, "handle-hover",
+ G_CALLBACK (list_view_handle_hover), view, 0);
+
+ g_signal_connect_object (gtk_tree_view_get_selection (view->details->tree_view),
+ "changed",
+ G_CALLBACK (list_selection_changed_callback), view, 0);
+
+ view->details->tree_view_drag_gesture = gtk_gesture_drag_new (GTK_WIDGET (view->details->tree_view));
+
+ gtk_event_controller_set_propagation_phase (GTK_EVENT_CONTROLLER (view->details->tree_view_drag_gesture),
+ GTK_PHASE_CAPTURE);
+ gtk_gesture_single_set_button (GTK_GESTURE_SINGLE (view->details->tree_view_drag_gesture), 0);
+
+ g_signal_connect (view->details->tree_view_drag_gesture, "drag-begin",
+ G_CALLBACK (on_tree_view_drag_gesture_drag_begin), view);
+ g_signal_connect (view->details->tree_view_drag_gesture, "drag-update",
+ G_CALLBACK (on_tree_view_drag_gesture_drag_update), view);
+
+ view->details->tree_view_multi_press_gesture = gtk_gesture_multi_press_new (GTK_WIDGET (view->details->tree_view));
+
+ gtk_event_controller_set_propagation_phase (GTK_EVENT_CONTROLLER (view->details->tree_view_multi_press_gesture),
+ GTK_PHASE_CAPTURE);
+ gtk_gesture_single_set_button (GTK_GESTURE_SINGLE (view->details->tree_view_multi_press_gesture), 0);
+
+ g_signal_connect (view->details->tree_view_multi_press_gesture, "pressed",
+ G_CALLBACK (on_tree_view_multi_press_gesture_pressed), view);
+ g_signal_connect (view->details->tree_view_multi_press_gesture, "released",
+ G_CALLBACK (on_tree_view_multi_press_gesture_released), view);
+
+ g_signal_connect_object (view->details->tree_view, "event",
+ G_CALLBACK (on_event), view, 0);
+ g_signal_connect_object (view->details->tree_view, "test-expand-row",
+ G_CALLBACK (test_expand_row_callback), view, 0);
+ g_signal_connect_object (view->details->tree_view, "row-expanded",
+ G_CALLBACK (row_expanded_callback), view, 0);
+ g_signal_connect_object (view->details->tree_view, "row-collapsed",
+ G_CALLBACK (row_collapsed_callback), view, 0);
+ g_signal_connect_object (view->details->tree_view, "row-activated",
+ G_CALLBACK (row_activated_callback), view, 0);
+
+ g_signal_connect_object (nautilus_list_view_preferences,
+ "changed::" NAUTILUS_PREFERENCES_LIST_VIEW_USE_TREE,
+ G_CALLBACK (list_view_use_tree_changed_callback),
+ view->details->tree_view,
+ G_CONNECT_SWAPPED);
+
+ view->details->model = g_object_new (NAUTILUS_TYPE_LIST_MODEL, NULL);
+ gtk_tree_view_set_model (view->details->tree_view, GTK_TREE_MODEL (view->details->model));
+ /* Need the model for the dnd drop icon "accept" change */
+ nautilus_list_model_set_drag_view (NAUTILUS_LIST_MODEL (view->details->model),
+ view->details->tree_view, 0, 0);
+
+ g_signal_connect_object (view->details->model, "sort-column-changed",
+ G_CALLBACK (sort_column_changed_callback), view, 0);
+
+ g_signal_connect_object (view->details->model, "subdirectory-unloaded",
+ G_CALLBACK (subdirectory_unloaded_callback), view, 0);
+
+ g_signal_connect_object (view->details->model, "get-icon-scale",
+ G_CALLBACK (get_icon_scale_callback), view, 0);
+
+ longpress_gesture = gtk_gesture_long_press_new (GTK_WIDGET (content_widget));
+ gtk_event_controller_set_propagation_phase (GTK_EVENT_CONTROLLER (longpress_gesture),
+ GTK_PHASE_CAPTURE);
+ gtk_gesture_single_set_touch_only (GTK_GESTURE_SINGLE (longpress_gesture),
+ TRUE);
+ g_signal_connect (longpress_gesture,
+ "pressed",
+ (GCallback) on_longpress_gesture_pressed_event,
+ view);
+
+ gtk_tree_selection_set_mode (gtk_tree_view_get_selection (view->details->tree_view), GTK_SELECTION_MULTIPLE);
+
+ g_settings_bind (nautilus_list_view_preferences, NAUTILUS_PREFERENCES_LIST_VIEW_USE_TREE,
+ view->details->tree_view, "show-expanders",
+ G_SETTINGS_BIND_DEFAULT);
+
+ nautilus_columns = nautilus_get_all_columns ();
+
+ for (l = nautilus_columns; l != NULL; l = l->next)
+ {
+ NautilusColumn *nautilus_column;
+ int column_num;
+ char *name;
+ char *label;
+ float xalign;
+ GtkSortType sort_order;
+
+ nautilus_column = NAUTILUS_COLUMN (l->data);
+
+ g_object_get (nautilus_column,
+ "name", &name,
+ "label", &label,
+ "xalign", &xalign,
+ "default-sort-order", &sort_order,
+ NULL);
+
+ column_num = nautilus_list_model_add_column (view->details->model,
+ nautilus_column);
+
+ /* Created the name column specially, because it
+ * has the icon in it.*/
+ if (!strcmp (name, "name"))
+ {
+ /* Create the file name column */
+ view->details->file_name_column = gtk_tree_view_column_new ();
+ gtk_tree_view_append_column (view->details->tree_view,
+ view->details->file_name_column);
+ view->details->file_name_column_num = column_num;
+
+ g_hash_table_insert (view->details->columns,
+ g_strdup ("name"),
+ view->details->file_name_column);
+
+ g_signal_connect (gtk_tree_view_column_get_button (view->details->file_name_column),
+ "event",
+ G_CALLBACK (on_column_header_event),
+ view);
+
+ gtk_tree_view_set_search_column (view->details->tree_view, column_num);
+
+ gtk_tree_view_column_set_sort_column_id (view->details->file_name_column, column_num);
+ gtk_tree_view_column_set_title (view->details->file_name_column, _("Name"));
+ gtk_tree_view_column_set_resizable (view->details->file_name_column, TRUE);
+ gtk_tree_view_column_set_expand (view->details->file_name_column, TRUE);
+
+ /* Initial padding */
+ cell = gtk_cell_renderer_text_new ();
+ gtk_tree_view_column_pack_start (view->details->file_name_column, cell, FALSE);
+ g_object_set (cell, "xpad", 6, NULL);
+ g_settings_bind (nautilus_list_view_preferences, NAUTILUS_PREFERENCES_LIST_VIEW_USE_TREE,
+ cell, "visible",
+ G_SETTINGS_BIND_INVERT_BOOLEAN | G_SETTINGS_BIND_GET);
+
+ /* File icon */
+ cell = gtk_cell_renderer_pixbuf_new ();
+ view->details->pixbuf_cell = (GtkCellRendererPixbuf *) cell;
+ set_up_pixbuf_size (view);
+
+ gtk_tree_view_column_pack_start (view->details->file_name_column, cell, FALSE);
+ gtk_tree_view_column_set_attributes (view->details->file_name_column,
+ cell,
+ "surface", nautilus_list_model_get_column_id_from_zoom_level (view->details->zoom_level),
+ NULL);
+
+ cell = gtk_cell_renderer_text_new ();
+ view->details->file_name_cell = (GtkCellRendererText *) cell;
+ g_object_set (cell,
+ "ellipsize", PANGO_ELLIPSIZE_END,
+ "single-paragraph-mode", FALSE,
+ "width-chars", 30,
+ "xpad", 5,
+ NULL);
+
+ gtk_tree_view_column_pack_start (view->details->file_name_column, cell, TRUE);
+ gtk_tree_view_column_set_cell_data_func (view->details->file_name_column, cell,
+ (GtkTreeCellDataFunc) filename_cell_data_func,
+ view, NULL);
+ }
+ else
+ {
+ if (g_strcmp0 (name, "starred") == 0)
+ {
+ cell = gtk_cell_renderer_pixbuf_new ();
+ g_object_set (cell,
+ "mode", GTK_CELL_RENDERER_MODE_ACTIVATABLE,
+ NULL);
+
+ column = gtk_tree_view_column_new_with_attributes (label,
+ cell,
+ NULL);
+ }
+ else
+ {
+ /* We need to use libgd */
+ cell = gd_styled_text_renderer_new ();
+ /* FIXME: should be just dim-label.
+ * See https://bugzilla.gnome.org/show_bug.cgi?id=744397
+ */
+ gd_styled_text_renderer_add_class (GD_STYLED_TEXT_RENDERER (cell),
+ "nautilus-list-dim-label");
+
+ column = gtk_tree_view_column_new_with_attributes (label,
+ cell,
+ "text", column_num,
+ NULL);
+ }
+
+ gtk_tree_view_column_set_alignment (column, xalign);
+ g_object_set (cell,
+ "xalign", xalign,
+ "xpad", 5,
+ NULL);
+ if (!strcmp (name, "permissions"))
+ {
+ g_object_set (cell,
+ "family", "Monospace",
+ NULL);
+ }
+ view->details->cells = g_list_append (view->details->cells,
+ cell);
+
+ gtk_tree_view_append_column (view->details->tree_view, column);
+ gtk_tree_view_column_set_sort_column_id (column, column_num);
+ g_hash_table_insert (view->details->columns,
+ g_strdup (name),
+ column);
+
+ g_signal_connect (gtk_tree_view_column_get_button (column),
+ "event",
+ G_CALLBACK (on_column_header_event),
+ view);
+
+ gtk_tree_view_column_set_resizable (column, TRUE);
+ gtk_tree_view_column_set_sort_order (column, sort_order);
+
+ if (!strcmp (name, "where"))
+ {
+ gtk_tree_view_column_set_cell_data_func (column, cell,
+ (GtkTreeCellDataFunc) where_cell_data_func,
+ view, NULL);
+ }
+ else if (!strcmp (name, "trash_orig_path"))
+ {
+ gtk_tree_view_column_set_cell_data_func (column, cell,
+ (GtkTreeCellDataFunc) trash_orig_path_cell_data_func,
+ view, NULL);
+ }
+ else if (!strcmp (name, "starred"))
+ {
+ gtk_tree_view_column_set_cell_data_func (column, cell,
+ (GtkTreeCellDataFunc) starred_cell_data_func,
+ view, NULL);
+ }
+ }
+ g_free (name);
+ g_free (label);
+ }
+ nautilus_column_list_free (nautilus_columns);
+
+ 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);
+
+ /* 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 (view,
+ default_column_order,
+ default_visible_columns);
+
+ gtk_widget_show (GTK_WIDGET (view->details->tree_view));
+ gtk_container_add (GTK_CONTAINER (content_widget), GTK_WIDGET (view->details->tree_view));
+
+ atk_obj = gtk_widget_get_accessible (GTK_WIDGET (view->details->tree_view));
+ atk_object_set_name (atk_obj, _("List View"));
+
+ g_strfreev (default_visible_columns);
+ g_strfreev (default_column_order);
+}
+
+static void
+nautilus_list_view_add_files (NautilusFilesView *view,
+ GList *files)
+{
+ NautilusListModel *model;
+ GList *l;
+
+ model = NAUTILUS_LIST_VIEW (view)->details->model;
+ for (l = files; l != NULL; l = l->next)
+ {
+ NautilusFile *parent;
+ NautilusDirectory *directory;
+
+ parent = nautilus_file_get_parent (NAUTILUS_FILE (l->data));
+ directory = nautilus_directory_get_for_file (parent);
+ nautilus_list_model_add_file (model, NAUTILUS_FILE (l->data), directory);
+
+ nautilus_file_unref (parent);
+ nautilus_directory_unref (directory);
+ }
+}
+
+static char **
+get_default_visible_columns (NautilusListView *list_view)
+{
+ NautilusFile *file;
+ NautilusDirectory *directory;
+
+ file = nautilus_files_view_get_directory_as_file (NAUTILUS_FILES_VIEW (list_view));
+
+ if (nautilus_file_is_in_trash (file))
+ {
+ return g_strdupv ((gchar **) default_trash_visible_columns);
+ }
+
+ if (nautilus_file_is_in_recent (file))
+ {
+ return g_strdupv ((gchar **) default_recent_visible_columns);
+ }
+
+ directory = nautilus_files_view_get_model (NAUTILUS_FILES_VIEW (list_view));
+ if (NAUTILUS_IS_SEARCH_DIRECTORY (directory))
+ {
+ return g_strdupv ((gchar **) default_search_visible_columns);
+ }
+
+ return g_settings_get_strv (nautilus_list_view_preferences,
+ NAUTILUS_PREFERENCES_LIST_VIEW_DEFAULT_VISIBLE_COLUMNS);
+}
+
+static GList *
+default_column_array_as_list (gchar **array)
+{
+ GList *res = NULL;
+ gint i = 0;
+
+ while (array[i] != NULL)
+ {
+ res = g_list_prepend (res, array[i]);
+ i++;
+ }
+
+ return res;
+}
+
+static char **
+get_visible_columns (NautilusListView *list_view)
+{
+ NautilusFile *file;
+ g_autoptr (GList) visible_columns = NULL;
+ g_autoptr (GFile) location = NULL;
+ GPtrArray *res;
+ GList *l;
+ g_autofree gchar *uri = NULL;
+ gboolean can_star_current_directory;
+ gboolean is_starred;
+
+ file = nautilus_files_view_get_directory_as_file (NAUTILUS_FILES_VIEW (list_view));
+ uri = nautilus_file_get_uri (file);
+
+ location = g_file_new_for_uri (uri);
+ can_star_current_directory = nautilus_tag_manager_can_star_contents (list_view->details->tag_manager,
+ location);
+ is_starred = eel_uri_is_starred (uri);
+
+ visible_columns = nautilus_file_get_metadata_list (file,
+ NAUTILUS_METADATA_KEY_LIST_VIEW_VISIBLE_COLUMNS);
+ if (visible_columns == NULL)
+ {
+ visible_columns = default_column_array_as_list (get_default_visible_columns (list_view));
+ }
+
+ res = g_ptr_array_new ();
+ for (l = visible_columns; l != NULL; l = l->next)
+ {
+ if (g_strcmp0 (l->data, "starred") != 0 ||
+ (g_strcmp0 (l->data, "starred") == 0 && (can_star_current_directory || is_starred)))
+ {
+ g_ptr_array_add (res, l->data);
+ }
+ }
+
+ g_ptr_array_add (res, NULL);
+
+ return (char **) g_ptr_array_free (res, FALSE);
+}
+
+static char **
+get_default_column_order (NautilusListView *list_view)
+{
+ NautilusFile *file;
+ NautilusDirectory *directory;
+
+ file = nautilus_files_view_get_directory_as_file (NAUTILUS_FILES_VIEW (list_view));
+
+ if (nautilus_file_is_in_trash (file))
+ {
+ return g_strdupv ((gchar **) default_trash_columns_order);
+ }
+
+ if (nautilus_file_is_in_recent (file))
+ {
+ return g_strdupv ((gchar **) default_recent_columns_order);
+ }
+
+ directory = nautilus_files_view_get_model (NAUTILUS_FILES_VIEW (list_view));
+ if (NAUTILUS_IS_SEARCH_DIRECTORY (directory))
+ {
+ return g_strdupv ((gchar **) default_search_columns_order);
+ }
+
+ return g_settings_get_strv (nautilus_list_view_preferences,
+ NAUTILUS_PREFERENCES_LIST_VIEW_DEFAULT_COLUMN_ORDER);
+}
+
+static char **
+get_column_order (NautilusListView *list_view)
+{
+ NautilusFile *file;
+ GList *column_order;
+
+ file = nautilus_files_view_get_directory_as_file (NAUTILUS_FILES_VIEW (list_view));
+
+ column_order = nautilus_file_get_metadata_list
+ (file,
+ NAUTILUS_METADATA_KEY_LIST_VIEW_COLUMN_ORDER);
+
+ if (column_order)
+ {
+ GPtrArray *res;
+ GList *l;
+
+ res = g_ptr_array_new ();
+ for (l = column_order; l != NULL; l = l->next)
+ {
+ g_ptr_array_add (res, l->data);
+ }
+ g_ptr_array_add (res, NULL);
+
+ g_list_free (column_order);
+
+ return (char **) g_ptr_array_free (res, FALSE);
+ }
+
+ return get_default_column_order (list_view);
+}
+
+static void
+check_allow_sort (NautilusListView *list_view)
+{
+ GList *column_names;
+ GList *l;
+ NautilusFile *file;
+ GtkTreeViewColumn *column;
+ gboolean allow_sorting;
+ int sort_column_id;
+
+ column_names = g_hash_table_get_keys (list_view->details->columns);
+ file = nautilus_files_view_get_directory_as_file (NAUTILUS_FILES_VIEW (list_view));
+ allow_sorting = !(nautilus_file_is_in_recent (file) || nautilus_file_is_in_search (file));
+
+ for (l = column_names; l != NULL; l = l->next)
+ {
+ column = g_hash_table_lookup (list_view->details->columns, l->data);
+ if (allow_sorting)
+ {
+ sort_column_id = nautilus_list_model_get_sort_column_id_from_attribute (list_view->details->model,
+ g_quark_from_string (l->data));
+ /* Restore its original sorting id. We rely on that the keys of the hashmap
+ * use the same string than the sort criterias */
+ gtk_tree_view_column_set_sort_column_id (column, sort_column_id);
+ }
+ else
+ {
+ /* This disables the header and any sorting capability (like shortcuts),
+ * but leaving them interactionable so the user can still resize them */
+ gtk_tree_view_column_set_sort_column_id (column, -1);
+ }
+ }
+
+ g_list_free (column_names);
+}
+
+static void
+set_columns_settings_from_metadata_and_preferences (NautilusListView *list_view)
+{
+ char **column_order;
+ char **visible_columns;
+
+ column_order = get_column_order (list_view);
+ visible_columns = get_visible_columns (list_view);
+
+ apply_columns_settings (list_view, column_order, visible_columns);
+
+ g_strfreev (column_order);
+ g_strfreev (visible_columns);
+}
+
+static void
+set_sort_order_from_metadata_and_preferences (NautilusListView *list_view)
+{
+ char *sort_attribute;
+ int sort_column_id;
+ NautilusFile *file;
+ gboolean sort_reversed, default_sort_reversed;
+ const gchar *default_sort_order;
+
+ file = nautilus_files_view_get_directory_as_file (NAUTILUS_FILES_VIEW (list_view));
+ default_sort_order = get_default_sort_order (file, &default_sort_reversed);
+ if (!(nautilus_file_is_in_recent (file) || nautilus_file_is_in_search (file)))
+ {
+ sort_attribute = nautilus_file_get_metadata (file,
+ NAUTILUS_METADATA_KEY_LIST_VIEW_SORT_COLUMN,
+ NULL);
+ sort_column_id = nautilus_list_model_get_sort_column_id_from_attribute (list_view->details->model,
+ g_quark_from_string (sort_attribute));
+ g_free (sort_attribute);
+
+ if (sort_column_id == -1)
+ {
+ sort_column_id =
+ nautilus_list_model_get_sort_column_id_from_attribute (list_view->details->model,
+ g_quark_from_string (default_sort_order));
+ }
+
+ sort_reversed = nautilus_file_get_boolean_metadata (file,
+ NAUTILUS_METADATA_KEY_LIST_VIEW_SORT_REVERSED,
+ default_sort_reversed);
+ }
+ else
+ {
+ /* Make sure we use the default one and not one that the user used previously
+ * of the change to not allow sorting on search and recent, or the
+ * case that the user or some app modified directly the metadata */
+ sort_column_id = nautilus_list_model_get_sort_column_id_from_attribute (list_view->details->model,
+ g_quark_from_string (default_sort_order));
+ sort_reversed = default_sort_reversed;
+ }
+ gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (list_view->details->model),
+ sort_column_id,
+ sort_reversed ? GTK_SORT_DESCENDING : GTK_SORT_ASCENDING);
+}
+
+static NautilusListZoomLevel
+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);
+
+ if (default_zoom_level < NAUTILUS_LIST_ZOOM_LEVEL_SMALL
+ || default_zoom_level > NAUTILUS_LIST_ZOOM_LEVEL_LARGER)
+ {
+ default_zoom_level = NAUTILUS_LIST_ZOOM_LEVEL_STANDARD;
+ }
+
+ return default_zoom_level;
+}
+
+static void
+nautilus_list_view_begin_loading (NautilusFilesView *view)
+{
+ NautilusListView *list_view;
+
+ list_view = NAUTILUS_LIST_VIEW (view);
+
+ nautilus_list_view_sort_directories_first_changed (NAUTILUS_FILES_VIEW (list_view));
+ set_sort_order_from_metadata_and_preferences (list_view);
+ set_columns_settings_from_metadata_and_preferences (list_view);
+ check_allow_sort (list_view);
+}
+
+static void
+nautilus_list_view_clear (NautilusFilesView *view)
+{
+ NautilusListView *list_view;
+ GtkTreeView *tree_view;
+ GtkTreeSelection *tree_selection;
+ GtkTreePath *path;
+
+ list_view = NAUTILUS_LIST_VIEW (view);
+
+ if (list_view->details->model != NULL)
+ {
+ tree_view = list_view->details->tree_view;
+
+ /* When the current cursor's row gets deleted, GTK will move the cursor to
+ * the next row, and when setting the cursor it also selects the new
+ * cursor's row, thereby triggering selection signals. The new cursor will
+ * soon be deleted again and the loop repeats.
+ *
+ * Since clear() removes all entries, those selections are useless but they
+ * take up most of the time in clear(). For example, when a search returns
+ * a large list, exiting from the search view would make nautilus hang.
+ *
+ * At the time the code is written simply removing the cursor solves the
+ * problem, but to be future-proof in case GTK does anything fancy with
+ * the current selection, we also remove the selection.
+ *
+ * Because GTK internally seeking the cursor takes time, only blocking the
+ * selection signal like everywhere else will not remove that overhead.
+ */
+
+ /* Clear the current selection */
+ tree_selection = gtk_tree_view_get_selection (tree_view);
+ gtk_tree_selection_unselect_all (tree_selection);
+
+ /* Clear the current cursor */
+ path = gtk_tree_path_new ();
+ gtk_tree_view_set_cursor (tree_view, path, NULL, FALSE);
+ gtk_tree_path_free (path);
+
+ nautilus_list_model_clear (list_view->details->model);
+ }
+}
+
+static void
+nautilus_list_view_file_changed (NautilusFilesView *view,
+ NautilusFile *file,
+ NautilusDirectory *directory)
+{
+ NautilusListView *listview;
+
+ listview = NAUTILUS_LIST_VIEW (view);
+
+ nautilus_list_model_file_changed (listview->details->model, file, directory);
+}
+
+typedef struct
+{
+ GtkTreePath *path;
+ gboolean is_common;
+ gboolean is_root;
+} HasCommonParentData;
+
+static void
+tree_selection_has_common_parent_foreach_func (GtkTreeModel *model,
+ GtkTreePath *path,
+ GtkTreeIter *iter,
+ gpointer user_data)
+{
+ HasCommonParentData *data;
+ GtkTreePath *parent_path;
+ gboolean has_parent;
+
+ data = (HasCommonParentData *) user_data;
+
+ parent_path = gtk_tree_path_copy (path);
+ gtk_tree_path_up (parent_path);
+
+ has_parent = (gtk_tree_path_get_depth (parent_path) > 0) ? TRUE : FALSE;
+
+ if (!has_parent)
+ {
+ data->is_root = TRUE;
+ }
+
+ if (data->is_common && !data->is_root)
+ {
+ if (data->path == NULL)
+ {
+ data->path = gtk_tree_path_copy (parent_path);
+ }
+ else if (gtk_tree_path_compare (data->path, parent_path) != 0)
+ {
+ data->is_common = FALSE;
+ }
+ }
+
+ gtk_tree_path_free (parent_path);
+}
+
+static void
+tree_selection_has_common_parent (GtkTreeSelection *selection,
+ gboolean *is_common,
+ gboolean *is_root)
+{
+ HasCommonParentData data;
+
+ g_assert (is_common != NULL);
+ g_assert (is_root != NULL);
+
+ data.path = NULL;
+ data.is_common = *is_common = TRUE;
+ data.is_root = *is_root = FALSE;
+
+ gtk_tree_selection_selected_foreach (selection,
+ tree_selection_has_common_parent_foreach_func,
+ &data);
+
+ *is_common = data.is_common;
+ *is_root = data.is_root;
+
+ if (data.path != NULL)
+ {
+ gtk_tree_path_free (data.path);
+ }
+}
+
+static char *
+nautilus_list_view_get_backing_uri (NautilusFilesView *view)
+{
+ NautilusListView *list_view;
+ NautilusListModel *list_model;
+ NautilusFile *file;
+ GtkTreeView *tree_view;
+ GtkTreeSelection *selection;
+ GtkTreePath *path;
+ GList *paths;
+ guint length;
+ char *uri;
+
+ g_return_val_if_fail (NAUTILUS_IS_LIST_VIEW (view), NULL);
+
+ list_view = NAUTILUS_LIST_VIEW (view);
+ list_model = list_view->details->model;
+ tree_view = list_view->details->tree_view;
+
+ g_assert (list_model);
+
+ /* We currently handle three common cases here:
+ * (a) if the selection contains non-filesystem items (i.e., the
+ * "(Empty)" label), we return the uri of the parent.
+ * (b) if the selection consists of exactly one _expanded_ directory, we
+ * return its URI.
+ * (c) if the selection consists of either exactly one item which is not
+ * an expanded directory) or multiple items in the same directory,
+ * we return the URI of the common parent.
+ */
+
+ uri = NULL;
+
+ selection = gtk_tree_view_get_selection (tree_view);
+ length = gtk_tree_selection_count_selected_rows (selection);
+
+ if (length == 1)
+ {
+ paths = gtk_tree_selection_get_selected_rows (selection, NULL);
+ path = (GtkTreePath *) paths->data;
+
+ file = nautilus_list_model_file_for_path (list_model, path);
+ if (file == NULL)
+ {
+ /* The selected item is a label, not a file */
+ gtk_tree_path_up (path);
+ file = nautilus_list_model_file_for_path (list_model, path);
+ }
+
+ if (file != NULL)
+ {
+ if (nautilus_file_is_directory (file) &&
+ gtk_tree_view_row_expanded (tree_view, path))
+ {
+ uri = nautilus_file_get_uri (file);
+ }
+ nautilus_file_unref (file);
+ }
+
+ gtk_tree_path_free (path);
+ g_list_free (paths);
+ }
+
+ if (uri == NULL && length > 0)
+ {
+ gboolean is_common, is_root;
+
+ /* Check that all the selected items belong to the same
+ * directory and that directory is not the root directory (which
+ * is handled by NautilusFilesView::get_backing_directory.) */
+
+ tree_selection_has_common_parent (selection, &is_common, &is_root);
+
+ if (is_common && !is_root)
+ {
+ paths = gtk_tree_selection_get_selected_rows (selection, NULL);
+ path = (GtkTreePath *) paths->data;
+
+ file = nautilus_list_model_file_for_path (list_model, path);
+ g_assert (file != NULL);
+ uri = nautilus_file_get_parent_uri (file);
+ nautilus_file_unref (file);
+
+ g_list_free_full (paths, (GDestroyNotify) gtk_tree_path_free);
+ }
+ }
+
+ if (uri != NULL)
+ {
+ return uri;
+ }
+
+ return NAUTILUS_FILES_VIEW_CLASS (nautilus_list_view_parent_class)->get_backing_uri (view);
+}
+
+static void
+nautilus_list_view_get_selection_foreach_func (GtkTreeModel *model,
+ GtkTreePath *path,
+ GtkTreeIter *iter,
+ gpointer data)
+{
+ GList **list;
+ NautilusFile *file;
+
+ list = data;
+
+ gtk_tree_model_get (model, iter,
+ NAUTILUS_LIST_MODEL_FILE_COLUMN, &file,
+ -1);
+
+ if (file != NULL)
+ {
+ (*list) = g_list_prepend ((*list), file);
+ }
+}
+
+static GList *
+nautilus_list_view_get_selection (NautilusFilesView *view)
+{
+ GList *list;
+
+ list = NULL;
+
+ gtk_tree_selection_selected_foreach (gtk_tree_view_get_selection (NAUTILUS_LIST_VIEW (view)->details->tree_view),
+ nautilus_list_view_get_selection_foreach_func, &list);
+
+ return g_list_reverse (list);
+}
+
+static void
+nautilus_list_view_get_selection_for_file_transfer_foreach_func (GtkTreeModel *model,
+ GtkTreePath *path,
+ GtkTreeIter *iter,
+ gpointer data)
+{
+ NautilusFile *file;
+ struct SelectionForeachData *selection_data;
+ GtkTreeIter parent, child;
+
+ selection_data = data;
+
+ gtk_tree_model_get (model, iter,
+ NAUTILUS_LIST_MODEL_FILE_COLUMN, &file,
+ -1);
+
+ if (file != NULL)
+ {
+ /* If the parent folder is also selected, don't include this file in the
+ * file operation, since that would copy it to the toplevel target instead
+ * of keeping it as a child of the copied folder
+ */
+ child = *iter;
+ while (gtk_tree_model_iter_parent (model, &parent, &child))
+ {
+ if (gtk_tree_selection_iter_is_selected (selection_data->selection,
+ &parent))
+ {
+ return;
+ }
+ child = parent;
+ }
+
+ nautilus_file_ref (file);
+ selection_data->list = g_list_prepend (selection_data->list, file);
+ }
+}
+
+
+static GList *
+nautilus_list_view_get_selection_for_file_transfer (NautilusFilesView *view)
+{
+ struct SelectionForeachData selection_data;
+
+ selection_data.list = NULL;
+ selection_data.selection = gtk_tree_view_get_selection (NAUTILUS_LIST_VIEW (view)->details->tree_view);
+
+ gtk_tree_selection_selected_foreach (selection_data.selection,
+ nautilus_list_view_get_selection_for_file_transfer_foreach_func, &selection_data);
+
+ return g_list_reverse (selection_data.list);
+}
+
+static gboolean
+nautilus_list_view_is_empty (NautilusFilesView *view)
+{
+ return nautilus_list_model_is_empty (NAUTILUS_LIST_VIEW (view)->details->model);
+}
+
+static void
+nautilus_list_view_end_file_changes (NautilusFilesView *view)
+{
+ NautilusListView *list_view;
+
+ list_view = NAUTILUS_LIST_VIEW (view);
+
+ if (list_view->details->new_selection_path)
+ {
+ gtk_tree_view_set_cursor (list_view->details->tree_view,
+ list_view->details->new_selection_path,
+ NULL, FALSE);
+ gtk_tree_path_free (list_view->details->new_selection_path);
+ list_view->details->new_selection_path = NULL;
+ }
+}
+
+static void
+nautilus_list_view_remove_file (NautilusFilesView *view,
+ NautilusFile *file,
+ NautilusDirectory *directory)
+{
+ GtkTreePath *path;
+ GtkTreePath *file_path;
+ GtkTreeIter iter;
+ GtkTreeIter temp_iter;
+ GtkTreeRowReference *row_reference;
+ NautilusListView *list_view;
+ GtkTreeModel *tree_model;
+ GtkTreeSelection *selection;
+
+ path = NULL;
+ row_reference = NULL;
+ list_view = NAUTILUS_LIST_VIEW (view);
+ tree_model = GTK_TREE_MODEL (list_view->details->model);
+
+ if (nautilus_list_model_get_tree_iter_from_file (list_view->details->model, file, directory, &iter))
+ {
+ selection = gtk_tree_view_get_selection (list_view->details->tree_view);
+ file_path = gtk_tree_model_get_path (tree_model, &iter);
+
+ if (gtk_tree_selection_path_is_selected (selection, file_path))
+ {
+ /* get reference for next element in the list view. If the element to be deleted is the
+ * last one, get reference to previous element. If there is only one element in view
+ * no need to select anything.
+ */
+ temp_iter = iter;
+
+ if (gtk_tree_model_iter_next (tree_model, &iter))
+ {
+ path = gtk_tree_model_get_path (tree_model, &iter);
+ row_reference = gtk_tree_row_reference_new (tree_model, path);
+ }
+ else
+ {
+ path = gtk_tree_model_get_path (tree_model, &temp_iter);
+ if (gtk_tree_path_prev (path))
+ {
+ row_reference = gtk_tree_row_reference_new (tree_model, path);
+ }
+ }
+ gtk_tree_path_free (path);
+ }
+
+ gtk_tree_path_free (file_path);
+
+ nautilus_list_model_remove_file (list_view->details->model, file, directory);
+
+ if (gtk_tree_row_reference_valid (row_reference))
+ {
+ if (list_view->details->new_selection_path)
+ {
+ gtk_tree_path_free (list_view->details->new_selection_path);
+ }
+ list_view->details->new_selection_path = gtk_tree_row_reference_get_path (row_reference);
+ }
+
+ if (row_reference)
+ {
+ gtk_tree_row_reference_free (row_reference);
+ }
+ }
+}
+
+static void
+nautilus_list_view_set_selection (NautilusFilesView *view,
+ GList *selection)
+{
+ NautilusListView *list_view;
+ NautilusListModel *model;
+ GtkTreeView *tree_view;
+ GtkTreeSelection *tree_selection;
+ GList *node;
+ gboolean cursor_is_set_on_selection = FALSE;
+ GList *iters, *l;
+ NautilusFile *file;
+
+ list_view = NAUTILUS_LIST_VIEW (view);
+ model = list_view->details->model;
+ tree_view = list_view->details->tree_view;
+ tree_selection = gtk_tree_view_get_selection (tree_view);
+
+ g_signal_handlers_block_by_func (tree_selection, list_selection_changed_callback, view);
+
+ gtk_tree_selection_unselect_all (tree_selection);
+ for (node = selection; node != NULL; node = node->next)
+ {
+ file = node->data;
+ iters = nautilus_list_model_get_all_iters_for_file (model, file);
+
+ for (l = iters; l != NULL; l = l->next)
+ {
+ if (!cursor_is_set_on_selection)
+ {
+ GtkTreePath *path;
+
+ path = gtk_tree_model_get_path (GTK_TREE_MODEL (model),
+ (GtkTreeIter *) l->data);
+ gtk_tree_view_set_cursor (tree_view, path, NULL, FALSE);
+ gtk_tree_path_free (path);
+
+ cursor_is_set_on_selection = TRUE;
+ continue;
+ }
+
+ gtk_tree_selection_select_iter (tree_selection,
+ (GtkTreeIter *) l->data);
+ }
+ g_list_free_full (iters, g_free);
+ }
+
+ g_signal_handlers_unblock_by_func (tree_selection, list_selection_changed_callback, view);
+ nautilus_files_view_notify_selection_changed (view);
+}
+
+static void
+nautilus_list_view_invert_selection (NautilusFilesView *view)
+{
+ NautilusListView *list_view;
+ GtkTreeSelection *tree_selection;
+ GList *node;
+ GList *iters, *l;
+ NautilusFile *file;
+ GList *selection = NULL;
+
+ list_view = NAUTILUS_LIST_VIEW (view);
+ tree_selection = gtk_tree_view_get_selection (list_view->details->tree_view);
+
+ g_signal_handlers_block_by_func (tree_selection, list_selection_changed_callback, view);
+
+ gtk_tree_selection_selected_foreach (tree_selection,
+ nautilus_list_view_get_selection_foreach_func, &selection);
+
+ gtk_tree_selection_select_all (tree_selection);
+
+ for (node = selection; node != NULL; node = node->next)
+ {
+ file = node->data;
+ iters = nautilus_list_model_get_all_iters_for_file (list_view->details->model, file);
+
+ for (l = iters; l != NULL; l = l->next)
+ {
+ gtk_tree_selection_unselect_iter (tree_selection,
+ (GtkTreeIter *) l->data);
+ }
+ g_list_free_full (iters, g_free);
+ }
+
+ g_list_free (selection);
+
+ g_signal_handlers_unblock_by_func (tree_selection, list_selection_changed_callback, view);
+ nautilus_files_view_notify_selection_changed (view);
+}
+
+static void
+nautilus_list_view_select_all (NautilusFilesView *view)
+{
+ gtk_tree_selection_select_all (gtk_tree_view_get_selection (NAUTILUS_LIST_VIEW (view)->details->tree_view));
+}
+
+static void
+nautilus_list_view_select_first (NautilusFilesView *view)
+{
+ GtkTreeSelection *selection;
+ GtkTreeIter iter;
+
+ if (!gtk_tree_model_get_iter_first (GTK_TREE_MODEL (NAUTILUS_LIST_VIEW (view)->details->model), &iter))
+ {
+ return;
+ }
+ selection = gtk_tree_view_get_selection (NAUTILUS_LIST_VIEW (view)->details->tree_view);
+ gtk_tree_selection_unselect_all (selection);
+ gtk_tree_selection_select_iter (selection, &iter);
+}
+
+static void
+nautilus_list_view_zoom_to_level (NautilusFilesView *view,
+ gint zoom_level)
+{
+ NautilusListView *list_view;
+
+ g_return_if_fail (NAUTILUS_IS_LIST_VIEW (view));
+
+ list_view = NAUTILUS_LIST_VIEW (view);
+
+ if (list_view->details->zoom_level == zoom_level)
+ {
+ return;
+ }
+
+ nautilus_list_view_set_zoom_level (list_view, zoom_level);
+ g_action_group_change_action_state (nautilus_files_view_get_action_group (view),
+ "zoom-to-level", g_variant_new_int32 (zoom_level));
+
+ nautilus_files_view_update_toolbar_menus (view);
+}
+
+static void
+action_zoom_to_level (GSimpleAction *action,
+ GVariant *state,
+ gpointer user_data)
+{
+ NautilusFilesView *view;
+ NautilusListZoomLevel zoom_level;
+
+ g_assert (NAUTILUS_IS_FILES_VIEW (user_data));
+
+ view = NAUTILUS_FILES_VIEW (user_data);
+ zoom_level = g_variant_get_int32 (state);
+ nautilus_list_view_zoom_to_level (view, 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
+column_editor_response_callback (GtkWidget *dialog,
+ int response_id,
+ gpointer user_data)
+{
+ gtk_widget_destroy (GTK_WIDGET (dialog));
+}
+
+static void
+column_chooser_changed_callback (NautilusColumnChooser *chooser,
+ NautilusListView *view)
+{
+ NautilusFile *file;
+ char **visible_columns;
+ char **column_order;
+ GList *list;
+ int i;
+
+ file = nautilus_files_view_get_directory_as_file (NAUTILUS_FILES_VIEW (view));
+
+ nautilus_column_chooser_get_settings (chooser,
+ &visible_columns,
+ &column_order);
+
+ list = NULL;
+ for (i = 0; visible_columns[i] != NULL; ++i)
+ {
+ list = g_list_prepend (list, visible_columns[i]);
+ }
+ list = g_list_reverse (list);
+ nautilus_file_set_metadata_list (file,
+ NAUTILUS_METADATA_KEY_LIST_VIEW_VISIBLE_COLUMNS,
+ list);
+ g_list_free (list);
+
+ list = NULL;
+ for (i = 0; column_order[i] != NULL; ++i)
+ {
+ list = g_list_prepend (list, column_order[i]);
+ }
+ list = g_list_reverse (list);
+ nautilus_file_set_metadata_list (file,
+ NAUTILUS_METADATA_KEY_LIST_VIEW_COLUMN_ORDER,
+ list);
+ g_list_free (list);
+
+ 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)
+{
+ GtkWidget *window;
+ GtkWidget *label;
+ GtkWidget *box;
+ GtkWidget *column_chooser;
+ NautilusFile *file;
+ char *str;
+ char *name;
+ const char *label_text;
+
+ file = nautilus_files_view_get_directory_as_file (NAUTILUS_FILES_VIEW (view));
+ name = nautilus_file_get_display_name (file);
+ str = g_strdup_printf (_("%s Visible Columns"), name);
+ g_free (name);
+
+ window = gtk_dialog_new_with_buttons (str,
+ GTK_WINDOW (gtk_widget_get_toplevel (GTK_WIDGET (view))),
+ GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT | GTK_DIALOG_USE_HEADER_BAR,
+ NULL, NULL);
+ g_free (str);
+ g_signal_connect (window, "response",
+ G_CALLBACK (column_editor_response_callback), NULL);
+
+ gtk_window_set_default_size (GTK_WINDOW (window), 300, 400);
+
+ box = gtk_box_new (GTK_ORIENTATION_VERTICAL, 12);
+ gtk_container_set_border_width (GTK_CONTAINER (box), 12);
+ gtk_widget_set_hexpand (box, TRUE);
+ gtk_widget_show (box);
+ gtk_box_pack_start (GTK_BOX (gtk_dialog_get_content_area (GTK_DIALOG (window))), box,
+ TRUE, TRUE, 0);
+
+ label_text = _("Choose the order of information to appear in this folder:");
+ str = g_strconcat ("<b>", label_text, "</b>", NULL);
+ label = gtk_label_new (NULL);
+ gtk_label_set_markup (GTK_LABEL (label), str);
+ gtk_label_set_line_wrap (GTK_LABEL (label), FALSE);
+ gtk_label_set_xalign (GTK_LABEL (label), 0);
+ gtk_label_set_yalign (GTK_LABEL (label), 0);
+ gtk_widget_show (label);
+ gtk_box_pack_start (GTK_BOX (box), label, FALSE, FALSE, 0);
+
+ g_free (str);
+
+ column_chooser = nautilus_column_chooser_new (file);
+ gtk_widget_show (column_chooser);
+ gtk_box_pack_start (GTK_BOX (box), column_chooser, TRUE, TRUE, 0);
+
+ 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 *list_view;
+
+ list_view = NAUTILUS_LIST_VIEW (user_data);
+
+ if (list_view->details->column_editor)
+ {
+ gtk_window_present (GTK_WINDOW (list_view->details->column_editor));
+ }
+ else
+ {
+ list_view->details->column_editor = create_column_editor (list_view);
+ g_object_add_weak_pointer (G_OBJECT (list_view->details->column_editor),
+ (gpointer *) &list_view->details->column_editor);
+
+ gtk_widget_show (list_view->details->column_editor);
+ }
+}
+
+const GActionEntry list_view_entries[] =
+{
+ { "visible-columns", action_visible_columns },
+ { "zoom-to-level", NULL, NULL, "1", action_zoom_to_level }
+};
+
+static void
+nautilus_list_view_set_zoom_level (NautilusListView *view,
+ NautilusListZoomLevel new_level)
+{
+ int column;
+
+ g_return_if_fail (NAUTILUS_IS_LIST_VIEW (view));
+ g_return_if_fail (new_level >= NAUTILUS_LIST_ZOOM_LEVEL_SMALL &&
+ new_level <= NAUTILUS_LIST_ZOOM_LEVEL_LARGER);
+
+ if (view->details->zoom_level == new_level)
+ {
+ return;
+ }
+
+ view->details->zoom_level = new_level;
+
+ /* Select correctly scaled icons. */
+ column = nautilus_list_model_get_column_id_from_zoom_level (new_level);
+ gtk_tree_view_column_set_attributes (view->details->file_name_column,
+ GTK_CELL_RENDERER (view->details->pixbuf_cell),
+ "surface", column,
+ NULL);
+ set_up_pixbuf_size (view);
+}
+
+static void
+nautilus_list_view_bump_zoom_level (NautilusFilesView *view,
+ int zoom_increment)
+{
+ NautilusListView *list_view;
+ gint new_level;
+
+ g_return_if_fail (NAUTILUS_IS_LIST_VIEW (view));
+
+ list_view = NAUTILUS_LIST_VIEW (view);
+ new_level = list_view->details->zoom_level + zoom_increment;
+
+ if (new_level >= NAUTILUS_LIST_ZOOM_LEVEL_SMALL &&
+ new_level <= NAUTILUS_LIST_ZOOM_LEVEL_LARGER)
+ {
+ nautilus_list_view_zoom_to_level (view, new_level);
+ }
+}
+
+static void
+nautilus_list_view_restore_standard_zoom_level (NautilusFilesView *view)
+{
+ nautilus_list_view_zoom_to_level (view, NAUTILUS_LIST_ZOOM_LEVEL_STANDARD);
+}
+
+static gboolean
+nautilus_list_view_can_zoom_in (NautilusFilesView *view)
+{
+ g_return_val_if_fail (NAUTILUS_IS_LIST_VIEW (view), FALSE);
+
+ return NAUTILUS_LIST_VIEW (view)->details->zoom_level < NAUTILUS_LIST_ZOOM_LEVEL_LARGER;
+}
+
+static gboolean
+nautilus_list_view_can_zoom_out (NautilusFilesView *view)
+{
+ g_return_val_if_fail (NAUTILUS_IS_LIST_VIEW (view), FALSE);
+
+ return NAUTILUS_LIST_VIEW (view)->details->zoom_level > NAUTILUS_LIST_ZOOM_LEVEL_SMALL;
+}
+
+static gfloat
+nautilus_list_view_get_zoom_level_percentage (NautilusFilesView *view)
+{
+ NautilusListView *list_view;
+ guint icon_size;
+
+ g_return_val_if_fail (NAUTILUS_IS_LIST_VIEW (view), 1.0);
+
+ list_view = NAUTILUS_LIST_VIEW (view);
+ icon_size = nautilus_list_model_get_icon_size_for_zoom_level (list_view->details->zoom_level);
+
+ return (gfloat) icon_size / NAUTILUS_LIST_ICON_SIZE_STANDARD;
+}
+
+static gboolean
+nautilus_list_view_is_zoom_level_default (NautilusFilesView *view)
+{
+ NautilusListView *list_view;
+ guint icon_size;
+
+ list_view = NAUTILUS_LIST_VIEW (view);
+ icon_size = nautilus_list_model_get_icon_size_for_zoom_level (list_view->details->zoom_level);
+
+ return icon_size == NAUTILUS_LIST_ICON_SIZE_STANDARD;
+}
+
+static void
+nautilus_list_view_click_policy_changed (NautilusFilesView *directory_view)
+{
+ GdkWindow *win;
+ GdkDisplay *display;
+ NautilusListView *view;
+ GtkTreeIter iter;
+ GtkTreeView *tree;
+
+ view = NAUTILUS_LIST_VIEW (directory_view);
+ display = gtk_widget_get_display (GTK_WIDGET (view));
+
+ /* ensure that we unset the hand cursor and refresh underlined rows */
+ if (get_click_policy () == NAUTILUS_CLICK_POLICY_DOUBLE)
+ {
+ if (view->details->hover_path != NULL)
+ {
+ if (gtk_tree_model_get_iter (GTK_TREE_MODEL (view->details->model),
+ &iter, view->details->hover_path))
+ {
+ gtk_tree_model_row_changed (GTK_TREE_MODEL (view->details->model),
+ view->details->hover_path, &iter);
+ }
+
+ gtk_tree_path_free (view->details->hover_path);
+ view->details->hover_path = NULL;
+ }
+
+ tree = view->details->tree_view;
+ if (gtk_widget_get_realized (GTK_WIDGET (tree)))
+ {
+ win = gtk_widget_get_window (GTK_WIDGET (tree));
+ gdk_window_set_cursor (win, NULL);
+
+ if (display != NULL)
+ {
+ gdk_display_flush (display);
+ }
+ }
+
+ g_clear_object (&hand_cursor);
+ }
+ else if (get_click_policy () == NAUTILUS_CLICK_POLICY_SINGLE)
+ {
+ if (hand_cursor == NULL)
+ {
+ hand_cursor = gdk_cursor_new_for_display (display, GDK_HAND2);
+ }
+ }
+}
+
+static void
+default_sort_order_changed_callback (gpointer callback_data)
+{
+ NautilusListView *list_view;
+
+ list_view = NAUTILUS_LIST_VIEW (callback_data);
+
+ set_sort_order_from_metadata_and_preferences (list_view);
+}
+
+static void
+default_visible_columns_changed_callback (gpointer callback_data)
+{
+ NautilusListView *list_view;
+
+ list_view = NAUTILUS_LIST_VIEW (callback_data);
+
+ set_columns_settings_from_metadata_and_preferences (list_view);
+}
+
+static void
+default_column_order_changed_callback (gpointer callback_data)
+{
+ NautilusListView *list_view;
+
+ list_view = NAUTILUS_LIST_VIEW (callback_data);
+
+ set_columns_settings_from_metadata_and_preferences (list_view);
+}
+
+static void
+nautilus_list_view_sort_directories_first_changed (NautilusFilesView *view)
+{
+ NautilusListView *list_view;
+
+ list_view = NAUTILUS_LIST_VIEW (view);
+
+ nautilus_list_model_set_should_sort_directories_first (list_view->details->model,
+ nautilus_files_view_should_sort_directories_first (view));
+}
+
+static int
+nautilus_list_view_compare_files (NautilusFilesView *view,
+ NautilusFile *file1,
+ NautilusFile *file2)
+{
+ NautilusListView *list_view;
+
+ list_view = NAUTILUS_LIST_VIEW (view);
+ return nautilus_list_model_compare_func (list_view->details->model, file1, file2);
+}
+
+static void
+nautilus_list_view_dispose (GObject *object)
+{
+ NautilusListView *list_view;
+ GtkClipboard *clipboard;
+
+ list_view = NAUTILUS_LIST_VIEW (object);
+
+ if (list_view->details->model)
+ {
+ g_object_unref (list_view->details->model);
+ list_view->details->model = NULL;
+ }
+
+ if (list_view->details->drag_dest)
+ {
+ g_object_unref (list_view->details->drag_dest);
+ list_view->details->drag_dest = NULL;
+ }
+
+ clipboard = gtk_clipboard_get (GDK_SELECTION_CLIPBOARD);
+ g_signal_handlers_disconnect_by_func (clipboard, on_clipboard_owner_changed, list_view);
+ g_signal_handlers_disconnect_by_func (nautilus_preferences,
+ default_sort_order_changed_callback,
+ list_view);
+ g_signal_handlers_disconnect_by_func (nautilus_list_view_preferences,
+ default_visible_columns_changed_callback,
+ list_view);
+ g_signal_handlers_disconnect_by_func (nautilus_list_view_preferences,
+ default_column_order_changed_callback,
+ list_view);
+
+ g_clear_object (&list_view->details->tree_view_drag_gesture);
+ g_clear_object (&list_view->details->tree_view_multi_press_gesture);
+
+ G_OBJECT_CLASS (nautilus_list_view_parent_class)->dispose (object);
+}
+
+static void
+nautilus_list_view_finalize (GObject *object)
+{
+ NautilusListView *list_view;
+
+ list_view = NAUTILUS_LIST_VIEW (object);
+
+ g_free (list_view->details->original_name);
+ list_view->details->original_name = NULL;
+
+ if (list_view->details->first_click_path)
+ {
+ gtk_tree_path_free (list_view->details->first_click_path);
+ }
+ if (list_view->details->new_selection_path)
+ {
+ gtk_tree_path_free (list_view->details->new_selection_path);
+ }
+
+ g_list_free (list_view->details->cells);
+ g_hash_table_destroy (list_view->details->columns);
+
+ if (list_view->details->hover_path != NULL)
+ {
+ gtk_tree_path_free (list_view->details->hover_path);
+ }
+
+ if (list_view->details->column_editor != NULL)
+ {
+ gtk_widget_destroy (list_view->details->column_editor);
+ }
+
+ g_regex_unref (list_view->details->regex);
+
+ g_cancellable_cancel (list_view->details->starred_cancellable);
+ g_clear_object (&list_view->details->starred_cancellable);
+
+ g_signal_handlers_disconnect_by_func (list_view->details->tag_manager,
+ on_starred_files_changed,
+ list_view);
+
+ g_free (list_view->details);
+
+ G_OBJECT_CLASS (nautilus_list_view_parent_class)->finalize (object);
+}
+
+static char *
+nautilus_list_view_get_first_visible_file (NautilusFilesView *view)
+{
+ NautilusFile *file;
+ GtkTreePath *path;
+ GtkTreeIter iter;
+ NautilusListView *list_view;
+
+ list_view = NAUTILUS_LIST_VIEW (view);
+
+ if (gtk_tree_view_get_path_at_pos (list_view->details->tree_view,
+ 0, 0,
+ &path, NULL, NULL, NULL))
+ {
+ gtk_tree_model_get_iter (GTK_TREE_MODEL (list_view->details->model),
+ &iter, path);
+
+ gtk_tree_path_free (path);
+
+ gtk_tree_model_get (GTK_TREE_MODEL (list_view->details->model),
+ &iter,
+ NAUTILUS_LIST_MODEL_FILE_COLUMN, &file,
+ -1);
+ if (file)
+ {
+ char *uri;
+
+ uri = nautilus_file_get_uri (file);
+
+ nautilus_file_unref (file);
+
+ return uri;
+ }
+ }
+
+ return NULL;
+}
+
+static void
+nautilus_list_view_scroll_to_file (NautilusListView *view,
+ NautilusFile *file)
+{
+ GtkTreePath *path;
+ GtkTreeIter iter;
+
+ if (!nautilus_list_model_get_first_iter_for_file (view->details->model, file, &iter))
+ {
+ return;
+ }
+
+ path = gtk_tree_model_get_path (GTK_TREE_MODEL (view->details->model), &iter);
+
+ gtk_tree_view_scroll_to_cell (view->details->tree_view,
+ path, NULL,
+ TRUE, 0.0, 0.0);
+
+ gtk_tree_path_free (path);
+}
+
+static void
+list_view_scroll_to_file (NautilusFilesView *view,
+ const char *uri)
+{
+ NautilusFile *file;
+
+ if (uri != NULL)
+ {
+ /* Only if existing, since we don't want to add the file to
+ * the directory if it has been removed since then */
+ file = nautilus_file_get_existing_by_uri (uri);
+ if (file != NULL)
+ {
+ nautilus_list_view_scroll_to_file (NAUTILUS_LIST_VIEW (view), file);
+ nautilus_file_unref (file);
+ }
+ }
+}
+
+static void
+on_clipboard_contents_received (GtkClipboard *clipboard,
+ const gchar *selection_data,
+ gpointer user_data)
+{
+ NautilusListView *view = NAUTILUS_LIST_VIEW (user_data);
+
+ if (!view->details->model)
+ {
+ /* We've been destroyed since call */
+ g_object_unref (view);
+ return;
+ }
+
+ if (nautilus_clipboard_is_cut_from_selection_data (selection_data))
+ {
+ GList *uris;
+ GList *files;
+
+ uris = nautilus_clipboard_get_uri_list_from_selection_data (selection_data);
+ files = nautilus_file_list_from_uri_list (uris);
+ nautilus_list_model_set_highlight_for_files (view->details->model, files);
+
+ nautilus_file_list_free (files);
+ g_list_free_full (uris, g_free);
+ }
+ else
+ {
+ nautilus_list_model_set_highlight_for_files (view->details->model, NULL);
+ }
+
+ g_object_unref (view);
+}
+
+static void
+update_clipboard_status (NautilusListView *view)
+{
+ g_object_ref (view); /* Need to keep the object alive until we get the reply */
+ gtk_clipboard_request_text (nautilus_clipboard_get (GTK_WIDGET (view)),
+ on_clipboard_contents_received,
+ view);
+}
+
+static void
+on_clipboard_owner_changed (GtkClipboard *clipboard,
+ GdkEvent *event,
+ gpointer user_data)
+{
+ update_clipboard_status (NAUTILUS_LIST_VIEW (user_data));
+}
+
+static void
+nautilus_list_view_end_loading (NautilusFilesView *view,
+ gboolean all_files_seen)
+{
+ update_clipboard_status (NAUTILUS_LIST_VIEW (view));
+}
+
+static guint
+nautilus_list_view_get_id (NautilusFilesView *view)
+{
+ return NAUTILUS_VIEW_LIST_ID;
+}
+
+static GdkRectangle *
+get_rectangle_for_path (NautilusListView *list_view,
+ GtkTreePath *path)
+{
+ GtkTreeView *tree_view = list_view->details->tree_view;
+ GdkRectangle *rect = g_malloc0 (sizeof (GdkRectangle));
+ int header_height;
+
+ gtk_tree_view_get_cell_area (tree_view,
+ path,
+ list_view->details->file_name_column,
+ rect);
+ gtk_tree_view_convert_bin_window_to_widget_coords (tree_view,
+ rect->x, rect->y,
+ &rect->x, &rect->y);
+
+ /* FIXME Due to smooth scrolling, we may get the cell area while the view is
+ * still scrolling (and still outside the view), not at the final position
+ * of the cell after scrolling.
+ * https://bugzilla.gnome.org/show_bug.cgi?id=746773
+ * The following workaround guesses the final "y" coordinate by clamping it
+ * to the widget edge. Note that the top edge has got columns header, which
+ * is private, so first guess the header height from the difference between
+ * widget coordinates and bin cooridinates.
+ */
+ gtk_tree_view_convert_bin_window_to_widget_coords (tree_view,
+ 0, 0,
+ NULL, &header_height);
+
+ rect->y = CLAMP (rect->y,
+ header_height,
+ gtk_widget_get_allocated_height (GTK_WIDGET (list_view)) - rect->height);
+ /* End of workaround */
+
+ return rect;
+}
+
+static GdkRectangle *
+nautilus_list_view_compute_rename_popover_pointing_to (NautilusFilesView *view)
+{
+ NautilusListView *list_view;
+ GtkTreeView *tree_view;
+ GtkTreeSelection *selection;
+ GList *list;
+ GtkTreePath *path;
+ GdkRectangle *rect;
+
+ list_view = NAUTILUS_LIST_VIEW (view);
+ tree_view = list_view->details->tree_view;
+ selection = gtk_tree_view_get_selection (tree_view);
+ list = gtk_tree_selection_get_selected_rows (selection, NULL);
+ path = list->data;
+ rect = get_rectangle_for_path (list_view, path);
+
+ if (list_view->details->last_event_button_x > 0)
+ {
+ /* Point to the position in the row where it was clicked. */
+ rect->x = list_view->details->last_event_button_x;
+ /* Make it zero width to point exactly at rect->x.*/
+ rect->width = 0;
+ }
+
+ g_list_free_full (list, (GDestroyNotify) gtk_tree_path_free);
+
+ return rect;
+}
+
+static GdkRectangle *
+nautilus_list_view_reveal_for_selection_context_menu (NautilusFilesView *view)
+{
+ NautilusListView *list_view;
+ GtkTreeView *tree_view;
+ GtkTreeSelection *tree_selection;
+ GtkTreePath *path;
+ GdkRectangle *rect;
+
+ g_return_val_if_fail (NAUTILUS_IS_LIST_VIEW (view), NULL);
+
+ list_view = NAUTILUS_LIST_VIEW (view);
+ tree_view = list_view->details->tree_view;
+ tree_selection = gtk_tree_view_get_selection (tree_view);
+ g_return_val_if_fail (tree_selection != NULL, NULL);
+
+ /* Get the path to the last focused item, if selected. Otherwise, get
+ * the path to the selected item which is sorted the lowest.
+ */
+ gtk_tree_view_get_cursor (tree_view, &path, NULL);
+ if (path == NULL || !gtk_tree_selection_path_is_selected (tree_selection, path))
+ {
+ GList *list;
+
+ list = gtk_tree_selection_get_selected_rows (tree_selection, NULL);
+ list = g_list_last (list);
+ path = g_steal_pointer (&list->data);
+
+ g_list_free_full (list, (GDestroyNotify) gtk_tree_path_free);
+ }
+
+ gtk_tree_view_scroll_to_cell (tree_view, path, NULL, FALSE, 0.0, 0.0);
+
+ rect = get_rectangle_for_path (list_view, path);
+
+ gtk_tree_path_free (path);
+
+ return rect;
+}
+
+static void
+nautilus_list_view_preview_selection_event (NautilusFilesView *view,
+ GtkDirectionType direction)
+{
+ NautilusListView *list_view;
+ GtkTreeView *tree_view;
+ GtkTreeSelection *selection;
+ GList *list;
+ GtkTreeIter iter;
+ GtkTreePath *path;
+ GtkTreeModel *tree_model;
+ gboolean moved;
+
+ /* We only support up and down movements for the list view */
+ if (direction != GTK_DIR_UP && direction != GTK_DIR_DOWN)
+ {
+ return;
+ }
+
+ list_view = NAUTILUS_LIST_VIEW (view);
+ tree_view = list_view->details->tree_view;
+ selection = gtk_tree_view_get_selection (tree_view);
+ list = gtk_tree_selection_get_selected_rows (selection, &tree_model);
+
+ if (list == NULL)
+ {
+ return;
+ }
+
+ /* Advance the first selected item, since that's what we use for
+ * the previewer */
+ path = list->data;
+ moved = FALSE;
+ if (gtk_tree_model_get_iter (tree_model, &iter, path))
+ {
+ if (direction == GTK_DIR_UP)
+ {
+ moved = gtk_tree_model_iter_previous (tree_model, &iter);
+ }
+ else
+ {
+ moved = gtk_tree_model_iter_next (tree_model, &iter);
+ }
+ }
+
+ if (moved)
+ {
+ g_signal_handlers_block_by_func (selection, list_selection_changed_callback, view);
+
+ gtk_tree_selection_unselect_all (selection);
+ gtk_tree_selection_select_iter (selection, &iter);
+
+ g_signal_handlers_unblock_by_func (selection, list_selection_changed_callback, view);
+ nautilus_files_view_notify_selection_changed (view);
+ }
+
+ g_list_free_full (list, (GDestroyNotify) gtk_tree_path_free);
+}
+
+static void
+nautilus_list_view_class_init (NautilusListViewClass *class)
+{
+ NautilusFilesViewClass *nautilus_files_view_class;
+
+ nautilus_files_view_class = NAUTILUS_FILES_VIEW_CLASS (class);
+
+ G_OBJECT_CLASS (class)->dispose = nautilus_list_view_dispose;
+ G_OBJECT_CLASS (class)->finalize = nautilus_list_view_finalize;
+
+ nautilus_files_view_class->add_files = nautilus_list_view_add_files;
+ nautilus_files_view_class->begin_loading = nautilus_list_view_begin_loading;
+ nautilus_files_view_class->end_loading = nautilus_list_view_end_loading;
+ nautilus_files_view_class->bump_zoom_level = nautilus_list_view_bump_zoom_level;
+ nautilus_files_view_class->can_zoom_in = nautilus_list_view_can_zoom_in;
+ nautilus_files_view_class->can_zoom_out = nautilus_list_view_can_zoom_out;
+ nautilus_files_view_class->get_zoom_level_percentage = nautilus_list_view_get_zoom_level_percentage;
+ nautilus_files_view_class->is_zoom_level_default = nautilus_list_view_is_zoom_level_default;
+ nautilus_files_view_class->click_policy_changed = nautilus_list_view_click_policy_changed;
+ nautilus_files_view_class->clear = nautilus_list_view_clear;
+ nautilus_files_view_class->file_changed = nautilus_list_view_file_changed;
+ nautilus_files_view_class->get_backing_uri = nautilus_list_view_get_backing_uri;
+ nautilus_files_view_class->get_selection = nautilus_list_view_get_selection;
+ nautilus_files_view_class->get_selection_for_file_transfer = nautilus_list_view_get_selection_for_file_transfer;
+ nautilus_files_view_class->is_empty = nautilus_list_view_is_empty;
+ nautilus_files_view_class->remove_file = nautilus_list_view_remove_file;
+ nautilus_files_view_class->restore_standard_zoom_level = nautilus_list_view_restore_standard_zoom_level;
+ nautilus_files_view_class->reveal_selection = nautilus_list_view_reveal_selection;
+ nautilus_files_view_class->select_all = nautilus_list_view_select_all;
+ nautilus_files_view_class->select_first = nautilus_list_view_select_first;
+ nautilus_files_view_class->set_selection = nautilus_list_view_set_selection;
+ nautilus_files_view_class->invert_selection = nautilus_list_view_invert_selection;
+ nautilus_files_view_class->compare_files = nautilus_list_view_compare_files;
+ nautilus_files_view_class->sort_directories_first_changed = nautilus_list_view_sort_directories_first_changed;
+ nautilus_files_view_class->end_file_changes = nautilus_list_view_end_file_changes;
+ nautilus_files_view_class->get_view_id = nautilus_list_view_get_id;
+ nautilus_files_view_class->get_first_visible_file = nautilus_list_view_get_first_visible_file;
+ nautilus_files_view_class->scroll_to_file = list_view_scroll_to_file;
+ nautilus_files_view_class->compute_rename_popover_pointing_to = nautilus_list_view_compute_rename_popover_pointing_to;
+ nautilus_files_view_class->reveal_for_selection_context_menu = nautilus_list_view_reveal_for_selection_context_menu;
+ nautilus_files_view_class->preview_selection_event = nautilus_list_view_preview_selection_event;
+}
+
+static void
+nautilus_list_view_init (NautilusListView *list_view)
+{
+ GActionGroup *view_action_group;
+ GtkClipboard *clipboard;
+
+ list_view->details = g_new0 (NautilusListViewDetails, 1);
+
+ /* ensure that the zoom level is always set before settings up the tree view columns */
+ list_view->details->zoom_level = get_default_zoom_level ();
+
+ create_and_set_up_tree_view (list_view);
+
+ gtk_style_context_add_class (gtk_widget_get_style_context (GTK_WIDGET (list_view)),
+ "nautilus-list-view");
+
+ g_signal_connect_swapped (nautilus_preferences,
+ "changed::" NAUTILUS_PREFERENCES_DEFAULT_SORT_ORDER,
+ G_CALLBACK (default_sort_order_changed_callback),
+ list_view);
+ g_signal_connect_swapped (nautilus_preferences,
+ "changed::" NAUTILUS_PREFERENCES_DEFAULT_SORT_IN_REVERSE_ORDER,
+ G_CALLBACK (default_sort_order_changed_callback),
+ list_view);
+ g_signal_connect_swapped (nautilus_list_view_preferences,
+ "changed::" NAUTILUS_PREFERENCES_LIST_VIEW_DEFAULT_VISIBLE_COLUMNS,
+ G_CALLBACK (default_visible_columns_changed_callback),
+ list_view);
+ g_signal_connect_swapped (nautilus_list_view_preferences,
+ "changed::" NAUTILUS_PREFERENCES_LIST_VIEW_DEFAULT_COLUMN_ORDER,
+ G_CALLBACK (default_column_order_changed_callback),
+ list_view);
+
+ /* React to clipboard changes */
+ clipboard = gtk_clipboard_get (GDK_SELECTION_CLIPBOARD);
+ g_signal_connect (clipboard, "owner-change",
+ G_CALLBACK (on_clipboard_owner_changed), list_view);
+
+ nautilus_list_view_click_policy_changed (NAUTILUS_FILES_VIEW (list_view));
+
+ nautilus_list_view_set_zoom_level (list_view, get_default_zoom_level ());
+
+ list_view->details->hover_path = NULL;
+
+ view_action_group = nautilus_files_view_get_action_group (NAUTILUS_FILES_VIEW (list_view));
+ g_action_map_add_action_entries (G_ACTION_MAP (view_action_group),
+ list_view_entries,
+ G_N_ELEMENTS (list_view_entries),
+ list_view);
+ /* 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 (list_view)),
+ "zoom-to-level", g_variant_new_int32 (get_default_zoom_level ()));
+
+ list_view->details->regex = g_regex_new ("\\R+", 0, G_REGEX_MATCH_NEWLINE_ANY, NULL);
+
+ list_view->details->tag_manager = nautilus_tag_manager_get ();
+ list_view->details->starred_cancellable = g_cancellable_new ();
+
+ g_signal_connect (list_view->details->tag_manager,
+ "starred-changed",
+ (GCallback) on_starred_files_changed,
+ list_view);
+}
+
+NautilusFilesView *
+nautilus_list_view_new (NautilusWindowSlot *slot)
+{
+ return g_object_new (NAUTILUS_TYPE_LIST_VIEW,
+ "window-slot", slot,
+ NULL);
+}