/* fm-list-view.c - implementation of list view of directory. * * Copyright (C) 2000 Eazel, Inc. * Copyright (C) 2001, 2002 Anders Carlsson * * 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 . * * Authors: John Sullivan * Anders Carlsson * David Emory Watson */ #include "nautilus-list-view.h" #include "nautilus-list-view-private.h" #include #include #include #include #include #include #include #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 /* The star icon itself is 16px, which leaves an empty 16px gutter on each side, * which is necessary to avoid the overlay scrollbar. */ #define STAR_COLUMN_WIDTH 48 /* 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", "date_created", "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_hash_table_lookup (view->details->columns, "starred") == column && !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, " %s", 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 ("", cell, NULL); gtk_tree_view_column_set_fixed_width (column, STAR_COLUMN_WIDTH); } 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 * get_default_visible_columns_as_list (NautilusListView *list_view) { GList *res = NULL; gint i = 0; gchar **array; array = get_default_visible_columns (list_view); while (array[i] != NULL) { res = g_list_prepend (res, array[i]); i++; } g_free (array); 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 = get_default_visible_columns_as_list (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 ("", label_text, "", 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_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_clear_object (&list_view->details->tag_manager); 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, GtkSelectionData *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_contents (nautilus_clipboard_get (GTK_WIDGET (view)), nautilus_clipboard_get_atom (), 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); }