/* nautilus-files-view.c * * Copyright (C) 1999, 2000 Free Software Foundation * Copyright (C) 2000, 2001 Eazel, Inc. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program 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 * General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program; if not, see . * * Authors: Ettore Perazzoli, * John Sullivan , * Darin Adler , * Pavel Cisler , * David Emory Watson */ #include "nautilus-files-view.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define DEBUG_FLAG NAUTILUS_DEBUG_DIRECTORY_VIEW #include "nautilus-debug.h" #include "nautilus-application.h" #include "nautilus-batch-rename-dialog.h" #include "nautilus-batch-rename-utilities.h" #include "nautilus-canvas-view.h" #include "nautilus-clipboard.h" #include "nautilus-compress-dialog-controller.h" #include "nautilus-directory.h" #include "nautilus-dnd.h" #include "nautilus-enums.h" #include "nautilus-error-reporting.h" #include "nautilus-file-changes-queue.h" #include "nautilus-file-name-widget-controller.h" #include "nautilus-file-operations.h" #include "nautilus-file-private.h" #include "nautilus-file-undo-manager.h" #include "nautilus-file-utilities.h" #include "nautilus-file.h" #include "nautilus-floating-bar.h" #include "nautilus-global-preferences.h" #include "nautilus-icon-info.h" #include "nautilus-icon-names.h" #include "nautilus-list-view.h" #include "nautilus-metadata.h" #include "nautilus-mime-actions.h" #include "nautilus-module.h" #include "nautilus-new-folder-dialog-controller.h" #include "nautilus-previewer.h" #include "nautilus-profile.h" #include "nautilus-program-choosing.h" #include "nautilus-properties-window.h" #include "nautilus-rename-file-popover-controller.h" #include "nautilus-search-directory.h" #include "nautilus-signaller.h" #include "nautilus-tag-manager.h" #include "nautilus-toolbar.h" #include "nautilus-trash-monitor.h" #include "nautilus-ui-utilities.h" #include "nautilus-view.h" #include "nautilus-view-icon-controller.h" #include "nautilus-window.h" #include "nautilus-tracker-utilities.h" /* Minimum starting update inverval */ #define UPDATE_INTERVAL_MIN 100 /* Maximum update interval */ #define UPDATE_INTERVAL_MAX 2000 /* Amount of miliseconds the update interval is increased */ #define UPDATE_INTERVAL_INC 250 /* Interval at which the update interval is increased */ #define UPDATE_INTERVAL_TIMEOUT_INTERVAL 250 /* Milliseconds that have to pass without a change to reset the update interval */ #define UPDATE_INTERVAL_RESET 1000 #define SILENT_WINDOW_OPEN_LIMIT 5 #define DUPLICATE_HORIZONTAL_ICON_OFFSET 70 #define DUPLICATE_VERTICAL_ICON_OFFSET 30 #define MAX_QUEUED_UPDATES 500 #define MAX_MENU_LEVELS 5 #define TEMPLATE_LIMIT 30 #define SHORTCUTS_PATH "/nautilus/scripts-accels" /* Delay to show the Loading... floating bar */ #define FLOATING_BAR_LOADING_DELAY 200 /* ms */ #define MIN_COMMON_FILENAME_PREFIX_LENGTH 4 enum { ADD_FILES, BEGIN_FILE_CHANGES, BEGIN_LOADING, CLEAR, END_FILE_CHANGES, END_LOADING, FILE_CHANGED, MOVE_COPY_ITEMS, REMOVE_FILE, SELECTION_CHANGED, TRASH, DELETE, LAST_SIGNAL }; enum { PROP_WINDOW_SLOT = 1, PROP_SUPPORTS_ZOOMING, PROP_ICON, PROP_SEARCHING, PROP_LOADING, PROP_SELECTION, PROP_LOCATION, PROP_SEARCH_QUERY, PROP_EXTENSIONS_BACKGROUND_MENU, PROP_TEMPLATES_MENU, NUM_PROPERTIES }; static guint signals[LAST_SIGNAL]; static char *scripts_directory_uri = NULL; static int scripts_directory_uri_length; static GHashTable *script_accels = NULL; typedef struct { /* Main components */ GtkWidget *overlay; NautilusWindowSlot *slot; NautilusDirectory *model; NautilusFile *directory_as_file; GFile *location; guint dir_merge_id; NautilusQuery *search_query; NautilusRenameFilePopoverController *rename_file_controller; NautilusNewFolderDialogController *new_folder_controller; NautilusCompressDialogController *compress_controller; gboolean supports_zooming; GList *scripts_directory_list; GList *templates_directory_list; guint display_selection_idle_id; guint update_context_menus_timeout_id; guint update_status_idle_id; guint reveal_selection_idle_id; guint display_pending_source_id; guint changes_timeout_id; guint update_interval; guint64 last_queued; guint files_added_handler_id; guint files_changed_handler_id; guint load_error_handler_id; guint done_loading_handler_id; guint file_changed_handler_id; /* Containers with FileAndDirectory* elements */ GList *new_added_files; GList *new_changed_files; GHashTable *non_ready_files; GList *old_added_files; GList *old_changed_files; GList *pending_selection; GHashTable *pending_reveal; /* whether we are in the active slot */ gboolean active; /* loading indicates whether this view has begun loading a directory. * This flag should need not be set inside subclasses. NautilusFilesView automatically * sets 'loading' to TRUE before it begins loading a directory's contents and to FALSE * after it finishes loading the directory and its view. */ gboolean loading; gboolean templates_present; gboolean scripts_present; gboolean in_destruction; gboolean sort_directories_first; gboolean show_hidden_files; gboolean ignore_hidden_file_preferences; gboolean batching_selection_level; gboolean selection_changed_while_batched; gboolean selection_was_removed; gboolean metadata_for_directory_as_file_pending; gboolean metadata_for_files_in_directory_pending; GList *subdirectory_list; GMenu *selection_menu_model; GMenu *background_menu_model; GtkWidget *selection_menu; GtkWidget *background_menu; GActionGroup *view_action_group; GtkWidget *scrolled_window; /* Empty states */ GtkWidget *folder_is_empty_widget; GtkWidget *trash_is_empty_widget; GtkWidget *no_search_results_widget; GtkWidget *starred_is_empty_widget; /* Floating bar */ guint floating_bar_set_status_timeout_id; guint floating_bar_loading_timeout_id; guint floating_bar_set_passthrough_timeout_id; GtkWidget *floating_bar; /* Toolbar menu */ NautilusToolbarMenuSections *toolbar_menu_sections; GtkWidget *sort_menu; GtkWidget *sort_trash_time; GtkWidget *visible_columns; GtkWidget *stop; GtkWidget *reload; GtkWidget *zoom_controls_box; GtkWidget *zoom_level_label; /* Exposed menus, for the path bar etc. */ GMenuModel *extensions_background_menu; GMenuModel *templates_menu; gulong stop_signal_handler; gulong reload_signal_handler; GCancellable *starred_cancellable; NautilusTagManager *tag_manager; gint name_accepted_handler_id; gint cancelled_handler_id; } NautilusFilesViewPrivate; /** * FileAndDirectory: * @file: A #NautilusFile * @directory: A #NautilusDirectory where @file is present. * * The #FileAndDirectory struct is used to relate files to the directories they * are displayed in. This is necessary because the same file can appear multiple * times in the same view, by expanding folders as a tree in a list of search * results. (Adapted from commit 671e4bdaa4d07b039015bedfcb5d42026e5d099e) */ typedef struct { NautilusFile *file; NautilusDirectory *directory; } FileAndDirectory; /* forward declarations */ static gboolean display_selection_info_idle_callback (gpointer data); static void trash_or_delete_files (GtkWindow *parent_window, const GList *files, NautilusFilesView *view); static void load_directory (NautilusFilesView *view, NautilusDirectory *directory); static void on_clipboard_owner_changed (GtkClipboard *clipboard, GdkEvent *event, gpointer user_data); static void open_one_in_new_window (gpointer data, gpointer callback_data); static void schedule_update_context_menus (NautilusFilesView *view); static void remove_update_context_menus_timeout_callback (NautilusFilesView *view); static void schedule_update_status (NautilusFilesView *view); static void remove_update_status_idle_callback (NautilusFilesView *view); static void reset_update_interval (NautilusFilesView *view); static void schedule_idle_display_of_pending_files (NautilusFilesView *view); static void unschedule_display_of_pending_files (NautilusFilesView *view); static void disconnect_model_handlers (NautilusFilesView *view); static void metadata_for_directory_as_file_ready_callback (NautilusFile *file, gpointer callback_data); static void metadata_for_files_in_directory_ready_callback (NautilusDirectory *directory, GList *files, gpointer callback_data); static void nautilus_files_view_trash_state_changed_callback (NautilusTrashMonitor *trash, gboolean state, gpointer callback_data); static void nautilus_files_view_select_file (NautilusFilesView *view, NautilusFile *file); static void update_templates_directory (NautilusFilesView *view); static void extract_files (NautilusFilesView *view, GList *files, GFile *destination_directory); static void extract_files_to_chosen_location (NautilusFilesView *view, GList *files); static void nautilus_files_view_check_empty_states (NautilusFilesView *view); static gboolean nautilus_files_view_is_searching (NautilusView *view); static void nautilus_files_view_iface_init (NautilusViewInterface *view); static void set_search_query_internal (NautilusFilesView *files_view, NautilusQuery *query, NautilusDirectory *base_model); static gboolean nautilus_files_view_is_read_only (NautilusFilesView *view); G_DEFINE_TYPE_WITH_CODE (NautilusFilesView, nautilus_files_view, GTK_TYPE_GRID, G_IMPLEMENT_INTERFACE (NAUTILUS_TYPE_VIEW, nautilus_files_view_iface_init) G_ADD_PRIVATE (NautilusFilesView)); static const struct { unsigned int keyval; const char *action; } extra_view_keybindings [] = { /* View actions */ { GDK_KEY_ZoomIn, "zoom-in" }, { GDK_KEY_ZoomOut, "zoom-out" }, }; /* * Floating Bar code */ static void remove_loading_floating_bar (NautilusFilesView *view) { NautilusFilesViewPrivate *priv; priv = nautilus_files_view_get_instance_private (view); if (priv->floating_bar_loading_timeout_id != 0) { g_source_remove (priv->floating_bar_loading_timeout_id); priv->floating_bar_loading_timeout_id = 0; } gtk_widget_hide (priv->floating_bar); nautilus_floating_bar_cleanup_actions (NAUTILUS_FLOATING_BAR (priv->floating_bar)); } static void real_setup_loading_floating_bar (NautilusFilesView *view) { NautilusFilesViewPrivate *priv; priv = nautilus_files_view_get_instance_private (view); nautilus_floating_bar_cleanup_actions (NAUTILUS_FLOATING_BAR (priv->floating_bar)); nautilus_floating_bar_set_primary_label (NAUTILUS_FLOATING_BAR (priv->floating_bar), nautilus_view_is_searching (NAUTILUS_VIEW (view)) ? _("Searching…") : _("Loading…")); nautilus_floating_bar_set_details_label (NAUTILUS_FLOATING_BAR (priv->floating_bar), NULL); nautilus_floating_bar_set_show_spinner (NAUTILUS_FLOATING_BAR (priv->floating_bar), priv->loading); nautilus_floating_bar_add_action (NAUTILUS_FLOATING_BAR (priv->floating_bar), "process-stop-symbolic", NAUTILUS_FLOATING_BAR_ACTION_ID_STOP); gtk_widget_set_halign (priv->floating_bar, GTK_ALIGN_END); gtk_widget_show (priv->floating_bar); } static gboolean setup_loading_floating_bar_timeout_cb (gpointer user_data) { NautilusFilesView *view = user_data; NautilusFilesViewPrivate *priv; priv = nautilus_files_view_get_instance_private (view); priv->floating_bar_loading_timeout_id = 0; real_setup_loading_floating_bar (view); return FALSE; } static void setup_loading_floating_bar (NautilusFilesView *view) { NautilusFilesViewPrivate *priv; priv = nautilus_files_view_get_instance_private (view); /* setup loading overlay */ if (priv->floating_bar_set_status_timeout_id != 0) { g_source_remove (priv->floating_bar_set_status_timeout_id); priv->floating_bar_set_status_timeout_id = 0; } if (priv->floating_bar_loading_timeout_id != 0) { g_source_remove (priv->floating_bar_loading_timeout_id); priv->floating_bar_loading_timeout_id = 0; } priv->floating_bar_loading_timeout_id = g_timeout_add (FLOATING_BAR_LOADING_DELAY, setup_loading_floating_bar_timeout_cb, view); } static void floating_bar_action_cb (NautilusFloatingBar *floating_bar, gint action, NautilusFilesView *view) { NautilusFilesViewPrivate *priv; priv = nautilus_files_view_get_instance_private (view); if (action == NAUTILUS_FLOATING_BAR_ACTION_ID_STOP) { remove_loading_floating_bar (view); nautilus_window_slot_stop_loading (priv->slot); } } static void real_floating_bar_set_short_status (NautilusFilesView *view, const gchar *primary_status, const gchar *detail_status) { NautilusFilesViewPrivate *priv; priv = nautilus_files_view_get_instance_private (view); if (priv->loading) { return; } nautilus_floating_bar_cleanup_actions (NAUTILUS_FLOATING_BAR (priv->floating_bar)); nautilus_floating_bar_set_show_spinner (NAUTILUS_FLOATING_BAR (priv->floating_bar), FALSE); if (primary_status == NULL && detail_status == NULL) { gtk_widget_hide (priv->floating_bar); nautilus_floating_bar_remove_hover_timeout (NAUTILUS_FLOATING_BAR (priv->floating_bar)); return; } nautilus_floating_bar_set_labels (NAUTILUS_FLOATING_BAR (priv->floating_bar), primary_status, detail_status); gtk_widget_show (priv->floating_bar); } typedef struct { gchar *primary_status; gchar *detail_status; NautilusFilesView *view; } FloatingBarSetStatusData; static void floating_bar_set_status_data_free (gpointer data) { FloatingBarSetStatusData *status_data = data; g_free (status_data->primary_status); g_free (status_data->detail_status); g_slice_free (FloatingBarSetStatusData, data); } static gboolean floating_bar_set_status_timeout_cb (gpointer data) { NautilusFilesViewPrivate *priv; FloatingBarSetStatusData *status_data = data; priv = nautilus_files_view_get_instance_private (status_data->view); priv->floating_bar_set_status_timeout_id = 0; real_floating_bar_set_short_status (status_data->view, status_data->primary_status, status_data->detail_status); return FALSE; } static gboolean remove_floating_bar_passthrough (gpointer data) { NautilusFilesViewPrivate *priv; priv = nautilus_files_view_get_instance_private (NAUTILUS_FILES_VIEW (data)); gtk_overlay_set_overlay_pass_through (GTK_OVERLAY (priv->overlay), priv->floating_bar, FALSE); priv->floating_bar_set_passthrough_timeout_id = 0; return G_SOURCE_REMOVE; } static void set_floating_bar_status (NautilusFilesView *view, const gchar *primary_status, const gchar *detail_status) { GtkSettings *settings; gint double_click_time; FloatingBarSetStatusData *status_data; NautilusFilesViewPrivate *priv; priv = nautilus_files_view_get_instance_private (view); if (priv->floating_bar_set_status_timeout_id != 0) { g_source_remove (priv->floating_bar_set_status_timeout_id); priv->floating_bar_set_status_timeout_id = 0; } settings = gtk_settings_get_for_screen (gtk_widget_get_screen (GTK_WIDGET (view))); g_object_get (settings, "gtk-double-click-time", &double_click_time, NULL); status_data = g_slice_new0 (FloatingBarSetStatusData); status_data->primary_status = g_strdup (primary_status); status_data->detail_status = g_strdup (detail_status); status_data->view = view; if (priv->floating_bar_set_passthrough_timeout_id != 0) { g_source_remove (priv->floating_bar_set_passthrough_timeout_id); priv->floating_bar_set_passthrough_timeout_id = 0; } /* Activate passthrough on the floating bar just long enough for a * potential double click to happen, so to not interfere with it */ gtk_overlay_set_overlay_pass_through (GTK_OVERLAY (priv->overlay), priv->floating_bar, TRUE); priv->floating_bar_set_passthrough_timeout_id = g_timeout_add ((guint) double_click_time, remove_floating_bar_passthrough, view); /* waiting for half of the double-click-time before setting * the status seems to be a good approximation of not setting it * too often and not delaying the statusbar too much. */ priv->floating_bar_set_status_timeout_id = g_timeout_add_full (G_PRIORITY_DEFAULT, (guint) (double_click_time / 2), floating_bar_set_status_timeout_cb, status_data, floating_bar_set_status_data_free); } static char * real_get_backing_uri (NautilusFilesView *view) { NautilusFilesViewPrivate *priv; g_return_val_if_fail (NAUTILUS_IS_FILES_VIEW (view), NULL); priv = nautilus_files_view_get_instance_private (view); if (priv->model == NULL) { return NULL; } return nautilus_directory_get_uri (priv->model); } /** * * nautilus_files_view_get_backing_uri: * * Returns the URI for the target location of new directory, new file, new * link and paste operations. */ char * nautilus_files_view_get_backing_uri (NautilusFilesView *view) { g_return_val_if_fail (NAUTILUS_IS_FILES_VIEW (view), NULL); return NAUTILUS_FILES_VIEW_CLASS (G_OBJECT_GET_CLASS (view))->get_backing_uri (view); } /** * nautilus_files_view_select_all: * * select all the items in the view * **/ static void nautilus_files_view_select_all (NautilusFilesView *view) { g_return_if_fail (NAUTILUS_IS_FILES_VIEW (view)); NAUTILUS_FILES_VIEW_CLASS (G_OBJECT_GET_CLASS (view))->select_all (view); } static void nautilus_files_view_select_first (NautilusFilesView *view) { g_return_if_fail (NAUTILUS_IS_FILES_VIEW (view)); NAUTILUS_FILES_VIEW_CLASS (G_OBJECT_GET_CLASS (view))->select_first (view); } static void nautilus_files_view_call_set_selection (NautilusFilesView *view, GList *selection) { g_return_if_fail (NAUTILUS_IS_FILES_VIEW (view)); NAUTILUS_FILES_VIEW_CLASS (G_OBJECT_GET_CLASS (view))->set_selection (view, selection); } static GList * nautilus_files_view_get_selection_for_file_transfer (NautilusFilesView *view) { g_return_val_if_fail (NAUTILUS_IS_FILES_VIEW (view), NULL); return NAUTILUS_FILES_VIEW_CLASS (G_OBJECT_GET_CLASS (view))->get_selection_for_file_transfer (view); } static void nautilus_files_view_invert_selection (NautilusFilesView *view) { g_return_if_fail (NAUTILUS_IS_FILES_VIEW (view)); NAUTILUS_FILES_VIEW_CLASS (G_OBJECT_GET_CLASS (view))->invert_selection (view); } /** * nautilus_files_view_reveal_selection: * * Scroll as necessary to reveal the selected items. **/ static void nautilus_files_view_reveal_selection (NautilusFilesView *view) { g_return_if_fail (NAUTILUS_IS_FILES_VIEW (view)); NAUTILUS_FILES_VIEW_CLASS (G_OBJECT_GET_CLASS (view))->reveal_selection (view); } /** * nautilus_files_view_get_toolbar_menu_sections: * @view: a #NautilusFilesView * * Retrieves the menu sections that should be added to the toolbar menu when * this view is active * * Returns: (transfer none): a #NautilusToolbarMenuSections with the details of * which menu sections should be added to the menu */ static NautilusToolbarMenuSections * nautilus_files_view_get_toolbar_menu_sections (NautilusView *view) { NautilusFilesViewPrivate *priv; g_return_val_if_fail (NAUTILUS_IS_FILES_VIEW (view), NULL); priv = nautilus_files_view_get_instance_private (NAUTILUS_FILES_VIEW (view)); return priv->toolbar_menu_sections; } static GMenuModel* nautilus_files_view_get_templates_menu (NautilusView *self) { GMenuModel *menu; g_object_get (self, "templates-menu", &menu, NULL); return menu; } static GMenuModel* nautilus_files_view_get_extensions_background_menu (NautilusView *self) { GMenuModel *menu; g_object_get (self, "extensions-background-menu", &menu, NULL); return menu; } static GMenuModel* real_get_extensions_background_menu (NautilusView *view) { NautilusFilesViewPrivate *priv; g_return_val_if_fail (NAUTILUS_IS_FILES_VIEW (view), NULL); priv = nautilus_files_view_get_instance_private (NAUTILUS_FILES_VIEW (view)); return priv->extensions_background_menu; } static GMenuModel* real_get_templates_menu (NautilusView *view) { NautilusFilesViewPrivate *priv; g_return_val_if_fail (NAUTILUS_IS_FILES_VIEW (view), NULL); priv = nautilus_files_view_get_instance_private (NAUTILUS_FILES_VIEW (view)); return priv->templates_menu; } static void nautilus_files_view_set_templates_menu (NautilusView *self, GMenuModel *menu) { g_object_set (self, "templates-menu", menu, NULL); } static void nautilus_files_view_set_extensions_background_menu (NautilusView *self, GMenuModel *menu) { g_object_set (self, "extensions-background-menu", menu, NULL); } static void real_set_extensions_background_menu (NautilusView *view, GMenuModel *menu) { NautilusFilesViewPrivate *priv; g_return_if_fail (NAUTILUS_IS_FILES_VIEW (view)); priv = nautilus_files_view_get_instance_private (NAUTILUS_FILES_VIEW (view)); g_set_object (&priv->extensions_background_menu, menu); } static void real_set_templates_menu (NautilusView *view, GMenuModel *menu) { NautilusFilesViewPrivate *priv; g_return_if_fail (NAUTILUS_IS_FILES_VIEW (view)); priv = nautilus_files_view_get_instance_private (NAUTILUS_FILES_VIEW (view)); g_set_object (&priv->templates_menu, menu); } static gboolean showing_trash_directory (NautilusFilesView *view) { NautilusFile *file; file = nautilus_files_view_get_directory_as_file (view); if (file != NULL) { return nautilus_file_is_in_trash (file); } return FALSE; } static gboolean showing_recent_directory (NautilusFilesView *view) { NautilusFile *file; file = nautilus_files_view_get_directory_as_file (view); if (file != NULL) { return nautilus_file_is_in_recent (file); } return FALSE; } static gboolean showing_starred_directory (NautilusFilesView *view) { NautilusFile *file; file = nautilus_files_view_get_directory_as_file (view); if (file != NULL) { return nautilus_file_is_in_starred (file); } return FALSE; } static gboolean nautilus_files_view_supports_creating_files (NautilusFilesView *view) { g_return_val_if_fail (NAUTILUS_IS_FILES_VIEW (view), FALSE); return !nautilus_files_view_is_read_only (view) && !showing_trash_directory (view) && !showing_recent_directory (view) && !showing_starred_directory (view); } static gboolean nautilus_files_view_supports_extract_here (NautilusFilesView *view) { g_return_val_if_fail (NAUTILUS_IS_FILES_VIEW (view), FALSE); return nautilus_files_view_supports_creating_files (view) && !nautilus_view_is_searching (NAUTILUS_VIEW (view)); } static gboolean nautilus_files_view_is_empty (NautilusFilesView *view) { g_return_val_if_fail (NAUTILUS_IS_FILES_VIEW (view), FALSE); return NAUTILUS_FILES_VIEW_CLASS (G_OBJECT_GET_CLASS (view))->is_empty (view); } /** * nautilus_files_view_bump_zoom_level: * * bump the current zoom level by invoking the relevant subclass through the slot * **/ void nautilus_files_view_bump_zoom_level (NautilusFilesView *view, int zoom_increment) { g_return_if_fail (NAUTILUS_IS_FILES_VIEW (view)); if (!nautilus_files_view_supports_zooming (view)) { return; } NAUTILUS_FILES_VIEW_CLASS (G_OBJECT_GET_CLASS (view))->bump_zoom_level (view, zoom_increment); } /** * nautilus_files_view_can_zoom_in: * * Determine whether the view can be zoomed any closer. * @view: The zoomable NautilusFilesView. * * Return value: TRUE if @view can be zoomed any closer, FALSE otherwise. * **/ gboolean nautilus_files_view_can_zoom_in (NautilusFilesView *view) { g_return_val_if_fail (NAUTILUS_IS_FILES_VIEW (view), FALSE); if (!nautilus_files_view_supports_zooming (view)) { return FALSE; } return NAUTILUS_FILES_VIEW_CLASS (G_OBJECT_GET_CLASS (view))->can_zoom_in (view); } /** * nautilus_files_view_can_zoom_out: * * Determine whether the view can be zoomed any further away. * @view: The zoomable NautilusFilesView. * * Return value: TRUE if @view can be zoomed any further away, FALSE otherwise. * **/ gboolean nautilus_files_view_can_zoom_out (NautilusFilesView *view) { g_return_val_if_fail (NAUTILUS_IS_FILES_VIEW (view), FALSE); if (!nautilus_files_view_supports_zooming (view)) { return FALSE; } return NAUTILUS_FILES_VIEW_CLASS (G_OBJECT_GET_CLASS (view))->can_zoom_out (view); } gboolean nautilus_files_view_supports_zooming (NautilusFilesView *view) { NautilusFilesViewPrivate *priv; priv = nautilus_files_view_get_instance_private (view); g_return_val_if_fail (NAUTILUS_IS_FILES_VIEW (view), FALSE); return priv->supports_zooming; } /** * nautilus_files_view_restore_standard_zoom_level: * * Restore the zoom level to 100% */ static void nautilus_files_view_restore_standard_zoom_level (NautilusFilesView *view) { if (!nautilus_files_view_supports_zooming (view)) { return; } NAUTILUS_FILES_VIEW_CLASS (G_OBJECT_GET_CLASS (view))->restore_standard_zoom_level (view); } static gfloat nautilus_files_view_get_zoom_level_percentage (NautilusFilesView *view) { g_return_val_if_fail (NAUTILUS_IS_FILES_VIEW (view), 1); return NAUTILUS_FILES_VIEW_CLASS (G_OBJECT_GET_CLASS (view))->get_zoom_level_percentage (view); } static gboolean nautilus_files_view_is_zoom_level_default (NautilusFilesView *view) { g_return_val_if_fail (NAUTILUS_IS_FILES_VIEW (view), FALSE); return NAUTILUS_FILES_VIEW_CLASS (G_OBJECT_GET_CLASS (view))->is_zoom_level_default (view); } gboolean nautilus_files_view_is_searching (NautilusView *view) { NautilusFilesView *files_view; NautilusFilesViewPrivate *priv; files_view = NAUTILUS_FILES_VIEW (view); priv = nautilus_files_view_get_instance_private (files_view); if (!priv->model) { return FALSE; } return NAUTILUS_IS_SEARCH_DIRECTORY (priv->model); } guint nautilus_files_view_get_view_id (NautilusView *view) { return NAUTILUS_FILES_VIEW_CLASS (G_OBJECT_GET_CLASS (view))->get_view_id (NAUTILUS_FILES_VIEW (view)); } char * nautilus_files_view_get_first_visible_file (NautilusFilesView *view) { return NAUTILUS_FILES_VIEW_CLASS (G_OBJECT_GET_CLASS (view))->get_first_visible_file (view); } void nautilus_files_view_scroll_to_file (NautilusFilesView *view, const char *uri) { NAUTILUS_FILES_VIEW_CLASS (G_OBJECT_GET_CLASS (view))->scroll_to_file (view, uri); } /** * nautilus_files_view_get_selection: * * Get a list of NautilusFile pointers that represents the * currently-selected items in this view. Subclasses must override * the signal handler for the 'get_selection' signal. Callers are * responsible for g_free-ing the list (and unrefing its data). * @view: NautilusFilesView whose selected items are of interest. * * Return value: GList of NautilusFile pointers representing the selection. * **/ static GList * nautilus_files_view_get_selection (NautilusView *view) { g_return_val_if_fail (NAUTILUS_IS_FILES_VIEW (view), NULL); return NAUTILUS_FILES_VIEW_CLASS (G_OBJECT_GET_CLASS (view))->get_selection (NAUTILUS_FILES_VIEW (view)); } typedef struct { NautilusFile *file; NautilusFilesView *directory_view; } ScriptLaunchParameters; typedef struct { NautilusFile *file; NautilusFilesView *directory_view; } CreateTemplateParameters; static FileAndDirectory * file_and_directory_new (NautilusFile *file, NautilusDirectory *directory) { FileAndDirectory *fad; fad = g_new0 (FileAndDirectory, 1); fad->directory = nautilus_directory_ref (directory); fad->file = nautilus_file_ref (file); return fad; } static NautilusFile * file_and_directory_get_file (FileAndDirectory *fad) { g_return_val_if_fail (fad != NULL, NULL); return nautilus_file_ref (fad->file); } static void file_and_directory_free (gpointer data) { FileAndDirectory *fad = data; nautilus_directory_unref (fad->directory); nautilus_file_unref (fad->file); g_free (fad); } G_DEFINE_AUTOPTR_CLEANUP_FUNC (FileAndDirectory, file_and_directory_free) static gboolean file_and_directory_equal (gconstpointer v1, gconstpointer v2) { const FileAndDirectory *fad1, *fad2; fad1 = v1; fad2 = v2; return (fad1->file == fad2->file && fad1->directory == fad2->directory); } static guint file_and_directory_hash (gconstpointer v) { const FileAndDirectory *fad; fad = v; return GPOINTER_TO_UINT (fad->file) ^ GPOINTER_TO_UINT (fad->directory); } static ScriptLaunchParameters * script_launch_parameters_new (NautilusFile *file, NautilusFilesView *directory_view) { ScriptLaunchParameters *result; result = g_new0 (ScriptLaunchParameters, 1); result->directory_view = directory_view; nautilus_file_ref (file); result->file = file; return result; } static void script_launch_parameters_free (ScriptLaunchParameters *parameters) { nautilus_file_unref (parameters->file); g_free (parameters); } static CreateTemplateParameters * create_template_parameters_new (NautilusFile *file, NautilusFilesView *directory_view) { CreateTemplateParameters *result; result = g_new0 (CreateTemplateParameters, 1); result->directory_view = directory_view; nautilus_file_ref (file); result->file = file; return result; } static void create_templates_parameters_free (CreateTemplateParameters *parameters) { nautilus_file_unref (parameters->file); g_free (parameters); } NautilusWindow * nautilus_files_view_get_window (NautilusFilesView *view) { NautilusFilesViewPrivate *priv; priv = nautilus_files_view_get_instance_private (view); return nautilus_window_slot_get_window (priv->slot); } NautilusWindowSlot * nautilus_files_view_get_nautilus_window_slot (NautilusFilesView *view) { NautilusFilesViewPrivate *priv; priv = nautilus_files_view_get_instance_private (view); g_assert (priv->slot != NULL); return priv->slot; } /* Returns the GtkWindow that this directory view occupies, or NULL * if at the moment this directory view is not in a GtkWindow or the * GtkWindow cannot be determined. Primarily used for parenting dialogs. */ static GtkWindow * nautilus_files_view_get_containing_window (NautilusFilesView *view) { GtkWidget *window; g_assert (NAUTILUS_IS_FILES_VIEW (view)); window = gtk_widget_get_ancestor (GTK_WIDGET (view), GTK_TYPE_WINDOW); if (window == NULL) { return NULL; } return GTK_WINDOW (window); } static gboolean nautilus_files_view_confirm_multiple (GtkWindow *parent_window, int count, gboolean tabs) { GtkDialog *dialog; char *prompt; char *detail; int response; if (count <= SILENT_WINDOW_OPEN_LIMIT) { return TRUE; } prompt = _("Are you sure you want to open all files?"); if (tabs) { detail = g_strdup_printf (ngettext ("This will open %'d separate tab.", "This will open %'d separate tabs.", count), count); } else { detail = g_strdup_printf (ngettext ("This will open %'d separate window.", "This will open %'d separate windows.", count), count); } dialog = eel_show_yes_no_dialog (prompt, detail, _("_OK"), _("_Cancel"), parent_window); g_free (detail); response = gtk_dialog_run (dialog); gtk_widget_destroy (GTK_WIDGET (dialog)); return response == GTK_RESPONSE_YES; } static char * get_view_directory (NautilusFilesView *view) { NautilusFilesViewPrivate *priv; char *uri, *path; GFile *f; priv = nautilus_files_view_get_instance_private (view); uri = nautilus_directory_get_uri (priv->model); f = g_file_new_for_uri (uri); path = g_file_get_path (f); g_object_unref (f); g_free (uri); return path; } typedef struct { gchar *uri; gboolean is_update; } PreviewExportData; static void preview_export_data_free (gpointer _data) { PreviewExportData *data = _data; g_free (data->uri); g_free (data); } G_DEFINE_AUTOPTR_CLEANUP_FUNC (PreviewExportData, preview_export_data_free) static void on_window_handle_export (NautilusWindow *window, const char *handle, guint xid, gpointer user_data) { g_autoptr(PreviewExportData) data = user_data; nautilus_previewer_call_show_file (data->uri, handle, xid, !data->is_update); } static void nautilus_files_view_preview (NautilusFilesView *view, PreviewExportData *data) { if (!nautilus_window_export_handle (nautilus_files_view_get_window (view), on_window_handle_export, data)) { /* Let's use a fallback, so at least a preview will be displayed */ nautilus_previewer_call_show_file (data->uri, "x11:0", 0, !data->is_update); } } void nautilus_files_view_preview_files (NautilusFilesView *view, GList *files, GArray *locations) { PreviewExportData *data = g_new0 (PreviewExportData, 1); data->uri = nautilus_file_get_uri (files->data); data->is_update = FALSE; nautilus_files_view_preview (view, data); } void nautilus_files_view_preview_update (NautilusFilesView *view, GList *files) { PreviewExportData *data; if (!nautilus_previewer_is_visible ()) { return; } data = g_new0 (PreviewExportData, 1); data->uri = nautilus_file_get_uri (files->data); data->is_update = TRUE; nautilus_files_view_preview (view, data); } void nautilus_files_view_preview_selection_event (NautilusFilesView *view, GtkDirectionType direction) { NAUTILUS_FILES_VIEW_CLASS (G_OBJECT_GET_CLASS (view))->preview_selection_event (view, direction); } void nautilus_files_view_activate_selection (NautilusFilesView *view) { g_autolist (NautilusFile) selection = NULL; selection = nautilus_view_get_selection (NAUTILUS_VIEW (view)); nautilus_files_view_activate_files (view, selection, 0, TRUE); } void nautilus_files_view_activate_files (NautilusFilesView *view, GList *files, NautilusWindowOpenFlags flags, gboolean confirm_multiple) { NautilusFilesViewPrivate *priv; GList *files_to_extract; GList *files_to_activate; char *path; if (files == NULL) { return; } priv = nautilus_files_view_get_instance_private (view); files_to_extract = nautilus_file_list_filter (files, &files_to_activate, (NautilusFileFilterFunc) nautilus_mime_file_extracts, NULL); if (nautilus_files_view_supports_extract_here (view)) { g_autoptr (GFile) location = NULL; g_autoptr (GFile) parent = NULL; location = nautilus_file_get_location (NAUTILUS_FILE (g_list_first (files)->data)); /* Get a parent from a random file. We assume all files has a common parent. * But don't assume the parent is the view location, since that's not the * case in list view when expand-folder setting is set */ parent = g_file_get_parent (location); extract_files (view, files_to_extract, parent); } else { extract_files_to_chosen_location (view, files_to_extract); } path = get_view_directory (view); nautilus_mime_activate_files (nautilus_files_view_get_containing_window (view), priv->slot, files_to_activate, path, flags, confirm_multiple); g_free (path); g_list_free (files_to_extract); g_list_free (files_to_activate); } void nautilus_files_view_activate_file (NautilusFilesView *view, NautilusFile *file, NautilusWindowOpenFlags flags) { g_autoptr (GList) files = NULL; files = g_list_append (files, file); nautilus_files_view_activate_files (view, files, flags, FALSE); } static void action_open_with_default_application (GSimpleAction *action, GVariant *state, gpointer user_data) { NautilusFilesView *view; view = NAUTILUS_FILES_VIEW (user_data); nautilus_files_view_activate_selection (view); } static void action_open_item_location (GSimpleAction *action, GVariant *state, gpointer user_data) { NautilusFilesView *view; g_autolist (NautilusFile) selection = NULL; NautilusFile *item; GFile *activation_location; NautilusFile *activation_file; NautilusFile *parent; g_autoptr (GFile) parent_location = NULL; view = NAUTILUS_FILES_VIEW (user_data); selection = nautilus_view_get_selection (NAUTILUS_VIEW (view)); if (!selection) { return; } item = NAUTILUS_FILE (selection->data); activation_location = nautilus_file_get_activation_location (item); activation_file = nautilus_file_get (activation_location); parent = nautilus_file_get_parent (activation_file); parent_location = nautilus_file_get_location (parent); if (nautilus_file_is_in_recent (item)) { /* Selection logic will check against a NautilusFile of the * activation uri, not the recent:// one. Fixes bug 784516 */ nautilus_file_unref (item); item = nautilus_file_ref (activation_file); selection->data = item; } nautilus_application_open_location_full (NAUTILUS_APPLICATION (g_application_get_default ()), parent_location, 0, selection, NULL, nautilus_files_view_get_nautilus_window_slot (view)); nautilus_file_unref (parent); nautilus_file_unref (activation_file); g_object_unref (activation_location); } static void action_open_item_new_tab (GSimpleAction *action, GVariant *state, gpointer user_data) { NautilusFilesView *view; g_autolist (NautilusFile) selection = NULL; GtkWindow *window; view = NAUTILUS_FILES_VIEW (user_data); selection = nautilus_view_get_selection (NAUTILUS_VIEW (view)); window = nautilus_files_view_get_containing_window (view); if (nautilus_files_view_confirm_multiple (window, g_list_length (selection), TRUE)) { nautilus_files_view_activate_files (view, selection, NAUTILUS_WINDOW_OPEN_FLAG_NEW_TAB | NAUTILUS_WINDOW_OPEN_FLAG_DONT_MAKE_ACTIVE, FALSE); } } static void app_chooser_dialog_response_cb (GtkDialog *dialog, gint response_id, gpointer user_data) { GtkWindow *parent_window; GList *files; GAppInfo *info; parent_window = user_data; files = g_object_get_data (G_OBJECT (dialog), "directory-view:files"); if (response_id != GTK_RESPONSE_OK) { goto out; } info = gtk_app_chooser_get_app_info (GTK_APP_CHOOSER (dialog)); g_signal_emit_by_name (nautilus_signaller_get_current (), "mime-data-changed"); nautilus_launch_application (info, files, parent_window); g_object_unref (info); out: gtk_widget_destroy (GTK_WIDGET (dialog)); } static void choose_program (NautilusFilesView *view, GList *files) { GtkWidget *dialog; g_autofree gchar *mime_type = NULL; GtkWindow *parent_window; g_assert (NAUTILUS_IS_FILES_VIEW (view)); mime_type = nautilus_file_get_mime_type (files->data); parent_window = nautilus_files_view_get_containing_window (view); dialog = gtk_app_chooser_dialog_new_for_content_type (parent_window, GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT | GTK_DIALOG_USE_HEADER_BAR, mime_type); g_object_set_data_full (G_OBJECT (dialog), "directory-view:files", files, (GDestroyNotify) nautilus_file_list_free); gtk_widget_show (dialog); g_signal_connect_object (dialog, "response", G_CALLBACK (app_chooser_dialog_response_cb), parent_window, 0); } static void open_with_other_program (NautilusFilesView *view) { g_autolist (NautilusFile) selection = NULL; g_assert (NAUTILUS_IS_FILES_VIEW (view)); selection = nautilus_view_get_selection (NAUTILUS_VIEW (view)); choose_program (view, g_steal_pointer (&selection)); } static void action_open_with_other_application (GSimpleAction *action, GVariant *state, gpointer user_data) { g_assert (NAUTILUS_IS_FILES_VIEW (user_data)); open_with_other_program (NAUTILUS_FILES_VIEW (user_data)); } static void trash_or_delete_selected_files (NautilusFilesView *view) { NautilusFilesViewPrivate *priv; priv = nautilus_files_view_get_instance_private (view); /* This might be rapidly called multiple times for the same selection * when using keybindings. So we remember if the current selection * was already removed (but the view doesn't know about it yet). */ if (!priv->selection_was_removed) { g_autolist (NautilusFile) selection = NULL; selection = nautilus_files_view_get_selection_for_file_transfer (view); trash_or_delete_files (nautilus_files_view_get_containing_window (view), selection, view); priv->selection_was_removed = TRUE; } } static void action_move_to_trash (GSimpleAction *action, GVariant *state, gpointer user_data) { trash_or_delete_selected_files (NAUTILUS_FILES_VIEW (user_data)); } static void action_remove_from_recent (GSimpleAction *action, GVariant *state, gpointer user_data) { /* TODO:implement a set of functions for this, is very confusing to * call trash_or_delete_file to remove from recent, even if it does like * that not deleting/moving the files to trash */ trash_or_delete_selected_files (NAUTILUS_FILES_VIEW (user_data)); } static void delete_selected_files (NautilusFilesView *view) { GList *selection; GList *node; GList *locations; selection = nautilus_files_view_get_selection_for_file_transfer (view); if (selection == NULL) { return; } locations = NULL; for (node = selection; node != NULL; node = node->next) { locations = g_list_prepend (locations, nautilus_file_get_location ((NautilusFile *) node->data)); } locations = g_list_reverse (locations); nautilus_file_operations_delete_async (locations, nautilus_files_view_get_containing_window (view), NULL, NULL); g_list_free_full (locations, g_object_unref); nautilus_file_list_free (selection); } static void action_delete (GSimpleAction *action, GVariant *state, gpointer user_data) { delete_selected_files (NAUTILUS_FILES_VIEW (user_data)); } static void action_star (GSimpleAction *action, GVariant *state, gpointer user_data) { NautilusFilesView *view; g_autolist (NautilusFile) selection = NULL; NautilusFilesViewPrivate *priv; view = NAUTILUS_FILES_VIEW (user_data); priv = nautilus_files_view_get_instance_private (view); selection = nautilus_view_get_selection (NAUTILUS_VIEW (view)); nautilus_tag_manager_star_files (priv->tag_manager, G_OBJECT (view), selection, NULL, priv->starred_cancellable); } static void action_unstar (GSimpleAction *action, GVariant *state, gpointer user_data) { NautilusFilesView *view; g_autolist (NautilusFile) selection = NULL; NautilusFilesViewPrivate *priv; view = NAUTILUS_FILES_VIEW (user_data); priv = nautilus_files_view_get_instance_private (view); selection = nautilus_view_get_selection (NAUTILUS_VIEW (view)); nautilus_tag_manager_unstar_files (priv->tag_manager, G_OBJECT (view), selection, NULL, priv->starred_cancellable); } static void action_restore_from_trash (GSimpleAction *action, GVariant *state, gpointer user_data) { NautilusFilesView *view; GList *selection; view = NAUTILUS_FILES_VIEW (user_data); selection = nautilus_files_view_get_selection_for_file_transfer (view); nautilus_restore_files_from_trash (selection, nautilus_files_view_get_containing_window (view)); nautilus_file_list_free (selection); } static void action_select_all (GSimpleAction *action, GVariant *state, gpointer user_data) { NautilusFilesView *view; g_assert (NAUTILUS_IS_FILES_VIEW (user_data)); view = NAUTILUS_FILES_VIEW (user_data); nautilus_files_view_select_all (view); } static void action_invert_selection (GSimpleAction *action, GVariant *state, gpointer user_data) { g_assert (NAUTILUS_IS_FILES_VIEW (user_data)); nautilus_files_view_invert_selection (user_data); } static void pattern_select_response_cb (GtkWidget *dialog, int response, gpointer user_data) { NautilusFilesView *view; NautilusDirectory *directory; GtkWidget *entry; GList *selection; view = NAUTILUS_FILES_VIEW (user_data); switch (response) { case GTK_RESPONSE_OK: { entry = g_object_get_data (G_OBJECT (dialog), "entry"); directory = nautilus_files_view_get_model (view); selection = nautilus_directory_match_pattern (directory, gtk_entry_get_text (GTK_ENTRY (entry))); nautilus_files_view_call_set_selection (view, selection); nautilus_files_view_reveal_selection (view); if (selection) { nautilus_file_list_free (selection); } /* fall through */ } case GTK_RESPONSE_NONE: case GTK_RESPONSE_DELETE_EVENT: case GTK_RESPONSE_CANCEL: { gtk_widget_destroy (GTK_WIDGET (dialog)); } break; default: g_assert_not_reached (); } } static void select_pattern (NautilusFilesView *view) { GtkWidget *dialog; GtkWidget *label; GtkWidget *example; GtkWidget *grid; GtkWidget *entry; char *example_pattern; dialog = gtk_dialog_new_with_buttons (_("Select Items Matching"), nautilus_files_view_get_containing_window (view), GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT | GTK_DIALOG_USE_HEADER_BAR, _("_Cancel"), GTK_RESPONSE_CANCEL, _("_Select"), GTK_RESPONSE_OK, NULL); gtk_dialog_set_default_response (GTK_DIALOG (dialog), GTK_RESPONSE_OK); gtk_container_set_border_width (GTK_CONTAINER (dialog), 5); gtk_box_set_spacing (GTK_BOX (gtk_dialog_get_content_area (GTK_DIALOG (dialog))), 2); label = gtk_label_new_with_mnemonic (_("_Pattern:")); gtk_widget_set_halign (label, GTK_ALIGN_START); example = gtk_label_new (NULL); gtk_widget_set_halign (example, GTK_ALIGN_START); example_pattern = g_strdup_printf ("%s%s ", _("Examples: "), "*.png, file\?\?.txt, pict*.\?\?\?"); gtk_label_set_markup (GTK_LABEL (example), example_pattern); g_free (example_pattern); entry = gtk_entry_new (); gtk_entry_set_activates_default (GTK_ENTRY (entry), TRUE); gtk_widget_set_hexpand (entry, TRUE); grid = gtk_grid_new (); g_object_set (grid, "orientation", GTK_ORIENTATION_VERTICAL, "border-width", 6, "row-spacing", 6, "column-spacing", 12, NULL); gtk_container_add (GTK_CONTAINER (grid), label); gtk_grid_attach_next_to (GTK_GRID (grid), entry, label, GTK_POS_RIGHT, 1, 1); gtk_grid_attach_next_to (GTK_GRID (grid), example, entry, GTK_POS_BOTTOM, 1, 1); gtk_label_set_mnemonic_widget (GTK_LABEL (label), entry); gtk_widget_show_all (grid); gtk_container_add (GTK_CONTAINER (gtk_dialog_get_content_area (GTK_DIALOG (dialog))), grid); g_object_set_data (G_OBJECT (dialog), "entry", entry); g_signal_connect (dialog, "response", G_CALLBACK (pattern_select_response_cb), view); gtk_widget_show_all (dialog); } static void action_select_pattern (GSimpleAction *action, GVariant *state, gpointer user_data) { g_assert (NAUTILUS_IS_FILES_VIEW (user_data)); select_pattern (user_data); } typedef struct { NautilusFilesView *directory_view; GHashTable *added_locations; GList *selection; } NewFolderData; typedef struct { NautilusFilesView *directory_view; GHashTable *to_remove_locations; NautilusFile *new_folder; } NewFolderSelectionData; static void track_newly_added_locations (NautilusFilesView *view, GList *new_files, gpointer user_data) { GHashTable *added_locations; added_locations = user_data; while (new_files) { NautilusFile *new_file; new_file = NAUTILUS_FILE (new_files->data); g_hash_table_add (added_locations, nautilus_file_get_location (new_file)); new_files = new_files->next; } } static void new_folder_done (GFile *new_folder, gboolean success, gpointer user_data) { NautilusFilesView *directory_view; NautilusFilesViewPrivate *priv; NautilusFile *file; NewFolderData *data; data = (NewFolderData *) user_data; directory_view = data->directory_view; priv = nautilus_files_view_get_instance_private (directory_view); if (directory_view == NULL) { goto fail; } g_signal_handlers_disconnect_by_func (directory_view, G_CALLBACK (track_newly_added_locations), data->added_locations); if (new_folder == NULL) { goto fail; } file = nautilus_file_get (new_folder); if (data->selection != NULL) { GList *uris, *l; char *target_uri; uris = NULL; for (l = data->selection; l != NULL; l = l->next) { uris = g_list_prepend (uris, nautilus_file_get_uri ((NautilusFile *) l->data)); } uris = g_list_reverse (uris); target_uri = nautilus_file_get_uri (file); nautilus_files_view_move_copy_items (directory_view, uris, target_uri, GDK_ACTION_MOVE); g_list_free_full (uris, g_free); g_free (target_uri); } if (g_hash_table_contains (data->added_locations, new_folder)) { /* The file was already added */ nautilus_files_view_select_file (directory_view, file); nautilus_files_view_reveal_selection (directory_view); } else { g_hash_table_insert (priv->pending_reveal, file, GUINT_TO_POINTER (TRUE)); } nautilus_file_unref (file); fail: g_hash_table_destroy (data->added_locations); if (data->directory_view != NULL) { g_object_remove_weak_pointer (G_OBJECT (data->directory_view), (gpointer *) &data->directory_view); } nautilus_file_list_free (data->selection); g_free (data); } static NewFolderData * new_folder_data_new (NautilusFilesView *directory_view, gboolean with_selection) { NewFolderData *data; data = g_new (NewFolderData, 1); data->directory_view = directory_view; data->added_locations = g_hash_table_new_full (g_file_hash, (GEqualFunc) g_file_equal, g_object_unref, NULL); if (with_selection) { data->selection = nautilus_files_view_get_selection_for_file_transfer (directory_view); } else { data->selection = NULL; } g_object_add_weak_pointer (G_OBJECT (data->directory_view), (gpointer *) &data->directory_view); return data; } static GdkRectangle * nautilus_files_view_compute_rename_popover_pointing_to (NautilusFilesView *view) { return NAUTILUS_FILES_VIEW_CLASS (G_OBJECT_GET_CLASS (view))->compute_rename_popover_pointing_to (view); } static void disconnect_rename_controller_signals (NautilusFilesView *self) { NautilusFilesViewPrivate *priv; g_assert (NAUTILUS_IS_FILES_VIEW (self)); priv = nautilus_files_view_get_instance_private (self); if (priv->name_accepted_handler_id != 0) { g_signal_handler_disconnect (priv->rename_file_controller, priv->name_accepted_handler_id); priv->name_accepted_handler_id = 0; } if (priv->cancelled_handler_id != 0) { g_signal_handler_disconnect (priv->rename_file_controller, priv->cancelled_handler_id); priv->cancelled_handler_id = 0; } } static void rename_file_popover_controller_on_name_accepted (NautilusFileNameWidgetController *controller, gpointer user_data) { NautilusFilesView *view; NautilusFile *target_file; g_autofree gchar *name = NULL; NautilusFilesViewPrivate *priv; view = NAUTILUS_FILES_VIEW (user_data); priv = nautilus_files_view_get_instance_private (view); name = nautilus_file_name_widget_controller_get_new_name (controller); target_file = nautilus_rename_file_popover_controller_get_target_file (priv->rename_file_controller); /* Put it on the queue for reveal after the view acknowledges the change */ g_hash_table_insert (priv->pending_reveal, target_file, GUINT_TO_POINTER (FALSE)); nautilus_rename_file (target_file, name, NULL, NULL); disconnect_rename_controller_signals (view); } static void rename_file_popover_controller_on_cancelled (NautilusFileNameWidgetController *controller, gpointer user_data) { NautilusFilesView *view; view = NAUTILUS_FILES_VIEW (user_data); disconnect_rename_controller_signals (view); } static void nautilus_files_view_rename_file_popover_new (NautilusFilesView *view, NautilusFile *target_file) { GdkRectangle *pointing_to; NautilusFilesViewPrivate *priv; priv = nautilus_files_view_get_instance_private (view); /* Make sure the whole item is visible. The selection is a single item, the * one to rename with the popover, so we can use reveal_selection() for this. */ nautilus_files_view_reveal_selection (view); pointing_to = nautilus_files_view_compute_rename_popover_pointing_to (view); nautilus_rename_file_popover_controller_show_for_file (priv->rename_file_controller, target_file, pointing_to, GTK_WIDGET (view)); priv->name_accepted_handler_id = g_signal_connect (priv->rename_file_controller, "name-accepted", G_CALLBACK (rename_file_popover_controller_on_name_accepted), view); priv->cancelled_handler_id = g_signal_connect (priv->rename_file_controller, "cancelled", G_CALLBACK (rename_file_popover_controller_on_cancelled), view); } static void new_folder_dialog_controller_on_name_accepted (NautilusFileNameWidgetController *controller, gpointer user_data) { NautilusFilesView *view; NautilusFilesViewPrivate *priv; NewFolderData *data; g_autofree gchar *parent_uri = NULL; g_autofree gchar *name = NULL; NautilusFile *parent; gboolean with_selection; view = NAUTILUS_FILES_VIEW (user_data); priv = nautilus_files_view_get_instance_private (view); with_selection = nautilus_new_folder_dialog_controller_get_with_selection (priv->new_folder_controller); data = new_folder_data_new (view, with_selection); name = nautilus_file_name_widget_controller_get_new_name (controller); g_signal_connect_data (view, "add-files", G_CALLBACK (track_newly_added_locations), data->added_locations, (GClosureNotify) NULL, G_CONNECT_AFTER); parent_uri = nautilus_files_view_get_backing_uri (view); parent = nautilus_file_get_by_uri (parent_uri); nautilus_file_operations_new_folder (GTK_WIDGET (view), parent_uri, name, new_folder_done, data); g_clear_object (&priv->new_folder_controller); /* After the dialog is destroyed the focus, is probably in the menu item * that created the dialog, but we want the focus to be in the newly created * folder. */ gtk_widget_grab_focus (GTK_WIDGET (view)); g_object_unref (parent); } static void new_folder_dialog_controller_on_cancelled (NautilusNewFolderDialogController *controller, gpointer user_data) { NautilusFilesView *view; NautilusFilesViewPrivate *priv; view = NAUTILUS_FILES_VIEW (user_data); priv = nautilus_files_view_get_instance_private (view); g_clear_object (&priv->new_folder_controller); } static void nautilus_files_view_new_folder_dialog_new (NautilusFilesView *view, gboolean with_selection) { g_autoptr (NautilusDirectory) containing_directory = NULL; NautilusFilesViewPrivate *priv; g_autofree char *uri = NULL; g_autofree char *common_prefix = NULL; priv = nautilus_files_view_get_instance_private (view); if (priv->new_folder_controller != NULL) { return; } uri = nautilus_files_view_get_backing_uri (view); containing_directory = nautilus_directory_get_by_uri (uri); if (with_selection) { g_autolist (NautilusFile) selection = NULL; selection = nautilus_view_get_selection (NAUTILUS_VIEW (view)); common_prefix = nautilus_get_common_filename_prefix (selection, MIN_COMMON_FILENAME_PREFIX_LENGTH); } priv->new_folder_controller = nautilus_new_folder_dialog_controller_new (nautilus_files_view_get_containing_window (view), containing_directory, with_selection, common_prefix); g_signal_connect (priv->new_folder_controller, "name-accepted", (GCallback) new_folder_dialog_controller_on_name_accepted, view); g_signal_connect (priv->new_folder_controller, "cancelled", (GCallback) new_folder_dialog_controller_on_cancelled, view); } typedef struct { NautilusFilesView *view; GHashTable *added_locations; } CompressData; static void compress_done (GFile *new_file, gboolean success, gpointer user_data) { CompressData *data; NautilusFilesView *view; NautilusFilesViewPrivate *priv; NautilusFile *file; data = user_data; view = data->view; if (view == NULL) { goto out; } priv = nautilus_files_view_get_instance_private (view); g_signal_handlers_disconnect_by_func (view, G_CALLBACK (track_newly_added_locations), data->added_locations); if (!success) { goto out; } file = nautilus_file_get (new_file); if (g_hash_table_contains (data->added_locations, new_file)) { /* The file was already added */ nautilus_files_view_select_file (view, file); nautilus_files_view_reveal_selection (view); } else { g_hash_table_insert (priv->pending_reveal, file, GUINT_TO_POINTER (TRUE)); } nautilus_file_unref (file); out: g_hash_table_destroy (data->added_locations); if (data->view != NULL) { g_object_remove_weak_pointer (G_OBJECT (data->view), (gpointer *) &data->view); } g_free (data); } static void compress_dialog_controller_on_name_accepted (NautilusFileNameWidgetController *controller, gpointer user_data) { NautilusFilesView *view; g_autofree gchar *name = NULL; GList *selection; GList *source_files = NULL; GList *l; CompressData *data; g_autoptr (GFile) output = NULL; g_autoptr (GFile) parent = NULL; NautilusCompressionFormat compression_format; NautilusFilesViewPrivate *priv; AutoarFormat format; AutoarFilter filter; view = NAUTILUS_FILES_VIEW (user_data); priv = nautilus_files_view_get_instance_private (view); selection = nautilus_files_view_get_selection_for_file_transfer (view); for (l = selection; l != NULL; l = l->next) { source_files = g_list_prepend (source_files, nautilus_file_get_location (l->data)); } source_files = g_list_reverse (source_files); name = nautilus_file_name_widget_controller_get_new_name (controller); /* Get a parent from a random file. We assume all files has a common parent. * But don't assume the parent is the view location, since that's not the * case in list view when expand-folder setting is set */ parent = g_file_get_parent (G_FILE (g_list_first (source_files)->data)); output = g_file_get_child (parent, name); data = g_new (CompressData, 1); data->view = view; data->added_locations = g_hash_table_new_full (g_file_hash, (GEqualFunc) g_file_equal, g_object_unref, NULL); g_object_add_weak_pointer (G_OBJECT (data->view), (gpointer *) &data->view); g_signal_connect_data (view, "add-files", G_CALLBACK (track_newly_added_locations), data->added_locations, NULL, G_CONNECT_AFTER); compression_format = g_settings_get_enum (nautilus_compression_preferences, NAUTILUS_PREFERENCES_DEFAULT_COMPRESSION_FORMAT); switch (compression_format) { case NAUTILUS_COMPRESSION_ZIP: { format = AUTOAR_FORMAT_ZIP; filter = AUTOAR_FILTER_NONE; } break; case NAUTILUS_COMPRESSION_TAR_XZ: { format = AUTOAR_FORMAT_TAR; filter = AUTOAR_FILTER_XZ; } break; case NAUTILUS_COMPRESSION_7ZIP: { format = AUTOAR_FORMAT_7ZIP; filter = AUTOAR_FILTER_NONE; } break; default: g_assert_not_reached (); } nautilus_file_operations_compress (source_files, output, format, filter, nautilus_files_view_get_containing_window (view), compress_done, data); nautilus_file_list_free (selection); g_list_free_full (source_files, g_object_unref); g_clear_object (&priv->compress_controller); } static void compress_dialog_controller_on_cancelled (NautilusNewFolderDialogController *controller, gpointer user_data) { NautilusFilesView *view; NautilusFilesViewPrivate *priv; view = NAUTILUS_FILES_VIEW (user_data); priv = nautilus_files_view_get_instance_private (view); g_clear_object (&priv->compress_controller); } static void nautilus_files_view_compress_dialog_new (NautilusFilesView *view) { NautilusDirectory *containing_directory; NautilusFilesViewPrivate *priv; g_autolist (NautilusFile) selection = NULL; g_autofree char *common_prefix = NULL; priv = nautilus_files_view_get_instance_private (view); if (priv->compress_controller != NULL) { return; } containing_directory = nautilus_directory_get_by_uri (nautilus_files_view_get_backing_uri (view)); selection = nautilus_view_get_selection (NAUTILUS_VIEW (view)); if (g_list_length (selection) == 1) { g_autofree char *display_name = NULL; display_name = nautilus_file_get_display_name (selection->data); if (nautilus_file_is_directory (selection->data)) { common_prefix = g_steal_pointer (&display_name); } else { common_prefix = eel_filename_strip_extension (display_name); } } else { common_prefix = nautilus_get_common_filename_prefix (selection, MIN_COMMON_FILENAME_PREFIX_LENGTH); } priv->compress_controller = nautilus_compress_dialog_controller_new (nautilus_files_view_get_containing_window (view), containing_directory, common_prefix); g_signal_connect (priv->compress_controller, "name-accepted", (GCallback) compress_dialog_controller_on_name_accepted, view); g_signal_connect (priv->compress_controller, "cancelled", (GCallback) compress_dialog_controller_on_cancelled, view); } static void nautilus_files_view_new_folder (NautilusFilesView *directory_view, gboolean with_selection) { nautilus_files_view_new_folder_dialog_new (directory_view, with_selection); } static NewFolderData * setup_new_folder_data (NautilusFilesView *directory_view) { NewFolderData *data; data = new_folder_data_new (directory_view, FALSE); g_signal_connect_data (directory_view, "add-files", G_CALLBACK (track_newly_added_locations), data->added_locations, (GClosureNotify) NULL, G_CONNECT_AFTER); return data; } void nautilus_files_view_new_file_with_initial_contents (NautilusFilesView *view, const char *parent_uri, const char *filename, const char *initial_contents, int length) { NewFolderData *data; g_assert (parent_uri != NULL); data = setup_new_folder_data (view); nautilus_file_operations_new_file (GTK_WIDGET (view), parent_uri, filename, initial_contents, length, new_folder_done, data); } static void nautilus_files_view_new_file (NautilusFilesView *directory_view, const char *parent_uri, NautilusFile *source) { NewFolderData *data; char *source_uri; char *container_uri; container_uri = NULL; if (parent_uri == NULL) { container_uri = nautilus_files_view_get_backing_uri (directory_view); g_assert (container_uri != NULL); } if (source == NULL) { nautilus_files_view_new_file_with_initial_contents (directory_view, parent_uri != NULL ? parent_uri : container_uri, NULL, NULL, 0); g_free (container_uri); return; } g_return_if_fail (nautilus_file_is_local (source)); data = setup_new_folder_data (directory_view); source_uri = nautilus_file_get_uri (source); nautilus_file_operations_new_file_from_template (GTK_WIDGET (directory_view), parent_uri != NULL ? parent_uri : container_uri, NULL, source_uri, new_folder_done, data); g_free (source_uri); g_free (container_uri); } static void action_new_folder (GSimpleAction *action, GVariant *state, gpointer user_data) { g_assert (NAUTILUS_IS_FILES_VIEW (user_data)); nautilus_files_view_new_folder (NAUTILUS_FILES_VIEW (user_data), FALSE); } static void action_new_folder_with_selection (GSimpleAction *action, GVariant *state, gpointer user_data) { g_assert (NAUTILUS_IS_FILES_VIEW (user_data)); nautilus_files_view_new_folder (NAUTILUS_FILES_VIEW (user_data), TRUE); } static void action_properties (GSimpleAction *action, GVariant *state, gpointer user_data) { NautilusFilesView *view; NautilusFilesViewPrivate *priv; g_autolist (NautilusFile) selection = NULL; GList *files; g_assert (NAUTILUS_IS_FILES_VIEW (user_data)); view = NAUTILUS_FILES_VIEW (user_data); priv = nautilus_files_view_get_instance_private (view); selection = nautilus_view_get_selection (NAUTILUS_VIEW (view)); if (g_list_length (selection) == 0) { if (priv->directory_as_file != NULL) { files = g_list_append (NULL, nautilus_file_ref (priv->directory_as_file)); nautilus_properties_window_present (files, GTK_WIDGET (view), NULL, NULL, NULL); nautilus_file_list_free (files); } } else { nautilus_properties_window_present (selection, GTK_WIDGET (view), NULL, NULL, NULL); } } static void action_current_dir_properties (GSimpleAction *action, GVariant *state, gpointer user_data) { NautilusFilesView *view; NautilusFilesViewPrivate *priv; GList *files; g_return_if_fail (NAUTILUS_IS_FILES_VIEW (user_data)); view = NAUTILUS_FILES_VIEW (user_data); priv = nautilus_files_view_get_instance_private (view); if (priv->directory_as_file != NULL) { files = g_list_append (NULL, nautilus_file_ref (priv->directory_as_file)); nautilus_properties_window_present (files, GTK_WIDGET (view), NULL, NULL, NULL); nautilus_file_list_free (files); } } static void nautilus_files_view_set_show_hidden_files (NautilusFilesView *view, gboolean show_hidden) { NautilusFilesViewPrivate *priv; priv = nautilus_files_view_get_instance_private (view); if (priv->ignore_hidden_file_preferences) { return; } if (show_hidden != priv->show_hidden_files) { priv->show_hidden_files = show_hidden; g_settings_set_boolean (gtk_filechooser_preferences, NAUTILUS_PREFERENCES_SHOW_HIDDEN_FILES, show_hidden); if (priv->model != NULL) { load_directory (view, priv->model); } } } static void action_show_hidden_files (GSimpleAction *action, GVariant *state, gpointer user_data) { gboolean show_hidden; NautilusFilesView *view; g_assert (NAUTILUS_IS_FILES_VIEW (user_data)); view = NAUTILUS_FILES_VIEW (user_data); show_hidden = g_variant_get_boolean (state); nautilus_files_view_set_show_hidden_files (view, show_hidden); g_simple_action_set_state (action, state); } static void action_zoom_in (GSimpleAction *action, GVariant *state, gpointer user_data) { NautilusFilesView *view; g_assert (NAUTILUS_IS_FILES_VIEW (user_data)); view = NAUTILUS_FILES_VIEW (user_data); nautilus_files_view_bump_zoom_level (view, 1); } static void action_zoom_out (GSimpleAction *action, GVariant *state, gpointer user_data) { NautilusFilesView *view; g_assert (NAUTILUS_IS_FILES_VIEW (user_data)); view = NAUTILUS_FILES_VIEW (user_data); nautilus_files_view_bump_zoom_level (view, -1); } static void action_zoom_standard (GSimpleAction *action, GVariant *state, gpointer user_data) { nautilus_files_view_restore_standard_zoom_level (user_data); } static void action_open_item_new_window (GSimpleAction *action, GVariant *state, gpointer user_data) { NautilusFilesView *view; GtkWindow *window; GList *selection; view = NAUTILUS_FILES_VIEW (user_data); selection = nautilus_view_get_selection (NAUTILUS_VIEW (view)); window = GTK_WINDOW (nautilus_files_view_get_containing_window (view)); if (nautilus_files_view_confirm_multiple (window, g_list_length (selection), TRUE)) { g_list_foreach (selection, open_one_in_new_window, view); } nautilus_file_list_free (selection); } static void handle_clipboard_data (NautilusFilesView *view, const gchar *selection_data, char *destination_uri, GdkDragAction action) { GList *item_uris; item_uris = nautilus_clipboard_get_uri_list_from_selection_data (selection_data); if (item_uris != NULL && destination_uri != NULL) { nautilus_files_view_move_copy_items (view, item_uris, destination_uri, action); /* If items are cut then remove from clipboard */ if (action == GDK_ACTION_MOVE) { gtk_clipboard_clear (nautilus_clipboard_get (GTK_WIDGET (view))); } g_list_free_full (item_uris, g_free); } } static void paste_clipboard_data (NautilusFilesView *view, const gchar *selection_data, char *destination_uri) { GdkDragAction action; if (nautilus_clipboard_is_cut_from_selection_data (selection_data)) { action = GDK_ACTION_MOVE; } else { action = GDK_ACTION_COPY; } handle_clipboard_data (view, selection_data, destination_uri, action); } static void paste_clipboard_text_received_callback (GtkClipboard *clipboard, const gchar *selection_data, gpointer data) { NautilusFilesView *view; NautilusFilesViewPrivate *priv; char *view_uri; view = NAUTILUS_FILES_VIEW (data); priv = nautilus_files_view_get_instance_private (view); view_uri = nautilus_files_view_get_backing_uri (view); if (priv->slot != NULL) { paste_clipboard_data (view, selection_data, view_uri); } g_free (view_uri); g_object_unref (view); } static void paste_files (NautilusFilesView *view) { GtkClipboard *clipboard; clipboard = nautilus_clipboard_get (GTK_WIDGET (view)); /* Performing an async request of clipboard contents, corresponding unref * is in the callback. */ g_object_ref (view); gtk_clipboard_request_text (clipboard, paste_clipboard_text_received_callback, view); } static void action_paste_files (GSimpleAction *action, GVariant *state, gpointer user_data) { NautilusFilesView *view; view = NAUTILUS_FILES_VIEW (user_data); paste_files (view); } static void action_paste_files_accel (GSimpleAction *action, GVariant *state, gpointer user_data) { NautilusFilesView *view; view = NAUTILUS_FILES_VIEW (user_data); if (nautilus_files_view_is_read_only (view)) { show_dialog (_("Could not paste files"), _("Permissions do not allow pasting files in this directory"), nautilus_files_view_get_containing_window (view), GTK_MESSAGE_ERROR); } else { paste_files (view); } } static void create_links_clipboard_received_callback (GtkClipboard *clipboard, const gchar *selection_data, gpointer data) { NautilusFilesView *view; NautilusFilesViewPrivate *priv; char *view_uri; view = NAUTILUS_FILES_VIEW (data); priv = nautilus_files_view_get_instance_private (view); view_uri = nautilus_files_view_get_backing_uri (view); if (priv->slot != NULL) { handle_clipboard_data (view, selection_data, view_uri, GDK_ACTION_LINK); } g_free (view_uri); g_object_unref (view); } static void action_create_links (GSimpleAction *action, GVariant *state, gpointer user_data) { NautilusFilesView *view; g_assert (NAUTILUS_IS_FILES_VIEW (user_data)); view = NAUTILUS_FILES_VIEW (user_data); g_object_ref (view); gtk_clipboard_request_text (nautilus_clipboard_get (GTK_WIDGET (view)), create_links_clipboard_received_callback, view); } static void click_policy_changed_callback (gpointer callback_data) { NautilusFilesView *view; view = NAUTILUS_FILES_VIEW (callback_data); NAUTILUS_FILES_VIEW_CLASS (G_OBJECT_GET_CLASS (view))->click_policy_changed (view); } gboolean nautilus_files_view_should_sort_directories_first (NautilusFilesView *view) { NautilusFilesViewPrivate *priv; gboolean is_search; priv = nautilus_files_view_get_instance_private (view); is_search = nautilus_view_is_searching (NAUTILUS_VIEW (view)); return priv->sort_directories_first && !is_search; } static void sort_directories_first_changed_callback (gpointer callback_data) { NautilusFilesView *view; NautilusFilesViewPrivate *priv; gboolean preference_value; view = NAUTILUS_FILES_VIEW (callback_data); priv = nautilus_files_view_get_instance_private (view); preference_value = g_settings_get_boolean (gtk_filechooser_preferences, NAUTILUS_PREFERENCES_SORT_DIRECTORIES_FIRST); if (preference_value != priv->sort_directories_first) { priv->sort_directories_first = preference_value; NAUTILUS_FILES_VIEW_CLASS (G_OBJECT_GET_CLASS (view))->sort_directories_first_changed (view); } } static void show_hidden_files_changed_callback (gpointer callback_data) { NautilusFilesView *view; NautilusFilesViewPrivate *priv; gboolean preference_value; view = NAUTILUS_FILES_VIEW (callback_data); priv = nautilus_files_view_get_instance_private (view); preference_value = g_settings_get_boolean (gtk_filechooser_preferences, NAUTILUS_PREFERENCES_SHOW_HIDDEN_FILES); nautilus_files_view_set_show_hidden_files (view, preference_value); if (priv->active) { schedule_update_context_menus (view); } } static gboolean set_up_scripts_directory_global (void) { g_autofree gchar *old_scripts_directory_path = NULL; g_autoptr (GFile) old_scripts_directory = NULL; g_autofree gchar *scripts_directory_path = NULL; g_autoptr (GFile) scripts_directory = NULL; const char *override; GFileType file_type; g_autoptr (GError) error = NULL; if (scripts_directory_uri != NULL) { return TRUE; } scripts_directory_path = nautilus_get_scripts_directory_path (); override = g_getenv ("GNOME22_USER_DIR"); if (override) { old_scripts_directory_path = g_build_filename (override, "nautilus-scripts", NULL); } else { old_scripts_directory_path = g_build_filename (g_get_home_dir (), ".gnome2", "nautilus-scripts", NULL); } old_scripts_directory = g_file_new_for_path (old_scripts_directory_path); scripts_directory = g_file_new_for_path (scripts_directory_path); file_type = g_file_query_file_type (old_scripts_directory, G_FILE_QUERY_INFO_NONE, NULL); if (file_type == G_FILE_TYPE_DIRECTORY && !g_file_query_exists (scripts_directory, NULL)) { g_autoptr (GFile) updated = NULL; const char *message; /* test if we already attempted to migrate first */ updated = g_file_get_child (old_scripts_directory, "DEPRECATED-DIRECTORY"); message = _("Nautilus 3.6 deprecated this directory and tried migrating " "this configuration to ~/.local/share/nautilus"); if (!g_file_query_exists (updated, NULL)) { g_autoptr (GFile) parent = NULL; parent = g_file_get_parent (scripts_directory); g_file_make_directory_with_parents (parent, NULL, &error); if (error == NULL || g_error_matches (error, G_IO_ERROR, G_IO_ERROR_EXISTS)) { g_clear_error (&error); g_file_set_attribute_uint32 (parent, G_FILE_ATTRIBUTE_UNIX_MODE, S_IRWXU, G_FILE_QUERY_INFO_NONE, NULL, NULL); g_file_move (old_scripts_directory, scripts_directory, G_FILE_COPY_NONE, NULL, NULL, NULL, &error); if (error == NULL) { g_file_replace_contents (updated, message, strlen (message), NULL, FALSE, G_FILE_CREATE_PRIVATE, NULL, NULL, NULL); } } g_clear_error (&error); } } g_file_make_directory_with_parents (scripts_directory, NULL, &error); if (error == NULL || g_error_matches (error, G_IO_ERROR, G_IO_ERROR_EXISTS)) { g_file_set_attribute_uint32 (scripts_directory, G_FILE_ATTRIBUTE_UNIX_MODE, S_IRWXU, G_FILE_QUERY_INFO_NONE, NULL, NULL); scripts_directory_uri = g_file_get_uri (scripts_directory); scripts_directory_uri_length = strlen (scripts_directory_uri); } return scripts_directory_uri != NULL; } static void scripts_added_or_changed_callback (NautilusDirectory *directory, GList *files, gpointer callback_data) { NautilusFilesView *view; NautilusFilesViewPrivate *priv; view = NAUTILUS_FILES_VIEW (callback_data); priv = nautilus_files_view_get_instance_private (view); if (priv->active) { schedule_update_context_menus (view); } } static void templates_added_or_changed_callback (NautilusDirectory *directory, GList *files, gpointer callback_data) { NautilusFilesView *view; NautilusFilesViewPrivate *priv; view = NAUTILUS_FILES_VIEW (callback_data); priv = nautilus_files_view_get_instance_private (view); if (priv->active) { schedule_update_context_menus (view); } } static void add_directory_to_directory_list (NautilusFilesView *view, NautilusDirectory *directory, GList **directory_list, GCallback changed_callback) { NautilusFileAttributes attributes; if (g_list_find (*directory_list, directory) == NULL) { nautilus_directory_ref (directory); attributes = NAUTILUS_FILE_ATTRIBUTES_FOR_ICON | NAUTILUS_FILE_ATTRIBUTE_INFO | NAUTILUS_FILE_ATTRIBUTE_DIRECTORY_ITEM_COUNT; nautilus_directory_file_monitor_add (directory, directory_list, FALSE, attributes, (NautilusDirectoryCallback) changed_callback, view); g_signal_connect_object (directory, "files-added", G_CALLBACK (changed_callback), view, 0); g_signal_connect_object (directory, "files-changed", G_CALLBACK (changed_callback), view, 0); *directory_list = g_list_append (*directory_list, directory); } } static void remove_directory_from_directory_list (NautilusFilesView *view, NautilusDirectory *directory, GList **directory_list, GCallback changed_callback) { *directory_list = g_list_remove (*directory_list, directory); g_signal_handlers_disconnect_by_func (directory, G_CALLBACK (changed_callback), view); nautilus_directory_file_monitor_remove (directory, directory_list); nautilus_directory_unref (directory); } static void add_directory_to_scripts_directory_list (NautilusFilesView *view, NautilusDirectory *directory) { NautilusFilesViewPrivate *priv; priv = nautilus_files_view_get_instance_private (view); add_directory_to_directory_list (view, directory, &priv->scripts_directory_list, G_CALLBACK (scripts_added_or_changed_callback)); } static void remove_directory_from_scripts_directory_list (NautilusFilesView *view, NautilusDirectory *directory) { NautilusFilesViewPrivate *priv; priv = nautilus_files_view_get_instance_private (view); remove_directory_from_directory_list (view, directory, &priv->scripts_directory_list, G_CALLBACK (scripts_added_or_changed_callback)); } static void add_directory_to_templates_directory_list (NautilusFilesView *view, NautilusDirectory *directory) { NautilusFilesViewPrivate *priv; priv = nautilus_files_view_get_instance_private (view); add_directory_to_directory_list (view, directory, &priv->templates_directory_list, G_CALLBACK (templates_added_or_changed_callback)); } static void remove_directory_from_templates_directory_list (NautilusFilesView *view, NautilusDirectory *directory) { NautilusFilesViewPrivate *priv; priv = nautilus_files_view_get_instance_private (view); remove_directory_from_directory_list (view, directory, &priv->templates_directory_list, G_CALLBACK (templates_added_or_changed_callback)); } static void slot_active_changed (NautilusWindowSlot *slot, GParamSpec *pspec, NautilusFilesView *view) { NautilusFilesViewPrivate *priv; priv = nautilus_files_view_get_instance_private (view); if (priv->active == nautilus_window_slot_get_active (slot)) { return; } priv->active = nautilus_window_slot_get_active (slot); if (priv->active) { /* Avoid updating the toolbar withouth making sure the toolbar * zoom slider has the correct adjustment that changes when the * view mode changes */ nautilus_files_view_update_context_menus (view); nautilus_files_view_update_toolbar_menus (view); schedule_update_context_menus (view); gtk_widget_insert_action_group (GTK_WIDGET (nautilus_files_view_get_window (view)), "view", G_ACTION_GROUP (priv->view_action_group)); } else { remove_update_context_menus_timeout_callback (view); gtk_widget_insert_action_group (GTK_WIDGET (nautilus_files_view_get_window (view)), "view", NULL); } } static void nautilus_files_view_grab_focus (GtkWidget *widget) { /* focus the child of the scrolled window if it exists */ NautilusFilesView *view; NautilusFilesViewPrivate *priv; GtkWidget *child; view = NAUTILUS_FILES_VIEW (widget); priv = nautilus_files_view_get_instance_private (view); child = gtk_bin_get_child (GTK_BIN (priv->scrolled_window)); GTK_WIDGET_CLASS (nautilus_files_view_parent_class)->grab_focus (widget); if (child) { gtk_widget_grab_focus (GTK_WIDGET (child)); } } static void nautilus_files_view_set_selection (NautilusView *nautilus_files_view, GList *selection) { NautilusFilesView *view; NautilusFilesViewPrivate *priv; GList *pending_selection; view = NAUTILUS_FILES_VIEW (nautilus_files_view); priv = nautilus_files_view_get_instance_private (view); if (!priv->loading) { /* If we aren't still loading, set the selection right now, * and reveal the new selection. */ nautilus_files_view_call_set_selection (view, selection); nautilus_files_view_reveal_selection (view); } else { /* If we are still loading, set the list of pending URIs instead. * done_loading() will eventually select the pending URIs and reveal them. */ pending_selection = g_list_copy_deep (selection, (GCopyFunc) g_object_ref, NULL); g_list_free_full (priv->pending_selection, g_object_unref); priv->pending_selection = pending_selection; } } static void nautilus_files_view_destroy (GtkWidget *object) { NautilusFilesView *view; NautilusFilesViewPrivate *priv; GtkClipboard *clipboard; GList *node, *next; view = NAUTILUS_FILES_VIEW (object); priv = nautilus_files_view_get_instance_private (view); priv->in_destruction = TRUE; nautilus_files_view_stop_loading (view); if (priv->model) { nautilus_directory_unref (priv->model); priv->model = NULL; } for (node = priv->scripts_directory_list; node != NULL; node = next) { next = node->next; remove_directory_from_scripts_directory_list (view, node->data); } for (node = priv->templates_directory_list; node != NULL; node = next) { next = node->next; remove_directory_from_templates_directory_list (view, node->data); } while (priv->subdirectory_list != NULL) { nautilus_files_view_remove_subdirectory (view, priv->subdirectory_list->data); } remove_update_context_menus_timeout_callback (view); remove_update_status_idle_callback (view); if (priv->display_selection_idle_id != 0) { g_source_remove (priv->display_selection_idle_id); priv->display_selection_idle_id = 0; } if (priv->reveal_selection_idle_id != 0) { g_source_remove (priv->reveal_selection_idle_id); priv->reveal_selection_idle_id = 0; } if (priv->floating_bar_set_status_timeout_id != 0) { g_source_remove (priv->floating_bar_set_status_timeout_id); priv->floating_bar_set_status_timeout_id = 0; } if (priv->floating_bar_loading_timeout_id != 0) { g_source_remove (priv->floating_bar_loading_timeout_id); priv->floating_bar_loading_timeout_id = 0; } if (priv->floating_bar_set_passthrough_timeout_id != 0) { g_source_remove (priv->floating_bar_set_passthrough_timeout_id); priv->floating_bar_set_passthrough_timeout_id = 0; } g_signal_handlers_disconnect_by_func (nautilus_preferences, schedule_update_context_menus, view); g_signal_handlers_disconnect_by_func (nautilus_preferences, click_policy_changed_callback, view); g_signal_handlers_disconnect_by_func (gtk_filechooser_preferences, sort_directories_first_changed_callback, view); g_signal_handlers_disconnect_by_func (gtk_filechooser_preferences, show_hidden_files_changed_callback, view); g_signal_handlers_disconnect_by_func (nautilus_window_state, nautilus_files_view_display_selection_info, view); g_signal_handlers_disconnect_by_func (gnome_lockdown_preferences, schedule_update_context_menus, view); g_signal_handlers_disconnect_by_func (nautilus_trash_monitor_get (), nautilus_files_view_trash_state_changed_callback, view); clipboard = gtk_clipboard_get (GDK_SELECTION_CLIPBOARD); g_signal_handlers_disconnect_by_func (clipboard, on_clipboard_owner_changed, view); nautilus_file_unref (priv->directory_as_file); priv->directory_as_file = NULL; g_clear_object (&priv->search_query); g_clear_object (&priv->location); /* We don't own the slot, so no unref */ priv->slot = NULL; GTK_WIDGET_CLASS (nautilus_files_view_parent_class)->destroy (object); } static void nautilus_files_view_finalize (GObject *object) { NautilusFilesView *view; NautilusFilesViewPrivate *priv; view = NAUTILUS_FILES_VIEW (object); priv = nautilus_files_view_get_instance_private (view); g_clear_object (&priv->view_action_group); g_clear_object (&priv->background_menu_model); g_clear_object (&priv->selection_menu_model); g_clear_object (&priv->toolbar_menu_sections->zoom_section); g_clear_object (&priv->toolbar_menu_sections->extended_section); g_clear_object (&priv->extensions_background_menu); g_clear_object (&priv->templates_menu); g_clear_object (&priv->rename_file_controller); g_clear_object (&priv->new_folder_controller); g_clear_object (&priv->compress_controller); g_free (priv->toolbar_menu_sections); g_hash_table_destroy (priv->non_ready_files); g_hash_table_destroy (priv->pending_reveal); g_cancellable_cancel (priv->starred_cancellable); g_clear_object (&priv->starred_cancellable); G_OBJECT_CLASS (nautilus_files_view_parent_class)->finalize (object); } /** * nautilus_files_view_display_selection_info: * * Display information about the current selection, and notify the view frame of the changed selection. * @view: NautilusFilesView for which to display selection info. * **/ void nautilus_files_view_display_selection_info (NautilusFilesView *view) { g_autolist (NautilusFile) selection = NULL; goffset non_folder_size; gboolean non_folder_size_known; guint non_folder_count, folder_count, folder_item_count; gboolean folder_item_count_known; guint file_item_count; GList *p; char *first_item_name; char *non_folder_count_str; char *non_folder_item_count_str; char *folder_count_str; char *folder_item_count_str; char *primary_status; char *detail_status; NautilusFile *file; g_return_if_fail (NAUTILUS_IS_FILES_VIEW (view)); selection = nautilus_view_get_selection (NAUTILUS_VIEW (view)); folder_item_count_known = TRUE; folder_count = 0; folder_item_count = 0; non_folder_count = 0; non_folder_size_known = FALSE; non_folder_size = 0; first_item_name = NULL; folder_count_str = NULL; folder_item_count_str = NULL; non_folder_count_str = NULL; non_folder_item_count_str = NULL; for (p = selection; p != NULL; p = p->next) { file = p->data; if (nautilus_file_is_directory (file)) { folder_count++; if (nautilus_file_get_directory_item_count (file, &file_item_count, NULL)) { folder_item_count += file_item_count; } else { folder_item_count_known = FALSE; } } else { non_folder_count++; if (!nautilus_file_can_get_size (file)) { non_folder_size_known = TRUE; non_folder_size += nautilus_file_get_size (file); } } if (first_item_name == NULL) { first_item_name = nautilus_file_get_display_name (file); } } /* Break out cases for localization's sake. But note that there are still pieces * being assembled in a particular order, which may be a problem for some localizers. */ if (folder_count != 0) { if (folder_count == 1 && non_folder_count == 0) { folder_count_str = g_strdup_printf (_("“%s” selected"), first_item_name); } else { folder_count_str = g_strdup_printf (ngettext ("%'d folder selected", "%'d folders selected", folder_count), folder_count); } if (folder_count == 1) { if (!folder_item_count_known) { folder_item_count_str = g_strdup (""); } else { folder_item_count_str = g_strdup_printf (ngettext ("(containing %'d item)", "(containing %'d items)", folder_item_count), folder_item_count); } } else { if (!folder_item_count_known) { folder_item_count_str = g_strdup (""); } else { /* translators: this is preceded with a string of form 'N folders' (N more than 1) */ folder_item_count_str = g_strdup_printf (ngettext ("(containing a total of %'d item)", "(containing a total of %'d items)", folder_item_count), folder_item_count); } } } if (non_folder_count != 0) { if (folder_count == 0) { if (non_folder_count == 1) { non_folder_count_str = g_strdup_printf (_("“%s” selected"), first_item_name); } else { non_folder_count_str = g_strdup_printf (ngettext ("%'d item selected", "%'d items selected", non_folder_count), non_folder_count); } } else { /* Folders selected also, use "other" terminology */ non_folder_count_str = g_strdup_printf (ngettext ("%'d other item selected", "%'d other items selected", non_folder_count), non_folder_count); } if (non_folder_size_known) { char *size_string; size_string = g_format_size (non_folder_size); /* This is marked for translation in case a localiser * needs to use something other than parentheses. The * the message in parentheses is the size of the selected items. */ non_folder_item_count_str = g_strdup_printf (_("(%s)"), size_string); g_free (size_string); } else { non_folder_item_count_str = g_strdup (""); } } if (folder_count == 0 && non_folder_count == 0) { primary_status = NULL; detail_status = NULL; } else if (folder_count == 0) { primary_status = g_strdup (non_folder_count_str); detail_status = g_strdup (non_folder_item_count_str); } else if (non_folder_count == 0) { primary_status = g_strdup (folder_count_str); detail_status = g_strdup (folder_item_count_str); } else { /* This is marked for translation in case a localizer * needs to change ", " to something else. The comma * is between the message about the number of folders * and the number of items in those folders and the * message about the number of other items and the * total size of those items. */ primary_status = g_strdup_printf (_("%s %s, %s %s"), folder_count_str, folder_item_count_str, non_folder_count_str, non_folder_item_count_str); detail_status = NULL; } g_free (first_item_name); g_free (folder_count_str); g_free (folder_item_count_str); g_free (non_folder_count_str); g_free (non_folder_item_count_str); set_floating_bar_status (view, primary_status, detail_status); g_free (primary_status); g_free (detail_status); } static void nautilus_files_view_send_selection_change (NautilusFilesView *view) { g_signal_emit (view, signals[SELECTION_CHANGED], 0); g_object_notify (G_OBJECT (view), "selection"); } static void nautilus_files_view_set_location (NautilusView *view, GFile *location) { NautilusDirectory *directory; NautilusFilesView *files_view; nautilus_profile_start (NULL); files_view = NAUTILUS_FILES_VIEW (view); directory = nautilus_directory_get (location); nautilus_files_view_stop_loading (files_view); /* In case we want to load a previous search we need to extract the real * location and the search location, and load the directory when everything * is ready. That's why we cannot use the nautilus_view_set_query, because * to set a query we need a previous location loaded, but to load a search * location we need to know the real location behind it. */ if (NAUTILUS_IS_SEARCH_DIRECTORY (directory)) { NautilusQuery *previous_query; NautilusDirectory *base_model; base_model = nautilus_search_directory_get_base_model (NAUTILUS_SEARCH_DIRECTORY (directory)); previous_query = nautilus_search_directory_get_query (NAUTILUS_SEARCH_DIRECTORY (directory)); set_search_query_internal (files_view, previous_query, base_model); g_object_unref (previous_query); } else { load_directory (NAUTILUS_FILES_VIEW (view), directory); } nautilus_directory_unref (directory); nautilus_profile_end (NULL); } static gboolean reveal_selection_idle_callback (gpointer data) { NautilusFilesView *view; NautilusFilesViewPrivate *priv; view = NAUTILUS_FILES_VIEW (data); priv = nautilus_files_view_get_instance_private (view); priv->reveal_selection_idle_id = 0; nautilus_files_view_reveal_selection (view); return FALSE; } static void nautilus_files_view_check_empty_states (NautilusFilesView *view) { NAUTILUS_FILES_VIEW_CLASS (G_OBJECT_GET_CLASS (view))->check_empty_states (view); } static void real_check_empty_states (NautilusFilesView *view) { NautilusFilesViewPrivate *priv; g_autofree gchar *uri = NULL; priv = nautilus_files_view_get_instance_private (view); gtk_widget_hide (priv->no_search_results_widget); gtk_widget_hide (priv->folder_is_empty_widget); gtk_widget_hide (priv->trash_is_empty_widget); gtk_widget_hide (priv->starred_is_empty_widget); if (!priv->loading && nautilus_files_view_is_empty (view)) { uri = g_file_get_uri (priv->location); if (nautilus_view_is_searching (NAUTILUS_VIEW (view))) { gtk_widget_show (priv->no_search_results_widget); } else if (eel_uri_is_trash_root (uri)) { gtk_widget_show (priv->trash_is_empty_widget); } else if (eel_uri_is_starred (uri)) { gtk_widget_show (priv->starred_is_empty_widget); } else { gtk_widget_show (priv->folder_is_empty_widget); } } } static void done_loading (NautilusFilesView *view, gboolean all_files_seen) { NautilusFilesViewPrivate *priv; g_autolist (NautilusFile) selection = NULL; gboolean do_reveal = FALSE; priv = nautilus_files_view_get_instance_private (view); if (!priv->loading) { return; } nautilus_profile_start (NULL); if (!priv->in_destruction) { remove_loading_floating_bar (view); schedule_update_context_menus (view); schedule_update_status (view); nautilus_files_view_update_toolbar_menus (view); reset_update_interval (view); selection = nautilus_view_get_selection (NAUTILUS_VIEW (view)); if (nautilus_view_is_searching (NAUTILUS_VIEW (view)) && all_files_seen && selection == NULL && priv->pending_selection == NULL) { nautilus_files_view_select_first (view); do_reveal = TRUE; } else if (priv->pending_selection != NULL && all_files_seen) { g_autolist (NautilusFile) pending_selection = NULL; pending_selection = g_steal_pointer (&priv->pending_selection); nautilus_files_view_call_set_selection (view, pending_selection); do_reveal = TRUE; } g_clear_pointer (&priv->pending_selection, nautilus_file_list_free); if (do_reveal) { if (NAUTILUS_IS_LIST_VIEW (view) || NAUTILUS_IS_VIEW_ICON_CONTROLLER (view)) { /* HACK: We should be able to directly call reveal_selection here, * but at this point the GtkTreeView hasn't allocated the new nodes * yet, and it has a bug in the scroll calculation dealing with this * special case. It would always make the selection the top row, even * if no scrolling would be neccessary to reveal it. So we let it * allocate before revealing. */ if (priv->reveal_selection_idle_id != 0) { g_source_remove (priv->reveal_selection_idle_id); } priv->reveal_selection_idle_id = g_idle_add (reveal_selection_idle_callback, view); } else { nautilus_files_view_reveal_selection (view); } } nautilus_files_view_display_selection_info (view); } priv->loading = FALSE; g_signal_emit (view, signals[END_LOADING], 0, all_files_seen); g_object_notify (G_OBJECT (view), "loading"); if (!priv->in_destruction) { nautilus_files_view_check_empty_states (view); } nautilus_profile_end (NULL); } typedef struct { GHashTable *debuting_files; GList *added_files; } DebutingFilesData; static void debuting_files_data_free (DebutingFilesData *data) { g_hash_table_unref (data->debuting_files); nautilus_file_list_free (data->added_files); g_free (data); } /* This signal handler watch for the arrival of the icons created * as the result of a file operation. Once the last one is detected * it selects and reveals them all. */ static void debuting_files_add_files_callback (NautilusFilesView *view, GList *new_files, DebutingFilesData *data) { GFile *location; GList *l; nautilus_profile_start (NULL); for (l = new_files; l != NULL; l = l->next) { location = nautilus_file_get_location (NAUTILUS_FILE (l->data)); if (g_hash_table_remove (data->debuting_files, location)) { nautilus_file_ref (NAUTILUS_FILE (l->data)); data->added_files = g_list_prepend (data->added_files, NAUTILUS_FILE (l->data)); } g_object_unref (location); } if (g_hash_table_size (data->debuting_files) == 0) { nautilus_files_view_call_set_selection (view, data->added_files); nautilus_files_view_reveal_selection (view); g_signal_handlers_disconnect_by_func (view, G_CALLBACK (debuting_files_add_files_callback), data); } nautilus_profile_end (NULL); } typedef struct { GList *added_files; NautilusFilesView *directory_view; } CopyMoveDoneData; static void copy_move_done_data_free (CopyMoveDoneData *data) { g_assert (data != NULL); if (data->directory_view != NULL) { g_object_remove_weak_pointer (G_OBJECT (data->directory_view), (gpointer *) &data->directory_view); } nautilus_file_list_free (data->added_files); g_free (data); } static void pre_copy_move_add_files_callback (NautilusFilesView *view, GList *new_files, CopyMoveDoneData *data) { GList *l; for (l = new_files; l != NULL; l = l->next) { nautilus_file_ref (NAUTILUS_FILE (l->data)); data->added_files = g_list_prepend (data->added_files, l->data); } } /* This needs to be called prior to nautilus_file_operations_copy_move. * It hooks up a signal handler to catch any icons that get added before * the copy_done_callback is invoked. The return value should be passed * as the data for uri_copy_move_done_callback. */ static CopyMoveDoneData * pre_copy_move (NautilusFilesView *directory_view) { CopyMoveDoneData *copy_move_done_data; copy_move_done_data = g_new0 (CopyMoveDoneData, 1); copy_move_done_data->directory_view = directory_view; g_object_add_weak_pointer (G_OBJECT (copy_move_done_data->directory_view), (gpointer *) ©_move_done_data->directory_view); /* We need to run after the default handler adds the folder we want to * operate on. The ADD_FILES signal is registered as G_SIGNAL_RUN_LAST, so we * must use connect_after. */ g_signal_connect_after (directory_view, "add-files", G_CALLBACK (pre_copy_move_add_files_callback), copy_move_done_data); return copy_move_done_data; } /* This function is used to pull out any debuting uris that were added * and (as a side effect) remove them from the debuting uri hash table. */ static gboolean copy_move_done_partition_func (NautilusFile *file, gpointer callback_data) { GFile *location; gboolean result; location = nautilus_file_get_location (file); result = g_hash_table_remove ((GHashTable *) callback_data, location); g_object_unref (location); return result; } static gboolean remove_not_really_moved_files (gpointer key, gpointer value, gpointer callback_data) { GList **added_files; GFile *loc; loc = key; if (GPOINTER_TO_INT (value)) { return FALSE; } added_files = callback_data; *added_files = g_list_prepend (*added_files, nautilus_file_get (loc)); return TRUE; } /* When this function is invoked, the file operation is over, but all * the icons may not have been added to the directory view yet, so * we can't select them yet. * * We're passed a hash table of the uri's to look out for, we hook * up a signal handler to await their arrival. */ static void copy_move_done_callback (GHashTable *debuting_files, gboolean success, gpointer data) { NautilusFilesView *directory_view; CopyMoveDoneData *copy_move_done_data; DebutingFilesData *debuting_files_data; GList *failed_files; copy_move_done_data = (CopyMoveDoneData *) data; directory_view = copy_move_done_data->directory_view; if (directory_view != NULL) { g_assert (NAUTILUS_IS_FILES_VIEW (directory_view)); debuting_files_data = g_new (DebutingFilesData, 1); debuting_files_data->debuting_files = g_hash_table_ref (debuting_files); debuting_files_data->added_files = nautilus_file_list_filter (copy_move_done_data->added_files, &failed_files, copy_move_done_partition_func, debuting_files); nautilus_file_list_free (copy_move_done_data->added_files); copy_move_done_data->added_files = failed_files; /* We're passed the same data used by pre_copy_move_add_files_callback, so disconnecting * it will free data. We've already siphoned off the added_files we need, and stashed the * directory_view pointer. */ g_signal_handlers_disconnect_by_func (directory_view, G_CALLBACK (pre_copy_move_add_files_callback), data); /* Any items in the debuting_files hash table that have * "FALSE" as their value aren't really being copied * or moved, so we can't wait for an add_files signal * to come in for those. */ g_hash_table_foreach_remove (debuting_files, remove_not_really_moved_files, &debuting_files_data->added_files); if (g_hash_table_size (debuting_files) == 0) { /* on the off-chance that all the icons have already been added */ if (debuting_files_data->added_files != NULL) { nautilus_files_view_call_set_selection (directory_view, debuting_files_data->added_files); nautilus_files_view_reveal_selection (directory_view); } debuting_files_data_free (debuting_files_data); } else { /* We need to run after the default handler adds the folder we want to * operate on. The ADD_FILES signal is registered as G_SIGNAL_RUN_LAST, so we * must use connect_after. */ g_signal_connect_data (directory_view, "add-files", G_CALLBACK (debuting_files_add_files_callback), debuting_files_data, (GClosureNotify) debuting_files_data_free, G_CONNECT_AFTER); } /* Schedule menu update for undo items */ schedule_update_context_menus (directory_view); } copy_move_done_data_free (copy_move_done_data); } static gboolean view_file_still_belongs (NautilusFilesView *view, FileAndDirectory *fad) { NautilusFilesViewPrivate *priv; priv = nautilus_files_view_get_instance_private (view); if (priv->model != fad->directory && g_list_find (priv->subdirectory_list, fad->directory) == NULL) { return FALSE; } return nautilus_directory_contains_file (fad->directory, fad->file); } static gboolean still_should_show_file (NautilusFilesView *view, FileAndDirectory *fad) { return nautilus_files_view_should_show_file (view, fad->file) && view_file_still_belongs (view, fad); } static gboolean ready_to_load (NautilusFile *file) { return nautilus_file_check_if_ready (file, NAUTILUS_FILE_ATTRIBUTES_FOR_ICON); } static int compare_files_cover (gconstpointer a, gconstpointer b, gpointer callback_data) { const FileAndDirectory *fad1, *fad2; NautilusFilesView *view; view = callback_data; fad1 = a; fad2 = b; if (fad1->directory < fad2->directory) { return -1; } else if (fad1->directory > fad2->directory) { return 1; } else { return NAUTILUS_FILES_VIEW_CLASS (G_OBJECT_GET_CLASS (view))->compare_files (view, fad1->file, fad2->file); } } static void sort_files (NautilusFilesView *view, GList **list) { *list = g_list_sort_with_data (*list, compare_files_cover, view); } /* Go through all the new added and changed files. * Put any that are not ready to load in the non_ready_files hash table. * Add all the rest to the old_added_files and old_changed_files lists. * Sort the old_*_files lists if anything was added to them. */ static void process_new_files (NautilusFilesView *view) { NautilusFilesViewPrivate *priv; g_autolist (FileAndDirectory) new_added_files = NULL; g_autolist (FileAndDirectory) new_changed_files = NULL; GList *old_added_files; GList *old_changed_files; GHashTable *non_ready_files; GList *node, *next; FileAndDirectory *pending; gboolean in_non_ready; priv = nautilus_files_view_get_instance_private (view); new_added_files = g_steal_pointer (&priv->new_added_files); new_changed_files = g_steal_pointer (&priv->new_changed_files); non_ready_files = priv->non_ready_files; old_added_files = priv->old_added_files; old_changed_files = priv->old_changed_files; /* Newly added files go into the old_added_files list if they're * ready, and into the hash table if they're not. */ for (node = new_added_files; node != NULL; node = next) { next = node->next; pending = (FileAndDirectory *) node->data; in_non_ready = g_hash_table_contains (non_ready_files, pending); if (nautilus_files_view_should_show_file (view, pending->file)) { if (ready_to_load (pending->file)) { if (in_non_ready) { g_hash_table_remove (non_ready_files, pending); } new_added_files = g_list_delete_link (new_added_files, node); old_added_files = g_list_prepend (old_added_files, pending); } else { if (!in_non_ready) { new_added_files = g_list_delete_link (new_added_files, node); g_hash_table_add (non_ready_files, pending); } } } } /* Newly changed files go into the old_added_files list if they're ready * and were seen non-ready in the past, into the old_changed_files list * if they are read and were not seen non-ready in the past, and into * the hash table if they're not ready. */ for (node = new_changed_files; node != NULL; node = next) { next = node->next; pending = (FileAndDirectory *) node->data; if (!still_should_show_file (view, pending) || ready_to_load (pending->file)) { if (g_hash_table_contains (non_ready_files, pending)) { g_hash_table_remove (non_ready_files, pending); if (still_should_show_file (view, pending)) { new_changed_files = g_list_delete_link (new_changed_files, node); old_added_files = g_list_prepend (old_added_files, pending); } } else { new_changed_files = g_list_delete_link (new_changed_files, node); old_changed_files = g_list_prepend (old_changed_files, pending); } } } /* If any files were added to old_added_files, then resort it. */ if (old_added_files != priv->old_added_files) { priv->old_added_files = old_added_files; sort_files (view, &priv->old_added_files); } /* Resort old_changed_files too, since file attributes * relevant to sorting could have changed. */ if (old_changed_files != priv->old_changed_files) { priv->old_changed_files = old_changed_files; sort_files (view, &priv->old_changed_files); } } static void on_end_file_changes (NautilusFilesView *view) { NautilusFilesViewPrivate *priv; priv = nautilus_files_view_get_instance_private (view); /* Addition and removal of files modify the empty state */ nautilus_files_view_check_empty_states (view); /* If the view is empty, zoom slider and sort menu are insensitive */ nautilus_files_view_update_toolbar_menus (view); /* Reveal files that were pending to be revealed, only if all of them * were acknowledged by the view */ if (g_hash_table_size (priv->pending_reveal) > 0) { GList *keys; GList *l; gboolean all_files_acknowledged = TRUE; keys = g_hash_table_get_keys (priv->pending_reveal); for (l = keys; l && all_files_acknowledged; l = l->next) { all_files_acknowledged = GPOINTER_TO_UINT (g_hash_table_lookup (priv->pending_reveal, l->data)); } if (all_files_acknowledged) { nautilus_files_view_set_selection (NAUTILUS_VIEW (view), keys); nautilus_files_view_reveal_selection (view); g_hash_table_remove_all (priv->pending_reveal); } g_list_free (keys); } } static int compare_pointers (gconstpointer pointer_1, gconstpointer pointer_2) { if (pointer_1 < pointer_2) { return -1; } else if (pointer_1 > pointer_2) { return +1; } return 0; } static gboolean _g_lists_sort_and_check_for_intersection (GList **list_1, GList **list_2) { GList *node_1; GList *node_2; int compare_result; *list_1 = g_list_sort (*list_1, compare_pointers); *list_2 = g_list_sort (*list_2, compare_pointers); node_1 = *list_1; node_2 = *list_2; while (node_1 != NULL && node_2 != NULL) { compare_result = compare_pointers (node_1->data, node_2->data); if (compare_result == 0) { return TRUE; } if (compare_result <= 0) { node_1 = node_1->next; } if (compare_result >= 0) { node_2 = node_2->next; } } return FALSE; } static void process_old_files (NautilusFilesView *view) { NautilusFilesViewPrivate *priv; g_autolist (FileAndDirectory) files_added = NULL; g_autolist (FileAndDirectory) files_changed = NULL; FileAndDirectory *pending; GList *files; g_autoptr (GList) pending_additions = NULL; priv = nautilus_files_view_get_instance_private (view); files_added = g_steal_pointer (&priv->old_added_files); files_changed = g_steal_pointer (&priv->old_changed_files); if (files_added != NULL || files_changed != NULL) { gboolean send_selection_change = FALSE; g_signal_emit (view, signals[BEGIN_FILE_CHANGES], 0); for (GList *node = files_added; node != NULL; node = node->next) { pending = node->data; pending_additions = g_list_prepend (pending_additions, pending->file); /* Acknowledge the files that were pending to be revealed */ if (g_hash_table_contains (priv->pending_reveal, pending->file)) { g_hash_table_insert (priv->pending_reveal, pending->file, GUINT_TO_POINTER (TRUE)); } } if (files_added != NULL) { g_signal_emit (view, signals[ADD_FILES], 0, pending_additions); } for (GList *node = files_changed; node != NULL; node = node->next) { gboolean should_show_file; pending = node->data; should_show_file = still_should_show_file (view, pending); g_signal_emit (view, signals[should_show_file ? FILE_CHANGED : REMOVE_FILE], 0, pending->file, pending->directory); /* Acknowledge the files that were pending to be revealed */ if (g_hash_table_contains (priv->pending_reveal, pending->file)) { if (should_show_file) { g_hash_table_insert (priv->pending_reveal, pending->file, GUINT_TO_POINTER (TRUE)); } else { g_hash_table_remove (priv->pending_reveal, pending->file); } } } if (files_changed != NULL) { g_autolist (NautilusFile) selection = NULL; selection = nautilus_view_get_selection (NAUTILUS_VIEW (view)); files = g_list_copy_deep (files_changed, (GCopyFunc) file_and_directory_get_file, NULL); send_selection_change = _g_lists_sort_and_check_for_intersection (&files, &selection); nautilus_file_list_free (files); } if (send_selection_change) { /* Send a selection change since some file names could * have changed. */ nautilus_files_view_send_selection_change (view); } g_signal_emit (view, signals[END_FILE_CHANGES], 0); } } static void display_pending_files (NautilusFilesView *view) { NautilusFilesViewPrivate *priv; g_autolist (NautilusFile) selection = NULL; process_new_files (view); process_old_files (view); priv = nautilus_files_view_get_instance_private (view); selection = nautilus_files_view_get_selection (NAUTILUS_VIEW (view)); if (selection == NULL && !priv->pending_selection && nautilus_view_is_searching (NAUTILUS_VIEW (view))) { nautilus_files_view_select_first (view); } if (priv->model != NULL && nautilus_directory_are_all_files_seen (priv->model) && g_hash_table_size (priv->non_ready_files) == 0) { done_loading (view, TRUE); } } static gboolean display_selection_info_idle_callback (gpointer data) { NautilusFilesView *view; NautilusFilesViewPrivate *priv; view = NAUTILUS_FILES_VIEW (data); priv = nautilus_files_view_get_instance_private (view); g_object_ref (G_OBJECT (view)); priv->display_selection_idle_id = 0; nautilus_files_view_display_selection_info (view); nautilus_files_view_send_selection_change (view); g_object_unref (G_OBJECT (view)); return FALSE; } static void remove_update_context_menus_timeout_callback (NautilusFilesView *view) { NautilusFilesViewPrivate *priv; priv = nautilus_files_view_get_instance_private (view); if (priv->update_context_menus_timeout_id != 0) { g_source_remove (priv->update_context_menus_timeout_id); priv->update_context_menus_timeout_id = 0; } } static void update_context_menus_if_pending (NautilusFilesView *view) { remove_update_context_menus_timeout_callback (view); nautilus_files_view_update_context_menus (view); } static gboolean update_context_menus_timeout_callback (gpointer data) { NautilusFilesView *view; NautilusFilesViewPrivate *priv; view = NAUTILUS_FILES_VIEW (data); priv = nautilus_files_view_get_instance_private (view); g_object_ref (G_OBJECT (view)); priv->update_context_menus_timeout_id = 0; nautilus_files_view_update_context_menus (view); g_object_unref (G_OBJECT (view)); return FALSE; } static gboolean display_pending_callback (gpointer data) { NautilusFilesView *view; NautilusFilesViewPrivate *priv; view = NAUTILUS_FILES_VIEW (data); priv = nautilus_files_view_get_instance_private (view); g_object_ref (G_OBJECT (view)); priv->display_pending_source_id = 0; display_pending_files (view); g_object_unref (G_OBJECT (view)); return FALSE; } static void schedule_idle_display_of_pending_files (NautilusFilesView *view) { NautilusFilesViewPrivate *priv; priv = nautilus_files_view_get_instance_private (view); /* Get rid of a pending source as it might be a timeout */ unschedule_display_of_pending_files (view); /* We want higher priority than the idle that handles the relayout * to avoid a resort on each add. But we still want to allow repaints * and other hight prio events while we have pending files to show. */ priv->display_pending_source_id = g_idle_add_full (G_PRIORITY_DEFAULT_IDLE - 20, display_pending_callback, view, NULL); } static void schedule_timeout_display_of_pending_files (NautilusFilesView *view, guint interval) { NautilusFilesViewPrivate *priv; priv = nautilus_files_view_get_instance_private (view); /* No need to schedule an update if there's already one pending. */ if (priv->display_pending_source_id != 0) { return; } priv->display_pending_source_id = g_timeout_add (interval, display_pending_callback, view); } static void unschedule_display_of_pending_files (NautilusFilesView *view) { NautilusFilesViewPrivate *priv; priv = nautilus_files_view_get_instance_private (view); /* Get rid of source if it's active. */ if (priv->display_pending_source_id != 0) { g_source_remove (priv->display_pending_source_id); priv->display_pending_source_id = 0; } } static void queue_pending_files (NautilusFilesView *view, NautilusDirectory *directory, GList *files, GList **pending_list) { NautilusFilesViewPrivate *priv; GList *fad_list; priv = nautilus_files_view_get_instance_private (view); if (files == NULL) { return; } fad_list = g_list_copy_deep (files, (GCopyFunc) file_and_directory_new, directory); *pending_list = g_list_concat (fad_list, *pending_list); /* Generally we don't want to show the files while the directory is loading * the files themselves, so we avoid jumping and oddities. However, for * search it can be a long wait, and we actually want to show files as * they are getting found. So for search is fine if not all files are * seen */ if (!priv->loading || (nautilus_directory_are_all_files_seen (directory) || nautilus_view_is_searching (NAUTILUS_VIEW (view)))) { schedule_timeout_display_of_pending_files (view, priv->update_interval); } } static void remove_changes_timeout_callback (NautilusFilesView *view) { NautilusFilesViewPrivate *priv; priv = nautilus_files_view_get_instance_private (view); if (priv->changes_timeout_id != 0) { g_source_remove (priv->changes_timeout_id); priv->changes_timeout_id = 0; } } static void reset_update_interval (NautilusFilesView *view) { NautilusFilesViewPrivate *priv; priv = nautilus_files_view_get_instance_private (view); priv->update_interval = UPDATE_INTERVAL_MIN; remove_changes_timeout_callback (view); /* Reschedule a pending timeout to idle */ if (priv->display_pending_source_id != 0) { schedule_idle_display_of_pending_files (view); } } static gboolean changes_timeout_callback (gpointer data) { gint64 now; gint64 time_delta; gboolean ret; NautilusFilesView *view; NautilusFilesViewPrivate *priv; view = NAUTILUS_FILES_VIEW (data); priv = nautilus_files_view_get_instance_private (view); g_object_ref (G_OBJECT (view)); now = g_get_monotonic_time (); time_delta = now - priv->last_queued; if (time_delta < UPDATE_INTERVAL_RESET * 1000) { if (priv->update_interval < UPDATE_INTERVAL_MAX && priv->loading) { /* Increase */ priv->update_interval += UPDATE_INTERVAL_INC; } ret = TRUE; } else { /* Reset */ reset_update_interval (view); ret = FALSE; } g_object_unref (G_OBJECT (view)); return ret; } static void schedule_changes (NautilusFilesView *view) { NautilusFilesViewPrivate *priv; priv = nautilus_files_view_get_instance_private (view); /* Remember when the change was queued */ priv->last_queued = g_get_monotonic_time (); /* No need to schedule if there are already changes pending or during loading */ if (priv->changes_timeout_id != 0 || priv->loading) { return; } priv->changes_timeout_id = g_timeout_add (UPDATE_INTERVAL_TIMEOUT_INTERVAL, changes_timeout_callback, view); } static void files_added_callback (NautilusDirectory *directory, GList *files, gpointer callback_data) { NautilusFilesViewPrivate *priv; NautilusFilesView *view; GtkWindow *window; char *uri; view = NAUTILUS_FILES_VIEW (callback_data); priv = nautilus_files_view_get_instance_private (view); nautilus_profile_start (NULL); window = nautilus_files_view_get_containing_window (view); uri = nautilus_files_view_get_uri (view); DEBUG_FILES (files, "Files added in window %p: %s", window, uri ? uri : "(no directory)"); g_free (uri); schedule_changes (view); queue_pending_files (view, directory, files, &priv->new_added_files); /* The number of items could have changed */ schedule_update_status (view); nautilus_profile_end (NULL); } static void files_changed_callback (NautilusDirectory *directory, GList *files, gpointer callback_data) { NautilusFilesViewPrivate *priv; NautilusFilesView *view; GtkWindow *window; char *uri; view = NAUTILUS_FILES_VIEW (callback_data); priv = nautilus_files_view_get_instance_private (view); window = nautilus_files_view_get_containing_window (view); uri = nautilus_files_view_get_uri (view); DEBUG_FILES (files, "Files changed in window %p: %s", window, uri ? uri : "(no directory)"); g_free (uri); schedule_changes (view); queue_pending_files (view, directory, files, &priv->new_changed_files); /* The free space or the number of items could have changed */ schedule_update_status (view); /* A change in MIME type could affect the Open with menu, for * one thing, so we need to update menus when files change. */ schedule_update_context_menus (view); } static void done_loading_callback (NautilusDirectory *directory, gpointer callback_data) { NautilusFilesView *view; NautilusFilesViewPrivate *priv; view = NAUTILUS_FILES_VIEW (callback_data); priv = nautilus_files_view_get_instance_private (view); nautilus_profile_start (NULL); process_new_files (view); if (g_hash_table_size (priv->non_ready_files) == 0) { /* Unschedule a pending update and schedule a new one with the minimal * update interval. This gives the view a short chance at gathering the * (cached) deep counts. */ unschedule_display_of_pending_files (view); schedule_timeout_display_of_pending_files (view, UPDATE_INTERVAL_MIN); remove_loading_floating_bar (view); } nautilus_profile_end (NULL); } static void load_error_callback (NautilusDirectory *directory, GError *error, gpointer callback_data) { NautilusFilesView *view; view = NAUTILUS_FILES_VIEW (callback_data); /* FIXME: By doing a stop, we discard some pending files. Is * that OK? */ nautilus_files_view_stop_loading (view); nautilus_report_error_loading_directory (nautilus_files_view_get_directory_as_file (view), error, nautilus_files_view_get_containing_window (view)); } void nautilus_files_view_add_subdirectory (NautilusFilesView *view, NautilusDirectory *directory) { NautilusFileAttributes attributes; NautilusFilesViewPrivate *priv; priv = nautilus_files_view_get_instance_private (view); g_assert (!g_list_find (priv->subdirectory_list, directory)); nautilus_directory_ref (directory); attributes = NAUTILUS_FILE_ATTRIBUTES_FOR_ICON | NAUTILUS_FILE_ATTRIBUTE_DIRECTORY_ITEM_COUNT | NAUTILUS_FILE_ATTRIBUTE_INFO | NAUTILUS_FILE_ATTRIBUTE_MOUNT | NAUTILUS_FILE_ATTRIBUTE_EXTENSION_INFO; nautilus_directory_file_monitor_add (directory, &priv->model, priv->show_hidden_files, attributes, files_added_callback, view); g_signal_connect (directory, "files-added", G_CALLBACK (files_added_callback), view); g_signal_connect (directory, "files-changed", G_CALLBACK (files_changed_callback), view); priv->subdirectory_list = g_list_prepend ( priv->subdirectory_list, directory); } void nautilus_files_view_remove_subdirectory (NautilusFilesView *view, NautilusDirectory *directory) { NautilusFilesViewPrivate *priv; priv = nautilus_files_view_get_instance_private (view); g_assert (g_list_find (priv->subdirectory_list, directory)); priv->subdirectory_list = g_list_remove ( priv->subdirectory_list, directory); g_signal_handlers_disconnect_by_func (directory, G_CALLBACK (files_added_callback), view); g_signal_handlers_disconnect_by_func (directory, G_CALLBACK (files_changed_callback), view); nautilus_directory_file_monitor_remove (directory, &priv->model); nautilus_directory_unref (directory); } /** * nautilus_files_view_get_loading: * @view: an #NautilusFilesView. * * Return value: #gboolean inicating whether @view is currently loaded. * **/ gboolean nautilus_files_view_get_loading (NautilusFilesView *view) { NautilusFilesViewPrivate *priv; g_return_val_if_fail (NAUTILUS_IS_FILES_VIEW (view), FALSE); priv = nautilus_files_view_get_instance_private (view); return priv->loading; } /** * nautilus_files_view_get_model: * * Get the model for this NautilusFilesView. * @view: NautilusFilesView of interest. * * Return value: NautilusDirectory for this view. * **/ NautilusDirectory * nautilus_files_view_get_model (NautilusFilesView *view) { NautilusFilesViewPrivate *priv; g_return_val_if_fail (NAUTILUS_IS_FILES_VIEW (view), NULL); priv = nautilus_files_view_get_instance_private (view); return priv->model; } GtkWidget * nautilus_files_view_get_content_widget (NautilusFilesView *view) { NautilusFilesViewPrivate *priv; g_return_val_if_fail (NAUTILUS_IS_FILES_VIEW (view), NULL); priv = nautilus_files_view_get_instance_private (view); return priv->scrolled_window; } /* home_dir_in_selection() * * Return TRUE if the home directory is in the selection. */ static gboolean home_dir_in_selection (GList *selection) { for (GList *node = selection; node != NULL; node = node->next) { if (nautilus_file_is_home (NAUTILUS_FILE (node->data))) { return TRUE; } } return FALSE; } static void trash_or_delete_done_cb (GHashTable *debuting_uris, gboolean user_cancel, NautilusFilesView *view) { NautilusFilesViewPrivate *priv; priv = nautilus_files_view_get_instance_private (view); if (user_cancel) { priv->selection_was_removed = FALSE; } } static void trash_or_delete_files (GtkWindow *parent_window, const GList *files, NautilusFilesView *view) { GList *locations; const GList *node; locations = NULL; for (node = files; node != NULL; node = node->next) { locations = g_list_prepend (locations, nautilus_file_get_location ((NautilusFile *) node->data)); } locations = g_list_reverse (locations); nautilus_file_operations_trash_or_delete_async (locations, parent_window, (NautilusDeleteCallback) trash_or_delete_done_cb, view); g_list_free_full (locations, g_object_unref); } static void open_one_in_new_window (gpointer data, gpointer callback_data) { g_assert (NAUTILUS_IS_FILE (data)); g_assert (NAUTILUS_IS_FILES_VIEW (callback_data)); nautilus_files_view_activate_file (NAUTILUS_FILES_VIEW (callback_data), NAUTILUS_FILE (data), NAUTILUS_WINDOW_OPEN_FLAG_NEW_WINDOW); } NautilusFile * nautilus_files_view_get_directory_as_file (NautilusFilesView *view) { NautilusFilesViewPrivate *priv; g_assert (NAUTILUS_IS_FILES_VIEW (view)); priv = nautilus_files_view_get_instance_private (view); return priv->directory_as_file; } static GdkPixbuf * get_menu_icon_for_file (NautilusFile *file, GtkWidget *widget) { NautilusIconInfo *info; GdkPixbuf *pixbuf; int size, scale; size = nautilus_get_icon_size_for_stock_size (GTK_ICON_SIZE_MENU); scale = gtk_widget_get_scale_factor (widget); info = nautilus_file_get_icon (file, size, scale, 0); pixbuf = nautilus_icon_info_get_pixbuf_nodefault_at_size (info, size); g_object_unref (info); return pixbuf; } static GList * get_extension_selection_menu_items (NautilusFilesView *view) { NautilusWindow *window; GList *items; GList *providers; GList *l; g_autolist (NautilusFile) selection = NULL; window = nautilus_files_view_get_window (view); selection = nautilus_view_get_selection (NAUTILUS_VIEW (view)); providers = nautilus_module_get_extensions_for_type (NAUTILUS_TYPE_MENU_PROVIDER); items = NULL; for (l = providers; l != NULL; l = l->next) { NautilusMenuProvider *provider; GList *file_items; provider = NAUTILUS_MENU_PROVIDER (l->data); file_items = nautilus_menu_provider_get_file_items (provider, GTK_WIDGET (window), selection); items = g_list_concat (items, file_items); } nautilus_module_extension_list_free (providers); return items; } static GList * get_extension_background_menu_items (NautilusFilesView *view) { NautilusFilesViewPrivate *priv; NautilusWindow *window; GList *items; GList *providers; GList *l; priv = nautilus_files_view_get_instance_private (view); window = nautilus_files_view_get_window (view); providers = nautilus_module_get_extensions_for_type (NAUTILUS_TYPE_MENU_PROVIDER); items = NULL; for (l = providers; l != NULL; l = l->next) { NautilusMenuProvider *provider; NautilusFileInfo *file_info; GList *file_items; provider = NAUTILUS_MENU_PROVIDER (l->data); file_info = NAUTILUS_FILE_INFO (priv->directory_as_file); file_items = nautilus_menu_provider_get_background_items (provider, GTK_WIDGET (window), file_info); items = g_list_concat (items, file_items); } nautilus_module_extension_list_free (providers); return items; } static void extension_action_callback (GSimpleAction *action, GVariant *state, gpointer user_data) { NautilusMenuItem *item = user_data; nautilus_menu_item_activate (item); } static void add_extension_action (NautilusFilesView *view, NautilusMenuItem *item, const char *action_name) { NautilusFilesViewPrivate *priv; gboolean sensitive; GSimpleAction *action; priv = nautilus_files_view_get_instance_private (view); g_object_get (item, "sensitive", &sensitive, NULL); action = g_simple_action_new (action_name, NULL); g_signal_connect_data (action, "activate", G_CALLBACK (extension_action_callback), g_object_ref (item), (GClosureNotify) g_object_unref, 0); g_action_map_add_action (G_ACTION_MAP (priv->view_action_group), G_ACTION (action)); g_simple_action_set_enabled (action, sensitive); g_object_unref (action); } static GMenuModel * build_menu_for_extension_menu_items (NautilusFilesView *view, const gchar *extension_prefix, GList *menu_items) { GList *l; GMenu *gmenu; gint idx = 0; gmenu = g_menu_new (); for (l = menu_items; l; l = l->next) { NautilusMenuItem *item; NautilusMenu *menu; GMenuItem *menu_item; char *name, *label; g_autofree gchar *escaped_name = NULL; char *extension_id, *detailed_action_name; item = NAUTILUS_MENU_ITEM (l->data); g_object_get (item, "label", &label, "menu", &menu, "name", &name, NULL); escaped_name = g_uri_escape_string (name, NULL, TRUE); extension_id = g_strdup_printf ("extension_%s_%d_%s", extension_prefix, idx, escaped_name); add_extension_action (view, item, extension_id); detailed_action_name = g_strconcat ("view.", extension_id, NULL); menu_item = g_menu_item_new (label, detailed_action_name); if (menu != NULL) { GList *children; g_autoptr (GMenuModel) children_menu = NULL; children = nautilus_menu_get_items (menu); children_menu = build_menu_for_extension_menu_items (view, extension_id, children); g_menu_item_set_submenu (menu_item, children_menu); nautilus_menu_item_list_free (children); } g_menu_append_item (gmenu, menu_item); idx++; g_free (extension_id); g_free (detailed_action_name); g_free (name); g_free (label); g_object_unref (menu_item); } return G_MENU_MODEL (gmenu); } static void update_extensions_menus (NautilusFilesView *view, GtkBuilder *builder) { GList *selection_items, *background_items; GObject *object; g_autoptr (GMenuModel) background_menu = NULL; g_autoptr (GMenuModel) selection_menu = NULL; selection_items = get_extension_selection_menu_items (view); if (selection_items != NULL) { selection_menu = build_menu_for_extension_menu_items (view, "extensions", selection_items); object = gtk_builder_get_object (builder, "selection-extensions-section"); nautilus_gmenu_set_from_model (G_MENU (object), selection_menu); nautilus_menu_item_list_free (selection_items); } background_items = get_extension_background_menu_items (view); if (background_items != NULL) { background_menu = build_menu_for_extension_menu_items (view, "extensions", background_items); object = gtk_builder_get_object (builder, "background-extensions-section"); nautilus_gmenu_set_from_model (G_MENU (object), background_menu); nautilus_menu_item_list_free (background_items); } nautilus_view_set_extensions_background_menu (NAUTILUS_VIEW (view), background_menu); } static char * change_to_view_directory (NautilusFilesView *view) { char *path; char *old_path; old_path = g_get_current_dir (); path = get_view_directory (view); /* FIXME: What to do about non-local directories? */ if (path != NULL) { g_chdir (path); } g_free (path); return old_path; } static char ** get_file_names_as_parameter_array (GList *selection, NautilusDirectory *model) { NautilusFile *file; char **parameters; GList *node; GFile *file_location; GFile *model_location; int i; if (model == NULL) { return NULL; } parameters = g_new (char *, g_list_length (selection) + 1); model_location = nautilus_directory_get_location (model); for (node = selection, i = 0; node != NULL; node = node->next, i++) { file = NAUTILUS_FILE (node->data); if (!nautilus_file_is_local (file)) { parameters[i] = NULL; g_strfreev (parameters); return NULL; } file_location = nautilus_file_get_location (NAUTILUS_FILE (node->data)); parameters[i] = g_file_get_relative_path (model_location, file_location); if (parameters[i] == NULL) { parameters[i] = g_file_get_path (file_location); } g_object_unref (file_location); } g_object_unref (model_location); parameters[i] = NULL; return parameters; } static char * get_file_paths_or_uris_as_newline_delimited_string (NautilusFilesView *view, GList *selection, gboolean get_paths) { char *path; char *uri; char *result; GString *expanding_string; GList *node; expanding_string = g_string_new (""); for (node = selection; node != NULL; node = node->next) { uri = nautilus_file_get_uri (NAUTILUS_FILE (node->data)); if (uri == NULL) { continue; } if (get_paths) { path = g_filename_from_uri (uri, NULL, NULL); if (path != NULL) { g_string_append (expanding_string, path); g_free (path); g_string_append (expanding_string, "\n"); } } else { g_string_append (expanding_string, uri); g_string_append (expanding_string, "\n"); } g_free (uri); } result = expanding_string->str; g_string_free (expanding_string, FALSE); return result; } static char * get_file_paths_as_newline_delimited_string (NautilusFilesView *view, GList *selection) { return get_file_paths_or_uris_as_newline_delimited_string (view, selection, TRUE); } static char * get_file_uris_as_newline_delimited_string (NautilusFilesView *view, GList *selection) { return get_file_paths_or_uris_as_newline_delimited_string (view, selection, FALSE); } /* returns newly allocated strings for setting the environment variables */ static void get_strings_for_environment_variables (NautilusFilesView *view, GList *selected_files, char **file_paths, char **uris, char **uri) { NautilusFilesViewPrivate *priv; char *directory_uri; priv = nautilus_files_view_get_instance_private (view); /* We need to check that the directory uri starts with "file:" since * nautilus_directory_is_local returns FALSE for nfs. */ directory_uri = nautilus_directory_get_uri (priv->model); if (g_str_has_prefix (directory_uri, "file:") || eel_uri_is_trash (directory_uri) || eel_uri_is_search (directory_uri)) { *file_paths = get_file_paths_as_newline_delimited_string (view, selected_files); } else { *file_paths = g_strdup (""); } g_free (directory_uri); *uris = get_file_uris_as_newline_delimited_string (view, selected_files); *uri = nautilus_directory_get_uri (priv->model); } /* * Set up some environment variables that scripts can use * to take advantage of the current Nautilus state. */ static void set_script_environment_variables (NautilusFilesView *view, GList *selected_files) { char *file_paths; char *uris; char *uri; char *geometry_string; get_strings_for_environment_variables (view, selected_files, &file_paths, &uris, &uri); g_setenv ("NAUTILUS_SCRIPT_SELECTED_FILE_PATHS", file_paths, TRUE); g_free (file_paths); g_setenv ("NAUTILUS_SCRIPT_SELECTED_URIS", uris, TRUE); g_free (uris); g_setenv ("NAUTILUS_SCRIPT_CURRENT_URI", uri, TRUE); g_free (uri); geometry_string = eel_gtk_window_get_geometry_string (GTK_WINDOW (nautilus_files_view_get_containing_window (view))); g_setenv ("NAUTILUS_SCRIPT_WINDOW_GEOMETRY", geometry_string, TRUE); g_free (geometry_string); } /* Unset all the special script environment variables. */ static void unset_script_environment_variables (void) { g_unsetenv ("NAUTILUS_SCRIPT_SELECTED_FILE_PATHS"); g_unsetenv ("NAUTILUS_SCRIPT_SELECTED_URIS"); g_unsetenv ("NAUTILUS_SCRIPT_CURRENT_URI"); g_unsetenv ("NAUTILUS_SCRIPT_WINDOW_GEOMETRY"); } static void run_script (GSimpleAction *action, GVariant *state, gpointer user_data) { NautilusFilesViewPrivate *priv; ScriptLaunchParameters *launch_parameters; GdkScreen *screen; g_autolist (NautilusFile) selection = NULL; char *file_uri; g_autofree char *local_file_path = NULL; char *quoted_path; char *old_working_dir; char **parameters; launch_parameters = (ScriptLaunchParameters *) user_data; priv = nautilus_files_view_get_instance_private (launch_parameters->directory_view); file_uri = nautilus_file_get_uri (launch_parameters->file); local_file_path = g_filename_from_uri (file_uri, NULL, NULL); g_assert (local_file_path != NULL); g_free (file_uri); quoted_path = g_shell_quote (local_file_path); old_working_dir = change_to_view_directory (launch_parameters->directory_view); selection = nautilus_view_get_selection (NAUTILUS_VIEW (launch_parameters->directory_view)); set_script_environment_variables (launch_parameters->directory_view, selection); parameters = get_file_names_as_parameter_array (selection, priv->model); screen = gtk_widget_get_screen (GTK_WIDGET (launch_parameters->directory_view)); DEBUG ("run_script, script_path=“%s” (omitting script parameters)", local_file_path); nautilus_launch_application_from_command_array (screen, quoted_path, FALSE, (const char * const *) parameters); g_strfreev (parameters); unset_script_environment_variables (); g_chdir (old_working_dir); g_free (old_working_dir); g_free (quoted_path); } static void add_script_to_scripts_menus (NautilusFilesView *view, NautilusFile *file, GMenu *menu) { NautilusFilesViewPrivate *priv; gchar *name; g_autofree gchar *uri = NULL; g_autofree gchar *escaped_uri = NULL; GdkPixbuf *mimetype_icon; gchar *action_name, *detailed_action_name; ScriptLaunchParameters *launch_parameters; GAction *action; GMenuItem *menu_item; const gchar *shortcut; priv = nautilus_files_view_get_instance_private (view); launch_parameters = script_launch_parameters_new (file, view); name = nautilus_file_get_display_name (file); uri = nautilus_file_get_uri (file); escaped_uri = g_uri_escape_string (uri, NULL, TRUE); action_name = g_strconcat ("script_", escaped_uri, NULL); action = G_ACTION (g_simple_action_new (action_name, NULL)); g_signal_connect_data (action, "activate", G_CALLBACK (run_script), launch_parameters, (GClosureNotify) script_launch_parameters_free, 0); g_action_map_add_action (G_ACTION_MAP (priv->view_action_group), action); g_object_unref (action); detailed_action_name = g_strconcat ("view.", action_name, NULL); menu_item = g_menu_item_new (name, detailed_action_name); mimetype_icon = get_menu_icon_for_file (file, GTK_WIDGET (view)); if (mimetype_icon != NULL) { g_menu_item_set_icon (menu_item, G_ICON (mimetype_icon)); g_object_unref (mimetype_icon); } g_menu_append_item (menu, menu_item); if ((shortcut = g_hash_table_lookup (script_accels, name))) { nautilus_application_set_accelerator (g_application_get_default (), detailed_action_name, shortcut); } g_free (name); g_free (action_name); g_free (detailed_action_name); g_object_unref (menu_item); } static gboolean directory_belongs_in_scripts_menu (const char *uri) { int num_levels; int i; if (!g_str_has_prefix (uri, scripts_directory_uri)) { return FALSE; } num_levels = 0; for (i = scripts_directory_uri_length; uri[i] != '\0'; i++) { if (uri[i] == '/') { num_levels++; } } if (num_levels > MAX_MENU_LEVELS) { return FALSE; } return TRUE; } /* Expected format: accel script_name */ static void nautilus_load_custom_accel_for_scripts (void) { gchar *path, *contents; gchar **lines, **result; GError *error = NULL; const int max_len = 100; int i; path = g_build_filename (g_get_user_config_dir (), SHORTCUTS_PATH, NULL); if (g_file_get_contents (path, &contents, NULL, &error)) { lines = g_strsplit (contents, "\n", -1); for (i = 0; lines[i] && (strstr (lines[i], " ") > 0); i++) { result = g_strsplit (lines[i], " ", 2); g_hash_table_insert (script_accels, g_strndup (result[1], max_len), g_strndup (result[0], max_len)); g_strfreev (result); } g_free (contents); g_strfreev (lines); } else { DEBUG ("Unable to open '%s', error message: %s", path, error->message); g_clear_error (&error); } g_free (path); } static GMenu * update_directory_in_scripts_menu (NautilusFilesView *view, NautilusDirectory *directory) { GList *file_list, *filtered, *node; GMenu *menu, *children_menu; GMenuItem *menu_item; gboolean any_scripts; NautilusFile *file; NautilusDirectory *dir; char *uri; gchar *file_name; int num; g_return_val_if_fail (NAUTILUS_IS_FILES_VIEW (view), NULL); g_return_val_if_fail (NAUTILUS_IS_DIRECTORY (directory), NULL); if (script_accels == NULL) { script_accels = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free); nautilus_load_custom_accel_for_scripts (); } file_list = nautilus_directory_get_file_list (directory); filtered = nautilus_file_list_filter_hidden (file_list, FALSE); nautilus_file_list_free (file_list); menu = g_menu_new (); filtered = nautilus_file_list_sort_by_display_name (filtered); num = 0; any_scripts = FALSE; for (node = filtered; num < TEMPLATE_LIMIT && node != NULL; node = node->next, num++) { file = node->data; if (nautilus_file_is_directory (file)) { uri = nautilus_file_get_uri (file); if (directory_belongs_in_scripts_menu (uri)) { dir = nautilus_directory_get_by_uri (uri); add_directory_to_scripts_directory_list (view, dir); children_menu = update_directory_in_scripts_menu (view, dir); if (children_menu != NULL) { file_name = nautilus_file_get_display_name (file); menu_item = g_menu_item_new_submenu (file_name, G_MENU_MODEL (children_menu)); g_menu_append_item (menu, menu_item); any_scripts = TRUE; g_object_unref (menu_item); g_object_unref (children_menu); g_free (file_name); } nautilus_directory_unref (dir); } g_free (uri); } else if (nautilus_file_is_launchable (file)) { add_script_to_scripts_menus (view, file, menu); any_scripts = TRUE; } } nautilus_file_list_free (filtered); if (!any_scripts) { g_object_unref (menu); menu = NULL; } return menu; } static void update_scripts_menu (NautilusFilesView *view, GtkBuilder *builder) { NautilusFilesViewPrivate *priv; GList *sorted_copy, *node; NautilusDirectory *directory; GMenu *submenu; char *uri; priv = nautilus_files_view_get_instance_private (view); sorted_copy = nautilus_directory_list_sort_by_uri (nautilus_directory_list_copy (priv->scripts_directory_list)); for (node = sorted_copy; node != NULL; node = node->next) { directory = node->data; uri = nautilus_directory_get_uri (directory); if (!directory_belongs_in_scripts_menu (uri)) { remove_directory_from_scripts_directory_list (view, directory); } g_free (uri); } nautilus_directory_list_free (sorted_copy); directory = nautilus_directory_get_by_uri (scripts_directory_uri); submenu = update_directory_in_scripts_menu (view, directory); if (submenu != NULL) { GObject *object; object = gtk_builder_get_object (builder, "scripts-submenu"); nautilus_gmenu_set_from_model (G_MENU (object), G_MENU_MODEL (submenu)); g_object_unref (submenu); } nautilus_directory_unref (directory); priv->scripts_present = submenu != NULL; } static void create_template (GSimpleAction *action, GVariant *state, gpointer user_data) { CreateTemplateParameters *parameters; parameters = user_data; nautilus_files_view_new_file (parameters->directory_view, NULL, parameters->file); } static void add_template_to_templates_menus (NautilusFilesView *view, NautilusFile *file, GMenu *menu) { NautilusFilesViewPrivate *priv; char *tmp, *uri, *name; g_autofree gchar *escaped_uri = NULL; GdkPixbuf *mimetype_icon; char *action_name, *detailed_action_name; CreateTemplateParameters *parameters; GAction *action; g_autofree char *label = NULL; GMenuItem *menu_item; priv = nautilus_files_view_get_instance_private (view); tmp = nautilus_file_get_display_name (file); name = eel_filename_strip_extension (tmp); g_free (tmp); uri = nautilus_file_get_uri (file); escaped_uri = g_uri_escape_string (uri, NULL, TRUE); action_name = g_strconcat ("template_", escaped_uri, NULL); action = G_ACTION (g_simple_action_new (action_name, NULL)); parameters = create_template_parameters_new (file, view); g_signal_connect_data (action, "activate", G_CALLBACK (create_template), parameters, (GClosureNotify) create_templates_parameters_free, 0); g_action_map_add_action (G_ACTION_MAP (priv->view_action_group), action); detailed_action_name = g_strconcat ("view.", action_name, NULL); label = eel_str_double_underscores (name); menu_item = g_menu_item_new (label, detailed_action_name); mimetype_icon = get_menu_icon_for_file (file, GTK_WIDGET (view)); if (mimetype_icon != NULL) { g_menu_item_set_icon (menu_item, G_ICON (mimetype_icon)); g_object_unref (mimetype_icon); } g_menu_append_item (menu, menu_item); g_free (name); g_free (uri); g_free (action_name); g_free (detailed_action_name); g_object_unref (action); g_object_unref (menu_item); } static void update_templates_directory (NautilusFilesView *view) { NautilusFilesViewPrivate *priv; NautilusDirectory *templates_directory; GList *node, *next; char *templates_uri; priv = nautilus_files_view_get_instance_private (view); for (node = priv->templates_directory_list; node != NULL; node = next) { next = node->next; remove_directory_from_templates_directory_list (view, node->data); } if (nautilus_should_use_templates_directory ()) { templates_uri = nautilus_get_templates_directory_uri (); templates_directory = nautilus_directory_get_by_uri (templates_uri); g_free (templates_uri); add_directory_to_templates_directory_list (view, templates_directory); nautilus_directory_unref (templates_directory); } } static gboolean directory_belongs_in_templates_menu (const char *templates_directory_uri, const char *uri) { int num_levels; int i; if (templates_directory_uri == NULL) { return FALSE; } if (!g_str_has_prefix (uri, templates_directory_uri)) { return FALSE; } num_levels = 0; for (i = strlen (templates_directory_uri); uri[i] != '\0'; i++) { if (uri[i] == '/') { num_levels++; } } if (num_levels > MAX_MENU_LEVELS) { return FALSE; } return TRUE; } static gboolean filter_templates_callback (NautilusFile *file, gpointer callback_data) { gboolean show_hidden = GPOINTER_TO_INT (callback_data); if (nautilus_file_is_hidden_file (file)) { if (!show_hidden) { return FALSE; } if (nautilus_file_is_directory (file)) { return FALSE; } } return TRUE; } static GList * filter_templates (GList *files, gboolean show_hidden) { GList *filtered_files; GList *removed_files; filtered_files = nautilus_file_list_filter (files, &removed_files, filter_templates_callback, GINT_TO_POINTER (show_hidden)); nautilus_file_list_free (removed_files); return filtered_files; } static GMenuModel * update_directory_in_templates_menu (NautilusFilesView *view, NautilusDirectory *directory) { NautilusFilesViewPrivate *priv; GList *file_list, *filtered, *node; GMenu *menu; GMenuItem *menu_item; gboolean any_templates; NautilusFile *file; NautilusDirectory *dir; char *uri; char *templates_directory_uri; int num; g_return_val_if_fail (NAUTILUS_IS_FILES_VIEW (view), NULL); g_return_val_if_fail (NAUTILUS_IS_DIRECTORY (directory), NULL); priv = nautilus_files_view_get_instance_private (view); file_list = nautilus_directory_get_file_list (directory); /* * The nautilus_file_list_filter_hidden() function isn't used here, because * we want to show hidden files, but not directories. This is a compromise * to allow creating hidden files but to prevent content from .git directory * for example. See https://gitlab.gnome.org/GNOME/nautilus/issues/1413. */ filtered = filter_templates (file_list, priv->show_hidden_files); nautilus_file_list_free (file_list); templates_directory_uri = nautilus_get_templates_directory_uri (); menu = g_menu_new (); filtered = nautilus_file_list_sort_by_display_name (filtered); num = 0; any_templates = FALSE; for (node = filtered; num < TEMPLATE_LIMIT && node != NULL; node = node->next, num++) { file = node->data; if (nautilus_file_is_directory (file)) { uri = nautilus_file_get_uri (file); if (directory_belongs_in_templates_menu (templates_directory_uri, uri)) { g_autoptr (GMenuModel) children_menu = NULL; dir = nautilus_directory_get_by_uri (uri); add_directory_to_templates_directory_list (view, dir); children_menu = update_directory_in_templates_menu (view, dir); if (children_menu != NULL) { g_autofree char *display_name = NULL; g_autofree char *label = NULL; display_name = nautilus_file_get_display_name (file); label = eel_str_double_underscores (display_name); menu_item = g_menu_item_new_submenu (label, children_menu); g_menu_append_item (menu, menu_item); any_templates = TRUE; g_object_unref (menu_item); } nautilus_directory_unref (dir); } g_free (uri); } else if (nautilus_file_can_read (file)) { add_template_to_templates_menus (view, file, menu); any_templates = TRUE; } } nautilus_file_list_free (filtered); g_free (templates_directory_uri); if (!any_templates) { g_object_unref (menu); menu = NULL; } return G_MENU_MODEL (menu); } static void update_templates_menu (NautilusFilesView *view, GtkBuilder *builder) { NautilusFilesViewPrivate *priv; GList *sorted_copy, *node; NautilusDirectory *directory; g_autoptr (GMenuModel) submenu = NULL; char *uri; char *templates_directory_uri; priv = nautilus_files_view_get_instance_private (view); if (nautilus_should_use_templates_directory ()) { templates_directory_uri = nautilus_get_templates_directory_uri (); } else { priv->templates_present = FALSE; return; } sorted_copy = nautilus_directory_list_sort_by_uri (nautilus_directory_list_copy (priv->templates_directory_list)); for (node = sorted_copy; node != NULL; node = node->next) { directory = node->data; uri = nautilus_directory_get_uri (directory); if (!directory_belongs_in_templates_menu (templates_directory_uri, uri)) { remove_directory_from_templates_directory_list (view, directory); } g_free (uri); } nautilus_directory_list_free (sorted_copy); directory = nautilus_directory_get_by_uri (templates_directory_uri); submenu = update_directory_in_templates_menu (view, directory); if (submenu != NULL) { GObject *object; object = gtk_builder_get_object (builder, "templates-submenu"); nautilus_gmenu_set_from_model (G_MENU (object), submenu); } nautilus_view_set_templates_menu (NAUTILUS_VIEW (view), submenu); nautilus_directory_unref (directory); priv->templates_present = submenu != NULL; g_free (templates_directory_uri); } static void action_open_scripts_folder (GSimpleAction *action, GVariant *state, gpointer user_data) { static GFile *location = NULL; if (location == NULL) { location = g_file_new_for_uri (scripts_directory_uri); } nautilus_application_open_location_full (NAUTILUS_APPLICATION (g_application_get_default ()), location, 0, NULL, NULL, NULL); } typedef struct _CopyCallbackData { NautilusFilesView *view; GList *selection; gboolean is_move; } CopyCallbackData; static void copy_data_free (CopyCallbackData *data) { nautilus_file_list_free (data->selection); g_free (data); } static gboolean uri_is_parent_of_selection (GList *selection, const char *uri) { gboolean found; GList *l; GFile *file; found = FALSE; file = g_file_new_for_uri (uri); for (l = selection; !found && l != NULL; l = l->next) { GFile *parent; parent = nautilus_file_get_parent_location (l->data); found = g_file_equal (file, parent); g_object_unref (parent); } g_object_unref (file); return found; } static void on_destination_dialog_folder_changed (GtkFileChooser *chooser, gpointer user_data) { CopyCallbackData *copy_data = user_data; char *uri; gboolean found; uri = gtk_file_chooser_get_current_folder_uri (chooser); found = uri_is_parent_of_selection (copy_data->selection, uri); gtk_dialog_set_response_sensitive (GTK_DIALOG (chooser), GTK_RESPONSE_OK, !found); g_free (uri); } static void on_destination_dialog_response (GtkDialog *dialog, gint response_id, gpointer user_data) { CopyCallbackData *copy_data = user_data; if (response_id == GTK_RESPONSE_OK) { char *target_uri; GList *uris, *l; target_uri = gtk_file_chooser_get_uri (GTK_FILE_CHOOSER (dialog)); uris = NULL; for (l = copy_data->selection; l != NULL; l = l->next) { uris = g_list_prepend (uris, nautilus_file_get_uri ((NautilusFile *) l->data)); } uris = g_list_reverse (uris); nautilus_files_view_move_copy_items (copy_data->view, uris, target_uri, copy_data->is_move ? GDK_ACTION_MOVE : GDK_ACTION_COPY); g_list_free_full (uris, g_free); g_free (target_uri); } copy_data_free (copy_data); gtk_widget_destroy (GTK_WIDGET (dialog)); } static gboolean destination_dialog_filter_cb (const GtkFileFilterInfo *filter_info, gpointer user_data) { GList *selection = user_data; GList *l; for (l = selection; l != NULL; l = l->next) { char *uri; uri = nautilus_file_get_uri (l->data); if (strcmp (uri, filter_info->uri) == 0) { g_free (uri); return FALSE; } g_free (uri); } return TRUE; } static GList * get_selected_folders (GList *selection) { GList *folders; GList *l; folders = NULL; for (l = selection; l != NULL; l = l->next) { if (nautilus_file_is_directory (l->data)) { folders = g_list_prepend (folders, nautilus_file_ref (l->data)); } } return g_list_reverse (folders); } static void copy_or_move_selection (NautilusFilesView *view, gboolean is_move) { NautilusFilesViewPrivate *priv; GtkWidget *dialog; char *uri; CopyCallbackData *copy_data; GList *selection; const gchar *title; NautilusDirectory *directory; priv = nautilus_files_view_get_instance_private (view); if (is_move) { title = _("Select Move Destination"); } else { title = _("Select Copy Destination"); } selection = nautilus_files_view_get_selection_for_file_transfer (view); dialog = gtk_file_chooser_dialog_new (title, GTK_WINDOW (nautilus_files_view_get_window (view)), GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER, _("_Cancel"), GTK_RESPONSE_CANCEL, _("_Select"), GTK_RESPONSE_OK, NULL); gtk_file_chooser_set_local_only (GTK_FILE_CHOOSER (dialog), FALSE); gtk_dialog_set_default_response (GTK_DIALOG (dialog), GTK_RESPONSE_OK); gtk_window_set_destroy_with_parent (GTK_WINDOW (dialog), TRUE); gtk_window_set_modal (GTK_WINDOW (dialog), TRUE); copy_data = g_new0 (CopyCallbackData, 1); copy_data->view = view; copy_data->selection = selection; copy_data->is_move = is_move; if (selection != NULL) { GtkFileFilter *filter; GList *folders; folders = get_selected_folders (selection); filter = gtk_file_filter_new (); gtk_file_filter_add_custom (filter, GTK_FILE_FILTER_URI, destination_dialog_filter_cb, folders, (GDestroyNotify) nautilus_file_list_free); gtk_file_chooser_set_filter (GTK_FILE_CHOOSER (dialog), filter); } if (nautilus_view_is_searching (NAUTILUS_VIEW (view))) { directory = nautilus_search_directory_get_base_model (NAUTILUS_SEARCH_DIRECTORY (priv->model)); uri = nautilus_directory_get_uri (directory); } else { uri = nautilus_directory_get_uri (priv->model); } gtk_file_chooser_set_current_folder_uri (GTK_FILE_CHOOSER (dialog), uri); g_free (uri); g_signal_connect (dialog, "current-folder-changed", G_CALLBACK (on_destination_dialog_folder_changed), copy_data); g_signal_connect (dialog, "response", G_CALLBACK (on_destination_dialog_response), copy_data); gtk_widget_show_all (dialog); } static void action_copy (GSimpleAction *action, GVariant *state, gpointer user_data) { NautilusFilesView *view; GtkClipboard *clipboard; GList *selection; view = NAUTILUS_FILES_VIEW (user_data); selection = nautilus_files_view_get_selection_for_file_transfer (view); clipboard = nautilus_clipboard_get (GTK_WIDGET (view)); nautilus_clipboard_prepare_for_files (clipboard, selection, FALSE); nautilus_file_list_free (selection); } static void action_cut (GSimpleAction *action, GVariant *state, gpointer user_data) { NautilusFilesView *view; GList *selection; GtkClipboard *clipboard; view = NAUTILUS_FILES_VIEW (user_data); selection = nautilus_files_view_get_selection_for_file_transfer (view); clipboard = nautilus_clipboard_get (GTK_WIDGET (view)); nautilus_clipboard_prepare_for_files (clipboard, selection, TRUE); nautilus_file_list_free (selection); } static void action_create_links_in_place (GSimpleAction *action, GVariant *state, gpointer user_data) { NautilusFilesView *view; GList *selection; GList *item_uris; GList *l; char *destination_uri; view = NAUTILUS_FILES_VIEW (user_data); selection = nautilus_files_view_get_selection_for_file_transfer (view); item_uris = NULL; for (l = selection; l != NULL; l = l->next) { item_uris = g_list_prepend (item_uris, nautilus_file_get_uri (l->data)); } item_uris = g_list_reverse (item_uris); destination_uri = nautilus_files_view_get_backing_uri (view); nautilus_files_view_move_copy_items (view, item_uris, destination_uri, GDK_ACTION_LINK); g_list_free_full (item_uris, g_free); nautilus_file_list_free (selection); } static void action_copy_to (GSimpleAction *action, GVariant *state, gpointer user_data) { NautilusFilesView *view; view = NAUTILUS_FILES_VIEW (user_data); copy_or_move_selection (view, FALSE); } static void action_move_to (GSimpleAction *action, GVariant *state, gpointer user_data) { NautilusFilesView *view; view = NAUTILUS_FILES_VIEW (user_data); copy_or_move_selection (view, TRUE); } typedef struct { NautilusFilesView *view; NautilusFile *target; } PasteIntoData; static void paste_into_clipboard_received_callback (GtkClipboard *clipboard, const gchar *selection_data, gpointer callback_data) { NautilusFilesViewPrivate *priv; PasteIntoData *data; NautilusFilesView *view; char *directory_uri; data = (PasteIntoData *) callback_data; view = NAUTILUS_FILES_VIEW (data->view); priv = nautilus_files_view_get_instance_private (view); if (priv->slot != NULL) { directory_uri = nautilus_file_get_activation_uri (data->target); paste_clipboard_data (view, selection_data, directory_uri); g_free (directory_uri); } g_object_unref (view); nautilus_file_unref (data->target); g_free (data); } static void paste_into (NautilusFilesView *view, NautilusFile *target) { PasteIntoData *data; g_assert (NAUTILUS_IS_FILES_VIEW (view)); g_assert (NAUTILUS_IS_FILE (target)); data = g_new (PasteIntoData, 1); data->view = g_object_ref (view); data->target = nautilus_file_ref (target); gtk_clipboard_request_text (nautilus_clipboard_get (GTK_WIDGET (view)), paste_into_clipboard_received_callback, data); } static void action_paste_files_into (GSimpleAction *action, GVariant *state, gpointer user_data) { NautilusFilesView *view; g_autolist (NautilusFile) selection = NULL; view = NAUTILUS_FILES_VIEW (user_data); selection = nautilus_view_get_selection (NAUTILUS_VIEW (view)); if (selection != NULL) { paste_into (view, NAUTILUS_FILE (selection->data)); } } static void real_action_rename (NautilusFilesView *view) { NautilusFile *file; g_autolist (NautilusFile) selection = NULL; GtkWidget *dialog; g_assert (NAUTILUS_IS_FILES_VIEW (view)); selection = nautilus_view_get_selection (NAUTILUS_VIEW (view)); if (selection != NULL) { /* If there is more than one file selected, invoke a batch renamer */ if (selection->next != NULL) { GdkCursor *cursor; GdkDisplay *display; display = gtk_widget_get_display (GTK_WIDGET (nautilus_files_view_get_window (view))); cursor = gdk_cursor_new_from_name (display, "progress"); gdk_window_set_cursor (gtk_widget_get_window (GTK_WIDGET (nautilus_files_view_get_window (view))), cursor); g_object_unref (cursor); dialog = nautilus_batch_rename_dialog_new (selection, nautilus_files_view_get_model (view), nautilus_files_view_get_window (view)); gtk_widget_show (GTK_WIDGET (dialog)); } else { file = NAUTILUS_FILE (selection->data); nautilus_files_view_rename_file_popover_new (view, file); } } } static void action_rename (GSimpleAction *action, GVariant *state, gpointer user_data) { real_action_rename (NAUTILUS_FILES_VIEW (user_data)); } typedef struct { NautilusFilesView *view; GHashTable *added_locations; } ExtractData; static void extract_done (GList *outputs, gpointer user_data) { NautilusFilesViewPrivate *priv; ExtractData *data; GList *l; gboolean all_files_acknowledged; data = user_data; if (data->view == NULL) { goto out; } priv = nautilus_files_view_get_instance_private (data->view); g_signal_handlers_disconnect_by_func (data->view, G_CALLBACK (track_newly_added_locations), data->added_locations); if (outputs == NULL) { goto out; } all_files_acknowledged = TRUE; for (l = outputs; l && all_files_acknowledged; l = l->next) { all_files_acknowledged = g_hash_table_contains (data->added_locations, l->data); } if (all_files_acknowledged) { GList *selection = NULL; for (l = outputs; l != NULL; l = l->next) { selection = g_list_prepend (selection, nautilus_file_get (l->data)); } nautilus_files_view_set_selection (NAUTILUS_VIEW (data->view), selection); nautilus_files_view_reveal_selection (data->view); nautilus_file_list_free (selection); } else { for (l = outputs; l != NULL; l = l->next) { gboolean acknowledged; acknowledged = g_hash_table_contains (data->added_locations, l->data); g_hash_table_insert (priv->pending_reveal, nautilus_file_get (l->data), GUINT_TO_POINTER (acknowledged)); } } out: g_hash_table_destroy (data->added_locations); if (data->view != NULL) { g_object_remove_weak_pointer (G_OBJECT (data->view), (gpointer *) &data->view); } g_free (data); } static void extract_files (NautilusFilesView *view, GList *files, GFile *destination_directory) { GList *locations = NULL; GList *l; gboolean extracting_to_current_directory; if (files == NULL) { return; } for (l = files; l != NULL; l = l->next) { locations = g_list_prepend (locations, nautilus_file_get_location (l->data)); } locations = g_list_reverse (locations); extracting_to_current_directory = g_file_equal (destination_directory, nautilus_view_get_location (NAUTILUS_VIEW (view))); if (extracting_to_current_directory) { ExtractData *data; data = g_new (ExtractData, 1); data->view = view; data->added_locations = g_hash_table_new_full (g_file_hash, (GEqualFunc) g_file_equal, g_object_unref, NULL); g_object_add_weak_pointer (G_OBJECT (data->view), (gpointer *) &data->view); g_signal_connect_data (view, "add-files", G_CALLBACK (track_newly_added_locations), data->added_locations, NULL, G_CONNECT_AFTER); nautilus_file_operations_extract_files (locations, destination_directory, nautilus_files_view_get_containing_window (view), extract_done, data); } else { nautilus_file_operations_extract_files (locations, destination_directory, nautilus_files_view_get_containing_window (view), NULL, NULL); } g_list_free_full (locations, g_object_unref); } typedef struct { NautilusFilesView *view; GList *files; } ExtractToData; static void on_extract_destination_dialog_response (GtkDialog *dialog, gint response_id, gpointer user_data) { ExtractToData *data; data = user_data; if (response_id == GTK_RESPONSE_OK) { g_autoptr (GFile) destination_directory = NULL; destination_directory = gtk_file_chooser_get_file (GTK_FILE_CHOOSER (dialog)); extract_files (data->view, data->files, destination_directory); } gtk_widget_destroy (GTK_WIDGET (dialog)); nautilus_file_list_free (data->files); g_free (data); } static void extract_files_to_chosen_location (NautilusFilesView *view, GList *files) { NautilusFilesViewPrivate *priv; ExtractToData *data; GtkWidget *dialog; g_autofree char *uri = NULL; priv = nautilus_files_view_get_instance_private (view); if (files == NULL) { return; } data = g_new (ExtractToData, 1); dialog = gtk_file_chooser_dialog_new (_("Select Extract Destination"), GTK_WINDOW (nautilus_files_view_get_window (view)), GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER, _("_Cancel"), GTK_RESPONSE_CANCEL, _("_Select"), GTK_RESPONSE_OK, NULL); gtk_file_chooser_set_local_only (GTK_FILE_CHOOSER (dialog), FALSE); gtk_dialog_set_default_response (GTK_DIALOG (dialog), GTK_RESPONSE_OK); gtk_window_set_destroy_with_parent (GTK_WINDOW (dialog), TRUE); gtk_window_set_modal (GTK_WINDOW (dialog), TRUE); /* The file chooser will not be able to display the search directory, * so we need to get the base directory of the search if we are, in fact, * in search. */ if (nautilus_view_is_searching (NAUTILUS_VIEW (view))) { NautilusSearchDirectory *search_directory; NautilusDirectory *directory; search_directory = NAUTILUS_SEARCH_DIRECTORY (priv->model); directory = nautilus_search_directory_get_base_model (search_directory); uri = nautilus_directory_get_uri (directory); } else { uri = nautilus_directory_get_uri (priv->model); } gtk_file_chooser_set_current_folder_uri (GTK_FILE_CHOOSER (dialog), uri); data->view = view; data->files = nautilus_file_list_copy (files); g_signal_connect (dialog, "response", G_CALLBACK (on_extract_destination_dialog_response), data); gtk_widget_show_all (dialog); } static void action_extract_here (GSimpleAction *action, GVariant *state, gpointer user_data) { NautilusFilesView *view; g_autolist (NautilusFile) selection = NULL; g_autoptr (GFile) location = NULL; g_autoptr (GFile) parent = NULL; view = NAUTILUS_FILES_VIEW (user_data); selection = nautilus_view_get_selection (NAUTILUS_VIEW (view)); location = nautilus_file_get_location (NAUTILUS_FILE (g_list_first (selection)->data)); /* Get a parent from a random file. We assume all files has a common parent. * But don't assume the parent is the view location, since that's not the * case in list view when expand-folder setting is set */ parent = g_file_get_parent (location); extract_files (view, selection, parent); } static void action_extract_to (GSimpleAction *action, GVariant *state, gpointer user_data) { NautilusFilesView *view; g_autolist (NautilusFile) selection = NULL; view = NAUTILUS_FILES_VIEW (user_data); selection = nautilus_view_get_selection (NAUTILUS_VIEW (view)); extract_files_to_chosen_location (view, selection); } static void action_compress (GSimpleAction *action, GVariant *state, gpointer user_data) { NautilusFilesView *view = user_data; nautilus_files_view_compress_dialog_new (view); } #define BG_KEY_PRIMARY_COLOR "primary-color" #define BG_KEY_SECONDARY_COLOR "secondary-color" #define BG_KEY_COLOR_TYPE "color-shading-type" #define BG_KEY_PICTURE_PLACEMENT "picture-options" #define BG_KEY_PICTURE_URI "picture-uri" static void set_uri_as_wallpaper (const char *uri) { GSettings *settings; settings = gnome_background_preferences; g_settings_delay (settings); if (uri == NULL) { uri = ""; } g_settings_set_string (settings, BG_KEY_PICTURE_URI, uri); g_settings_set_string (settings, BG_KEY_PRIMARY_COLOR, "#000000"); g_settings_set_string (settings, BG_KEY_SECONDARY_COLOR, "#000000"); g_settings_set_enum (settings, BG_KEY_COLOR_TYPE, G_DESKTOP_BACKGROUND_SHADING_SOLID); g_settings_set_enum (settings, BG_KEY_PICTURE_PLACEMENT, G_DESKTOP_BACKGROUND_STYLE_ZOOM); /* Apply changes atomically. */ g_settings_apply (settings); } static void wallpaper_copy_done_callback (GHashTable *debuting_files, gboolean success, gpointer data) { GHashTableIter iter; gpointer key, value; g_hash_table_iter_init (&iter, debuting_files); while (g_hash_table_iter_next (&iter, &key, &value)) { char *uri; uri = g_file_get_uri (G_FILE (key)); set_uri_as_wallpaper (uri); g_free (uri); break; } } static gboolean can_set_wallpaper (GList *selection) { NautilusFile *file; if (g_list_length (selection) != 1) { return FALSE; } file = NAUTILUS_FILE (selection->data); if (!nautilus_file_is_mime_type (file, "image/*")) { return FALSE; } /* FIXME: check file size? */ return TRUE; } static void action_set_as_wallpaper (GSimpleAction *action, GVariant *state, gpointer user_data) { g_autolist (NautilusFile) selection = NULL; /* Copy the item to Pictures/Wallpaper (internationalized) since it may be * remote. Then set it as the current wallpaper. */ g_assert (NAUTILUS_IS_FILES_VIEW (user_data)); selection = nautilus_view_get_selection (user_data); if (can_set_wallpaper (selection)) { NautilusFile *file; char *target_uri; GList *uris; GFile *parent; GFile *target; file = NAUTILUS_FILE (selection->data); parent = g_file_new_for_path (g_get_user_special_dir (G_USER_DIRECTORY_PICTURES)); target = g_file_get_child (parent, _("Wallpapers")); g_object_unref (parent); g_file_make_directory_with_parents (target, NULL, NULL); target_uri = g_file_get_uri (target); g_object_unref (target); uris = g_list_prepend (NULL, nautilus_file_get_uri (file)); nautilus_file_operations_copy_move (uris, target_uri, GDK_ACTION_COPY, GTK_WIDGET (user_data), wallpaper_copy_done_callback, NULL); g_free (target_uri); g_list_free_full (uris, g_free); } } static void file_mount_callback (NautilusFile *file, GFile *result_location, GError *error, gpointer callback_data) { NautilusFilesView *view; view = NAUTILUS_FILES_VIEW (callback_data); if (error != NULL && (error->domain != G_IO_ERROR || (error->code != G_IO_ERROR_CANCELLED && error->code != G_IO_ERROR_FAILED_HANDLED && error->code != G_IO_ERROR_ALREADY_MOUNTED))) { char *text; char *name; name = nautilus_file_get_display_name (file); /* Translators: %s is a file name formatted for display */ text = g_strdup_printf (_("Unable to access “%s”"), name); show_dialog (text, error->message, GTK_WINDOW (nautilus_files_view_get_window (view)), GTK_MESSAGE_ERROR); g_free (text); g_free (name); } } static void file_unmount_callback (NautilusFile *file, GFile *result_location, GError *error, gpointer callback_data) { NautilusFilesView *view; view = NAUTILUS_FILES_VIEW (callback_data); g_object_unref (view); if (error != NULL && (error->domain != G_IO_ERROR || (error->code != G_IO_ERROR_CANCELLED && error->code != G_IO_ERROR_FAILED_HANDLED))) { char *text; char *name; name = nautilus_file_get_display_name (file); /* Translators: %s is a file name formatted for display */ text = g_strdup_printf (_("Unable to remove “%s”"), name); show_dialog (text, error->message, GTK_WINDOW (nautilus_files_view_get_window (view)), GTK_MESSAGE_ERROR); g_free (text); g_free (name); } } static void file_eject_callback (NautilusFile *file, GFile *result_location, GError *error, gpointer callback_data) { NautilusFilesView *view; view = NAUTILUS_FILES_VIEW (callback_data); g_object_unref (view); if (error != NULL && (error->domain != G_IO_ERROR || (error->code != G_IO_ERROR_CANCELLED && error->code != G_IO_ERROR_FAILED_HANDLED))) { char *text; char *name; name = nautilus_file_get_display_name (file); /* Translators: %s is a file name formatted for display */ text = g_strdup_printf (_("Unable to eject “%s”"), name); show_dialog (text, error->message, GTK_WINDOW (nautilus_files_view_get_window (view)), GTK_MESSAGE_ERROR); g_free (text); g_free (name); } } static void file_stop_callback (NautilusFile *file, GFile *result_location, GError *error, gpointer callback_data) { NautilusFilesView *view; view = NAUTILUS_FILES_VIEW (callback_data); if (error != NULL && (error->domain != G_IO_ERROR || (error->code != G_IO_ERROR_CANCELLED && error->code != G_IO_ERROR_FAILED_HANDLED))) { show_dialog (_("Unable to stop drive"), error->message, GTK_WINDOW (nautilus_files_view_get_window (view)), GTK_MESSAGE_ERROR); } } static void action_mount_volume (GSimpleAction *action, GVariant *state, gpointer user_data) { NautilusFile *file; GList *selection, *l; NautilusFilesView *view; GMountOperation *mount_op; view = NAUTILUS_FILES_VIEW (user_data); selection = nautilus_view_get_selection (NAUTILUS_VIEW (view)); for (l = selection; l != NULL; l = l->next) { file = NAUTILUS_FILE (l->data); if (nautilus_file_can_mount (file)) { mount_op = gtk_mount_operation_new (nautilus_files_view_get_containing_window (view)); g_mount_operation_set_password_save (mount_op, G_PASSWORD_SAVE_FOR_SESSION); nautilus_file_mount (file, mount_op, NULL, file_mount_callback, view); g_object_unref (mount_op); } } nautilus_file_list_free (selection); } static void action_unmount_volume (GSimpleAction *action, GVariant *state, gpointer user_data) { NautilusFile *file; g_autolist (NautilusFile) selection = NULL; GList *l; NautilusFilesView *view; view = NAUTILUS_FILES_VIEW (user_data); selection = nautilus_view_get_selection (NAUTILUS_VIEW (view)); for (l = selection; l != NULL; l = l->next) { file = NAUTILUS_FILE (l->data); if (nautilus_file_can_unmount (file)) { GMountOperation *mount_op; mount_op = gtk_mount_operation_new (nautilus_files_view_get_containing_window (view)); nautilus_file_unmount (file, mount_op, NULL, file_unmount_callback, g_object_ref (view)); g_object_unref (mount_op); } } } static void action_eject_volume (GSimpleAction *action, GVariant *state, gpointer user_data) { NautilusFile *file; g_autolist (NautilusFile) selection = NULL; GList *l; NautilusFilesView *view; view = NAUTILUS_FILES_VIEW (user_data); selection = nautilus_view_get_selection (NAUTILUS_VIEW (view)); for (l = selection; l != NULL; l = l->next) { file = NAUTILUS_FILE (l->data); if (nautilus_file_can_eject (file)) { GMountOperation *mount_op; mount_op = gtk_mount_operation_new (nautilus_files_view_get_containing_window (view)); nautilus_file_eject (file, mount_op, NULL, file_eject_callback, g_object_ref (view)); g_object_unref (mount_op); } } } static void file_start_callback (NautilusFile *file, GFile *result_location, GError *error, gpointer callback_data) { NautilusFilesView *view; view = NAUTILUS_FILES_VIEW (callback_data); if (error != NULL && (error->domain != G_IO_ERROR || (error->code != G_IO_ERROR_CANCELLED && error->code != G_IO_ERROR_FAILED_HANDLED && error->code != G_IO_ERROR_ALREADY_MOUNTED))) { char *text; char *name; name = nautilus_file_get_display_name (file); /* Translators: %s is a file name formatted for display */ text = g_strdup_printf (_("Unable to start “%s”"), name); show_dialog (text, error->message, GTK_WINDOW (nautilus_files_view_get_window (view)), GTK_MESSAGE_ERROR); g_free (text); g_free (name); } } static void action_start_volume (GSimpleAction *action, GVariant *state, gpointer user_data) { NautilusFile *file; g_autolist (NautilusFile) selection = NULL; GList *l; NautilusFilesView *view; GMountOperation *mount_op; view = NAUTILUS_FILES_VIEW (user_data); selection = nautilus_view_get_selection (NAUTILUS_VIEW (view)); for (l = selection; l != NULL; l = l->next) { file = NAUTILUS_FILE (l->data); if (nautilus_file_can_start (file) || nautilus_file_can_start_degraded (file)) { mount_op = gtk_mount_operation_new (nautilus_files_view_get_containing_window (view)); nautilus_file_start (file, mount_op, NULL, file_start_callback, view); g_object_unref (mount_op); } } } static void action_stop_volume (GSimpleAction *action, GVariant *state, gpointer user_data) { NautilusFile *file; g_autolist (NautilusFile) selection = NULL; GList *l; NautilusFilesView *view; view = NAUTILUS_FILES_VIEW (user_data); selection = nautilus_view_get_selection (NAUTILUS_VIEW (view)); for (l = selection; l != NULL; l = l->next) { file = NAUTILUS_FILE (l->data); if (nautilus_file_can_stop (file)) { GMountOperation *mount_op; mount_op = gtk_mount_operation_new (nautilus_files_view_get_containing_window (view)); nautilus_file_stop (file, mount_op, NULL, file_stop_callback, view); g_object_unref (mount_op); } } } static void action_detect_media (GSimpleAction *action, GVariant *state, gpointer user_data) { NautilusFile *file; g_autolist (NautilusFile) selection = NULL; GList *l; NautilusView *view; view = NAUTILUS_VIEW (user_data); selection = nautilus_view_get_selection (view); for (l = selection; l != NULL; l = l->next) { file = NAUTILUS_FILE (l->data); if (nautilus_file_can_poll_for_media (file) && !nautilus_file_is_media_check_automatic (file)) { nautilus_file_poll_for_media (file); } } } const GActionEntry view_entries[] = { /* Toolbar menu */ { "zoom-in", action_zoom_in }, { "zoom-out", action_zoom_out }, { "zoom-standard", action_zoom_standard }, { "show-hidden-files", NULL, NULL, "true", action_show_hidden_files }, /* Background menu */ { "new-folder", action_new_folder }, { "select-all", action_select_all }, { "paste", action_paste_files }, { "paste_accel", action_paste_files_accel }, { "create-link", action_create_links }, { "new-document" }, /* Selection menu */ { "scripts" }, { "new-folder-with-selection", action_new_folder_with_selection }, { "open-scripts-folder", action_open_scripts_folder }, { "open-item-location", action_open_item_location }, { "open-with-default-application", action_open_with_default_application }, { "open-with-other-application", action_open_with_other_application }, { "open-item-new-window", action_open_item_new_window }, { "open-item-new-tab", action_open_item_new_tab }, { "cut", action_cut}, { "copy", action_copy}, { "create-link-in-place", action_create_links_in_place }, { "move-to", action_move_to}, { "copy-to", action_copy_to}, { "move-to-trash", action_move_to_trash}, { "delete-from-trash", action_delete }, { "star", action_star}, { "unstar", action_unstar}, /* We separate the shortcut and the menu item since we want the shortcut * to always be available, but we don't want the menu item shown if not * completely necesary. Since the visibility of the menu item is based on * the action enability, we need to split the actions for the menu and the * shortcut. */ { "delete-permanently-shortcut", action_delete }, { "delete-permanently-menu-item", action_delete }, /* This is only shown when the setting to show always delete permanently * is set and when the common use cases for delete permanently which uses * Delete as a shortcut are not needed. For instance this will be only * present when the setting is true and when it can trash files */ { "permanent-delete-permanently-menu-item", action_delete }, { "remove-from-recent", action_remove_from_recent }, { "restore-from-trash", action_restore_from_trash}, { "paste-into", action_paste_files_into }, { "rename", action_rename}, { "extract-here", action_extract_here }, { "extract-to", action_extract_to }, { "compress", action_compress }, { "properties", action_properties}, { "current-directory-properties", action_current_dir_properties}, { "set-as-wallpaper", action_set_as_wallpaper }, { "mount-volume", action_mount_volume }, { "unmount-volume", action_unmount_volume }, { "eject-volume", action_eject_volume }, { "start-volume", action_start_volume }, { "stop-volume", action_stop_volume }, { "detect-media", action_detect_media }, /* Only accesible by shorcuts */ { "select-pattern", action_select_pattern }, { "invert-selection", action_invert_selection }, }; static gboolean can_paste_into_file (NautilusFile *file) { if (nautilus_file_is_directory (file) && nautilus_file_can_write (file)) { return TRUE; } if (nautilus_file_has_activation_uri (file)) { GFile *location; NautilusFile *activation_file; gboolean res; location = nautilus_file_get_activation_location (file); activation_file = nautilus_file_get (location); g_object_unref (location); /* The target location might not have data for it read yet, * and we can't want to do sync I/O, so treat the unknown * case as can-write */ res = (nautilus_file_get_file_type (activation_file) == G_FILE_TYPE_UNKNOWN) || (nautilus_file_get_file_type (activation_file) == G_FILE_TYPE_DIRECTORY && nautilus_file_can_write (activation_file)); nautilus_file_unref (activation_file); return res; } return FALSE; } static void on_clipboard_contents_received (GtkClipboard *clipboard, const gchar *selection_data, gpointer user_data) { NautilusFilesViewPrivate *priv; NautilusFilesView *view; gboolean can_link_from_copied_files; gboolean settings_show_create_link; gboolean is_read_only; gboolean selection_contains_recent; gboolean selection_contains_starred; GAction *action; gboolean is_data_valid; view = NAUTILUS_FILES_VIEW (user_data); priv = nautilus_files_view_get_instance_private (view); if (priv->slot == NULL || !priv->active) { /* We've been destroyed or became inactive since call */ g_object_unref (view); return; } is_data_valid = nautilus_clipboard_is_data_valid_from_selection_data (selection_data); settings_show_create_link = g_settings_get_boolean (nautilus_preferences, NAUTILUS_PREFERENCES_SHOW_CREATE_LINK); is_read_only = nautilus_files_view_is_read_only (view); selection_contains_recent = showing_recent_directory (view); selection_contains_starred = showing_starred_directory (view); can_link_from_copied_files = !nautilus_clipboard_is_cut_from_selection_data (selection_data) && !selection_contains_recent && !selection_contains_starred && !is_read_only && selection_data != NULL; action = g_action_map_lookup_action (G_ACTION_MAP (priv->view_action_group), "create-link"); g_simple_action_set_enabled (G_SIMPLE_ACTION (action), can_link_from_copied_files && settings_show_create_link); action = g_action_map_lookup_action (G_ACTION_MAP (priv->view_action_group), "paste"); /* Take into account if the action was previously disabled for other reasons, * like the directory not being writabble */ g_simple_action_set_enabled (G_SIMPLE_ACTION (action), is_data_valid && g_action_get_enabled (action)); action = g_action_map_lookup_action (G_ACTION_MAP (priv->view_action_group), "paste-into"); g_simple_action_set_enabled (G_SIMPLE_ACTION (action), is_data_valid && g_action_get_enabled (action)); action = g_action_map_lookup_action (G_ACTION_MAP (priv->view_action_group), "create-link"); g_simple_action_set_enabled (G_SIMPLE_ACTION (action), is_data_valid && g_action_get_enabled (action)); g_object_unref (view); } static void file_should_show_foreach (NautilusFile *file, gboolean *show_mount, gboolean *show_unmount, gboolean *show_eject, gboolean *show_start, gboolean *show_stop, gboolean *show_poll, GDriveStartStopType *start_stop_type) { *show_mount = FALSE; *show_unmount = FALSE; *show_eject = FALSE; *show_start = FALSE; *show_stop = FALSE; *show_poll = FALSE; if (nautilus_file_can_eject (file)) { *show_eject = TRUE; } if (nautilus_file_can_mount (file)) { *show_mount = TRUE; } if (nautilus_file_can_start (file) || nautilus_file_can_start_degraded (file)) { *show_start = TRUE; } if (nautilus_file_can_stop (file)) { *show_stop = TRUE; } /* Dot not show both Unmount and Eject/Safe Removal; too confusing to * have too many menu entries */ if (nautilus_file_can_unmount (file) && !*show_eject && !*show_stop) { *show_unmount = TRUE; } if (nautilus_file_can_poll_for_media (file) && !nautilus_file_is_media_check_automatic (file)) { *show_poll = TRUE; } *start_stop_type = nautilus_file_get_start_stop_type (file); } static gboolean can_restore_from_trash (GList *files) { NautilusFile *original_file; NautilusFile *original_dir; GHashTable *original_dirs_hash; GList *original_dirs; gboolean can_restore; original_file = NULL; original_dir = NULL; original_dirs = NULL; original_dirs_hash = NULL; if (files != NULL) { if (g_list_length (files) == 1) { original_file = nautilus_file_get_trash_original_file (files->data); } else { original_dirs_hash = nautilus_trashed_files_get_original_directories (files, NULL); if (original_dirs_hash != NULL) { original_dirs = g_hash_table_get_keys (original_dirs_hash); if (g_list_length (original_dirs) == 1) { original_dir = nautilus_file_ref (NAUTILUS_FILE (original_dirs->data)); } } } } can_restore = original_file != NULL || original_dirs != NULL; nautilus_file_unref (original_file); nautilus_file_unref (original_dir); g_list_free (original_dirs); if (original_dirs_hash != NULL) { g_hash_table_destroy (original_dirs_hash); } return can_restore; } static void on_clipboard_owner_changed (GtkClipboard *clipboard, GdkEvent *event, gpointer user_data) { NautilusFilesView *self = NAUTILUS_FILES_VIEW (user_data); /* Update paste menu item */ nautilus_files_view_update_context_menus (self); } static gboolean can_delete_all (GList *files) { NautilusFile *file; GList *l; for (l = files; l != NULL; l = l->next) { file = l->data; if (!nautilus_file_can_delete (file)) { return FALSE; } } return TRUE; } static gboolean can_trash_all (GList *files) { NautilusFile *file; GList *l; for (l = files; l != NULL; l = l->next) { file = l->data; if (!nautilus_file_can_trash (file)) { return FALSE; } } return TRUE; } static gboolean all_in_trash (GList *files) { NautilusFile *file; GList *l; for (l = files; l != NULL; l = l->next) { file = l->data; if (!nautilus_file_is_in_trash (file)) { return FALSE; } } return TRUE; } static gboolean can_extract_all (GList *files) { NautilusFile *file; GList *l; for (l = files; l != NULL; l = l->next) { file = l->data; if (!nautilus_file_is_archive (file)) { return FALSE; } } return TRUE; } static gboolean nautilus_handles_all_files_to_extract (GList *files) { NautilusFile *file; GList *l; for (l = files; l != NULL; l = l->next) { file = l->data; if (!nautilus_mime_file_extracts (file)) { return FALSE; } } return TRUE; } GActionGroup * nautilus_files_view_get_action_group (NautilusFilesView *view) { NautilusFilesViewPrivate *priv; g_assert (NAUTILUS_IS_FILES_VIEW (view)); priv = nautilus_files_view_get_instance_private (view); return priv->view_action_group; } static void real_update_actions_state (NautilusFilesView *view) { NautilusFilesViewPrivate *priv; g_autolist (NautilusFile) selection = NULL; GList *l; gint selection_count; gboolean zoom_level_is_default; gboolean selection_contains_home_dir; gboolean selection_contains_recent; gboolean selection_contains_search; gboolean selection_contains_starred; gboolean selection_all_in_trash; gboolean selection_is_read_only; gboolean can_create_files; gboolean can_delete_files; gboolean can_move_files; gboolean can_trash_files; gboolean can_copy_files; gboolean can_paste_files_into; gboolean can_extract_files; gboolean handles_all_files_to_extract; gboolean can_extract_here; gboolean item_opens_in_view; gboolean is_read_only; GAction *action; GActionGroup *view_action_group; gboolean show_mount; gboolean show_unmount; gboolean show_eject; gboolean show_start; gboolean show_stop; gboolean show_detect_media; gboolean settings_show_delete_permanently; gboolean settings_show_create_link; GDriveStartStopType start_stop_type; g_autoptr (GFile) current_location = NULL; g_autofree gchar *current_uri = NULL; gboolean current_directory_tracked; gboolean show_star; gboolean show_unstar; gchar *uri; priv = nautilus_files_view_get_instance_private (view); view_action_group = priv->view_action_group; selection = nautilus_view_get_selection (NAUTILUS_VIEW (view)); selection_count = g_list_length (selection); selection_contains_home_dir = home_dir_in_selection (selection); selection_contains_recent = showing_recent_directory (view); selection_contains_starred = showing_starred_directory (view); selection_contains_search = nautilus_view_is_searching (NAUTILUS_VIEW (view)); selection_is_read_only = selection_count == 1 && (!nautilus_file_can_write (NAUTILUS_FILE (selection->data)) && !nautilus_file_has_activation_uri (NAUTILUS_FILE (selection->data))); selection_all_in_trash = all_in_trash (selection); zoom_level_is_default = nautilus_files_view_is_zoom_level_default (view); is_read_only = nautilus_files_view_is_read_only (view); can_create_files = nautilus_files_view_supports_creating_files (view); can_delete_files = can_delete_all (selection) && selection_count != 0 && !selection_contains_home_dir; can_trash_files = can_trash_all (selection) && selection_count != 0 && !selection_contains_home_dir; can_copy_files = selection_count != 0; can_move_files = can_delete_files && !selection_contains_recent && !selection_contains_starred; can_paste_files_into = (!selection_contains_recent && !selection_contains_starred && selection_count == 1 && can_paste_into_file (NAUTILUS_FILE (selection->data))); can_extract_files = selection_count != 0 && can_extract_all (selection); can_extract_here = nautilus_files_view_supports_extract_here (view); handles_all_files_to_extract = nautilus_handles_all_files_to_extract (selection); settings_show_delete_permanently = g_settings_get_boolean (nautilus_preferences, NAUTILUS_PREFERENCES_SHOW_DELETE_PERMANENTLY); settings_show_create_link = g_settings_get_boolean (nautilus_preferences, NAUTILUS_PREFERENCES_SHOW_CREATE_LINK); /* Right click actions */ /* Selection menu actions */ action = g_action_map_lookup_action (G_ACTION_MAP (view_action_group), "new-folder-with-selection"); g_simple_action_set_enabled (G_SIMPLE_ACTION (action), can_create_files && can_delete_files && (selection_count > 1) && !selection_contains_recent && !selection_contains_starred); action = g_action_map_lookup_action (G_ACTION_MAP (view_action_group), "rename"); if (selection_count > 1) { g_simple_action_set_enabled (G_SIMPLE_ACTION (action), nautilus_file_can_rename_files (selection)); } else { g_simple_action_set_enabled (G_SIMPLE_ACTION (action), selection_count == 1 && nautilus_file_can_rename (selection->data)); } action = g_action_map_lookup_action (G_ACTION_MAP (view_action_group), "extract-here"); g_simple_action_set_enabled (G_SIMPLE_ACTION (action), can_extract_files && !handles_all_files_to_extract && can_extract_here); action = g_action_map_lookup_action (G_ACTION_MAP (view_action_group), "extract-to"); g_simple_action_set_enabled (G_SIMPLE_ACTION (action), can_extract_files && (!handles_all_files_to_extract || can_extract_here)); action = g_action_map_lookup_action (G_ACTION_MAP (view_action_group), "compress"); g_simple_action_set_enabled (G_SIMPLE_ACTION (action), can_create_files && can_copy_files); action = g_action_map_lookup_action (G_ACTION_MAP (view_action_group), "open-item-location"); g_simple_action_set_enabled (G_SIMPLE_ACTION (action), selection_count == 1 && (selection_contains_recent || selection_contains_search || selection_contains_starred)); action = g_action_map_lookup_action (G_ACTION_MAP (view_action_group), "new-folder"); g_simple_action_set_enabled (G_SIMPLE_ACTION (action), can_create_files); item_opens_in_view = selection_count != 0; for (l = selection; l != NULL; l = l->next) { NautilusFile *file; file = NAUTILUS_FILE (selection->data); if (!nautilus_file_opens_in_view (file)) { item_opens_in_view = FALSE; } if (!item_opens_in_view) { break; } } action = g_action_map_lookup_action (G_ACTION_MAP (view_action_group), "open-with-default-application"); g_simple_action_set_enabled (G_SIMPLE_ACTION (action), selection_count != 0); /* Allow to select a different application to open the item */ action = g_action_map_lookup_action (G_ACTION_MAP (view_action_group), "open-with-other-application"); g_simple_action_set_enabled (G_SIMPLE_ACTION (action), selection_count > 0); action = g_action_map_lookup_action (G_ACTION_MAP (view_action_group), "open-item-new-tab"); g_simple_action_set_enabled (G_SIMPLE_ACTION (action), item_opens_in_view); action = g_action_map_lookup_action (G_ACTION_MAP (view_action_group), "open-item-new-window"); g_simple_action_set_enabled (G_SIMPLE_ACTION (action), item_opens_in_view); action = g_action_map_lookup_action (G_ACTION_MAP (view_action_group), "set-as-wallpaper"); g_simple_action_set_enabled (G_SIMPLE_ACTION (action), can_set_wallpaper (selection)); action = g_action_map_lookup_action (G_ACTION_MAP (view_action_group), "restore-from-trash"); g_simple_action_set_enabled (G_SIMPLE_ACTION (action), can_restore_from_trash (selection)); action = g_action_map_lookup_action (G_ACTION_MAP (view_action_group), "move-to-trash"); g_simple_action_set_enabled (G_SIMPLE_ACTION (action), can_trash_files); action = g_action_map_lookup_action (G_ACTION_MAP (view_action_group), "delete-from-trash"); g_simple_action_set_enabled (G_SIMPLE_ACTION (action), can_delete_files && selection_all_in_trash); action = g_action_map_lookup_action (G_ACTION_MAP (view_action_group), "delete-permanently-shortcut"); g_simple_action_set_enabled (G_SIMPLE_ACTION (action), can_delete_files); action = g_action_map_lookup_action (G_ACTION_MAP (view_action_group), "delete-permanently-menu-item"); g_simple_action_set_enabled (G_SIMPLE_ACTION (action), can_delete_files && !can_trash_files && !selection_all_in_trash && !selection_contains_recent && !selection_contains_starred); action = g_action_map_lookup_action (G_ACTION_MAP (view_action_group), "permanent-delete-permanently-menu-item"); g_simple_action_set_enabled (G_SIMPLE_ACTION (action), can_delete_files && can_trash_files && settings_show_delete_permanently && !selection_all_in_trash && !selection_contains_recent && !selection_contains_starred); action = g_action_map_lookup_action (G_ACTION_MAP (view_action_group), "remove-from-recent"); g_simple_action_set_enabled (G_SIMPLE_ACTION (action), selection_contains_recent && selection_count > 0); action = g_action_map_lookup_action (G_ACTION_MAP (view_action_group), "cut"); g_simple_action_set_enabled (G_SIMPLE_ACTION (action), can_move_files && !selection_contains_recent && !selection_contains_starred); action = g_action_map_lookup_action (G_ACTION_MAP (view_action_group), "copy"); g_simple_action_set_enabled (G_SIMPLE_ACTION (action), can_copy_files); action = g_action_map_lookup_action (G_ACTION_MAP (view_action_group), "create-link-in-place"); g_simple_action_set_enabled (G_SIMPLE_ACTION (action), can_copy_files && can_create_files && settings_show_create_link); action = g_action_map_lookup_action (G_ACTION_MAP (view_action_group), "copy-to"); g_simple_action_set_enabled (G_SIMPLE_ACTION (action), can_copy_files); action = g_action_map_lookup_action (G_ACTION_MAP (view_action_group), "move-to"); g_simple_action_set_enabled (G_SIMPLE_ACTION (action), can_move_files && !selection_contains_recent && !selection_contains_starred); /* Drive menu */ show_mount = (selection != NULL); show_unmount = (selection != NULL); show_eject = (selection != NULL); show_start = (selection != NULL && selection_count == 1); show_stop = (selection != NULL && selection_count == 1); show_detect_media = (selection != NULL && selection_count == 1); for (l = selection; l != NULL && (show_mount || show_unmount || show_eject || show_start || show_stop || show_detect_media); l = l->next) { NautilusFile *file; gboolean show_mount_one; gboolean show_unmount_one; gboolean show_eject_one; gboolean show_start_one; gboolean show_stop_one; gboolean show_detect_media_one; file = NAUTILUS_FILE (l->data); file_should_show_foreach (file, &show_mount_one, &show_unmount_one, &show_eject_one, &show_start_one, &show_stop_one, &show_detect_media_one, &start_stop_type); show_mount &= show_mount_one; show_unmount &= show_unmount_one; show_eject &= show_eject_one; show_start &= show_start_one; show_stop &= show_stop_one; show_detect_media &= show_detect_media_one; } action = g_action_map_lookup_action (G_ACTION_MAP (view_action_group), "mount-volume"); g_simple_action_set_enabled (G_SIMPLE_ACTION (action), show_mount); action = g_action_map_lookup_action (G_ACTION_MAP (view_action_group), "unmount-volume"); g_simple_action_set_enabled (G_SIMPLE_ACTION (action), show_unmount); action = g_action_map_lookup_action (G_ACTION_MAP (view_action_group), "eject-volume"); g_simple_action_set_enabled (G_SIMPLE_ACTION (action), show_eject); action = g_action_map_lookup_action (G_ACTION_MAP (view_action_group), "start-volume"); g_simple_action_set_enabled (G_SIMPLE_ACTION (action), show_start); action = g_action_map_lookup_action (G_ACTION_MAP (view_action_group), "stop-volume"); g_simple_action_set_enabled (G_SIMPLE_ACTION (action), show_stop); action = g_action_map_lookup_action (G_ACTION_MAP (view_action_group), "detect-media"); g_simple_action_set_enabled (G_SIMPLE_ACTION (action), show_detect_media); action = g_action_map_lookup_action (G_ACTION_MAP (view_action_group), "scripts"); g_simple_action_set_enabled (G_SIMPLE_ACTION (action), priv->scripts_present); /* Background menu actions */ action = g_action_map_lookup_action (G_ACTION_MAP (view_action_group), "new-folder"); g_simple_action_set_enabled (G_SIMPLE_ACTION (action), can_create_files); action = g_action_map_lookup_action (G_ACTION_MAP (view_action_group), "paste"); g_simple_action_set_enabled (G_SIMPLE_ACTION (action), !is_read_only && !selection_contains_recent && !selection_contains_starred); action = g_action_map_lookup_action (G_ACTION_MAP (view_action_group), "paste-into"); g_simple_action_set_enabled (G_SIMPLE_ACTION (action), !selection_is_read_only && !selection_contains_recent && can_paste_files_into && !selection_contains_starred); action = g_action_map_lookup_action (G_ACTION_MAP (view_action_group), "properties"); g_simple_action_set_enabled (G_SIMPLE_ACTION (action), TRUE); action = g_action_map_lookup_action (G_ACTION_MAP (view_action_group), "new-document"); g_simple_action_set_enabled (G_SIMPLE_ACTION (action), can_create_files && !selection_contains_recent && !selection_contains_starred && priv->templates_present); g_object_ref (view); /* Need to keep the object alive until we get the reply */ gtk_clipboard_request_text (nautilus_clipboard_get (GTK_WIDGET (view)), on_clipboard_contents_received, view); action = g_action_map_lookup_action (G_ACTION_MAP (view_action_group), "select-all"); g_simple_action_set_enabled (G_SIMPLE_ACTION (action), !nautilus_files_view_is_empty (view)); /* Toolbar menu actions */ g_action_group_change_action_state (view_action_group, "show-hidden-files", g_variant_new_boolean (priv->show_hidden_files)); /* Zoom */ action = g_action_map_lookup_action (G_ACTION_MAP (view_action_group), "zoom-in"); g_simple_action_set_enabled (G_SIMPLE_ACTION (action), nautilus_files_view_can_zoom_in (view)); action = g_action_map_lookup_action (G_ACTION_MAP (view_action_group), "zoom-out"); g_simple_action_set_enabled (G_SIMPLE_ACTION (action), nautilus_files_view_can_zoom_out (view)); action = g_action_map_lookup_action (G_ACTION_MAP (view_action_group), "zoom-standard"); g_simple_action_set_enabled (G_SIMPLE_ACTION (action), nautilus_files_view_supports_zooming (view) && !zoom_level_is_default); action = g_action_map_lookup_action (G_ACTION_MAP (view_action_group), "zoom-to-level"); g_simple_action_set_enabled (G_SIMPLE_ACTION (action), !nautilus_files_view_is_empty (view)); /* FIXME: We are assuming tracker indexes XDG folders and ignore the search * setting. This should be fixed in a better way for Nautilus 3.30. * See https://gitlab.gnome.org/GNOME/nautilus/issues/243 */ current_location = nautilus_file_get_location (nautilus_files_view_get_directory_as_file (view)); current_uri = g_file_get_uri (current_location); current_directory_tracked = nautilus_tracker_directory_is_tracked (current_location); show_star = (selection != NULL) && (current_directory_tracked || selection_contains_starred); show_unstar = (selection != NULL) && (current_directory_tracked || selection_contains_starred); for (l = selection; l != NULL; l = l->next) { NautilusFile *file; file = NAUTILUS_FILE (l->data); uri = nautilus_file_get_uri (file); if (!show_star && !show_unstar) { break; } if (nautilus_tag_manager_file_is_starred (priv->tag_manager, uri)) { show_star = FALSE; } else { show_unstar = FALSE; } g_free (uri); } action = g_action_map_lookup_action (G_ACTION_MAP (view_action_group), "star"); g_simple_action_set_enabled (G_SIMPLE_ACTION (action), show_star); action = g_action_map_lookup_action (G_ACTION_MAP (view_action_group), "unstar"); g_simple_action_set_enabled (G_SIMPLE_ACTION (action), show_unstar); } /* Convenience function to be called when updating menus, * so children can subclass it and it will be called when * they chain up to the parent in update_context_menus * or update_toolbar_menus */ void nautilus_files_view_update_actions_state (NautilusFilesView *view) { g_assert (NAUTILUS_IS_FILES_VIEW (view)); NAUTILUS_FILES_VIEW_CLASS (G_OBJECT_GET_CLASS (view))->update_actions_state (view); } static void update_selection_menu (NautilusFilesView *view, GtkBuilder *builder) { g_autolist (NautilusFile) selection = NULL; GList *l; gint selection_count; gboolean show_app; gboolean show_run; gboolean show_extract; gboolean item_opens_in_view; gchar *item_label; GAppInfo *app; GIcon *app_icon; GMenuItem *menu_item; GObject *object; gboolean show_mount; gboolean show_unmount; gboolean show_eject; gboolean show_start; gboolean show_stop; gboolean show_detect_media; GDriveStartStopType start_stop_type; selection = nautilus_view_get_selection (NAUTILUS_VIEW (view)); selection_count = g_list_length (selection); show_mount = (selection != NULL); show_unmount = (selection != NULL); show_eject = (selection != NULL); show_start = (selection != NULL && selection_count == 1); show_stop = (selection != NULL && selection_count == 1); show_detect_media = (selection != NULL && selection_count == 1); start_stop_type = G_DRIVE_START_STOP_TYPE_UNKNOWN; item_label = g_strdup_printf (ngettext ("New Folder with Selection (%'d Item)", "New Folder with Selection (%'d Items)", selection_count), selection_count); menu_item = g_menu_item_new (item_label, "view.new-folder-with-selection"); g_menu_item_set_attribute (menu_item, "hidden-when", "s", "action-disabled"); object = gtk_builder_get_object (builder, "new-folder-with-selection-section"); g_menu_append_item (G_MENU (object), menu_item); g_object_unref (menu_item); g_free (item_label); /* Open With menu item */ show_extract = show_app = show_run = item_opens_in_view = selection_count != 0; for (l = selection; l != NULL; l = l->next) { NautilusFile *file; file = NAUTILUS_FILE (l->data); if (!nautilus_mime_file_extracts (file)) { show_extract = FALSE; } if (!nautilus_mime_file_opens_in_external_app (file)) { show_app = FALSE; } if (!nautilus_mime_file_launches (file)) { show_run = FALSE; } if (!nautilus_file_opens_in_view (file)) { item_opens_in_view = FALSE; } if (!show_extract && !show_app && !show_run && !item_opens_in_view) { break; } } item_label = NULL; app = NULL; app_icon = NULL; if (show_app) { app = nautilus_mime_get_default_application_for_files (selection); } if (app != NULL) { char *escaped_app; escaped_app = eel_str_double_underscores (g_app_info_get_name (app)); item_label = g_strdup_printf (_("Open With %s"), escaped_app); app_icon = g_app_info_get_icon (app); if (app_icon != NULL) { g_object_ref (app_icon); } g_free (escaped_app); g_object_unref (app); } else if (show_run) { item_label = g_strdup (_("Run")); } else if (show_extract) { item_label = nautilus_files_view_supports_extract_here (view) ? g_strdup (_("Extract Here")) : g_strdup (_("Extract to…")); } else { item_label = g_strdup (_("Open")); } menu_item = g_menu_item_new (item_label, "view.open-with-default-application"); if (app_icon != NULL) { g_menu_item_set_icon (menu_item, app_icon); } object = gtk_builder_get_object (builder, "open-with-default-application-section"); g_menu_append_item (G_MENU (object), menu_item); g_free (item_label); g_object_unref (menu_item); /* Drives */ for (l = selection; l != NULL && (show_mount || show_unmount || show_eject || show_start || show_stop || show_detect_media); l = l->next) { NautilusFile *file; gboolean show_mount_one; gboolean show_unmount_one; gboolean show_eject_one; gboolean show_start_one; gboolean show_stop_one; gboolean show_detect_media_one; file = NAUTILUS_FILE (l->data); file_should_show_foreach (file, &show_mount_one, &show_unmount_one, &show_eject_one, &show_start_one, &show_stop_one, &show_detect_media_one, &start_stop_type); show_mount &= show_mount_one; show_unmount &= show_unmount_one; show_eject &= show_eject_one; show_start &= show_start_one; show_stop &= show_stop_one; show_detect_media &= show_detect_media_one; } if (show_start) { switch (start_stop_type) { default: case G_DRIVE_START_STOP_TYPE_UNKNOWN: case G_DRIVE_START_STOP_TYPE_SHUTDOWN: { item_label = _("_Start"); } break; case G_DRIVE_START_STOP_TYPE_NETWORK: { item_label = _("_Connect"); } break; case G_DRIVE_START_STOP_TYPE_MULTIDISK: { item_label = _("_Start Multi-disk Drive"); } break; case G_DRIVE_START_STOP_TYPE_PASSWORD: { item_label = _("U_nlock Drive"); } break; } menu_item = g_menu_item_new (item_label, "view.start-volume"); object = gtk_builder_get_object (builder, "drive-section"); g_menu_append_item (G_MENU (object), menu_item); g_object_unref (menu_item); } if (show_stop) { switch (start_stop_type) { default: case G_DRIVE_START_STOP_TYPE_UNKNOWN: { item_label = _("Stop Drive"); } break; case G_DRIVE_START_STOP_TYPE_SHUTDOWN: { item_label = _("_Safely Remove Drive"); } break; case G_DRIVE_START_STOP_TYPE_NETWORK: { item_label = _("_Disconnect"); } break; case G_DRIVE_START_STOP_TYPE_MULTIDISK: { item_label = _("_Stop Multi-disk Drive"); } break; case G_DRIVE_START_STOP_TYPE_PASSWORD: { item_label = _("_Lock Drive"); } break; } menu_item = g_menu_item_new (item_label, "view.stop-volume"); object = gtk_builder_get_object (builder, "drive-section"); g_menu_append_item (G_MENU (object), menu_item); g_object_unref (menu_item); } update_scripts_menu (view, builder); } static void update_background_menu (NautilusFilesView *view, GtkBuilder *builder) { if (nautilus_files_view_supports_creating_files (view) && !showing_recent_directory (view) && !showing_starred_directory (view)) { update_templates_menu (view, builder); } } static void real_update_context_menus (NautilusFilesView *view) { NautilusFilesViewPrivate *priv; g_autoptr (GtkBuilder) builder = NULL; GObject *object; priv = nautilus_files_view_get_instance_private (view); builder = gtk_builder_new_from_resource ("/org/gnome/nautilus/ui/nautilus-files-view-context-menus.ui"); g_clear_object (&priv->background_menu_model); g_clear_object (&priv->selection_menu_model); object = gtk_builder_get_object (builder, "background-menu"); priv->background_menu_model = g_object_ref (G_MENU (object)); object = gtk_builder_get_object (builder, "selection-menu"); priv->selection_menu_model = g_object_ref (G_MENU (object)); update_selection_menu (view, builder); update_background_menu (view, builder); update_extensions_menus (view, builder); nautilus_files_view_update_actions_state (view); } /* Convenience function to reset the context menus owned by the view and update * them with the current state. * Children can subclass it and add items on the menu after chaining up to the * parent, so menus are already reseted. * It will also update the actions state, which will also update children * actions state if the children subclass nautilus_files_view_update_actions_state */ void nautilus_files_view_update_context_menus (NautilusFilesView *view) { g_assert (NAUTILUS_IS_FILES_VIEW (view)); NAUTILUS_FILES_VIEW_CLASS (G_OBJECT_GET_CLASS (view))->update_context_menus (view); } static void nautilus_files_view_reset_view_menu (NautilusFilesView *view) { NautilusFilesViewPrivate *priv; GActionGroup *view_action_group; gboolean sort_available; g_autofree gchar *zoom_level_percent = NULL; NautilusFile *file; view_action_group = nautilus_files_view_get_action_group (view); priv = nautilus_files_view_get_instance_private (view); file = nautilus_files_view_get_directory_as_file (NAUTILUS_FILES_VIEW (view)); gtk_widget_set_visible (priv->visible_columns, g_action_group_has_action (view_action_group, "visible-columns")); sort_available = g_action_group_get_action_enabled (view_action_group, "sort"); gtk_widget_set_visible (priv->sort_menu, sort_available); gtk_widget_set_visible (priv->sort_trash_time, nautilus_file_is_in_trash (file)); /* We want to make insensitive available actions but that are not current * available due to the directory */ gtk_widget_set_sensitive (priv->sort_menu, !nautilus_files_view_is_empty (view)); gtk_widget_set_sensitive (priv->zoom_controls_box, !nautilus_files_view_is_empty (view)); zoom_level_percent = g_strdup_printf ("%.0f%%", nautilus_files_view_get_zoom_level_percentage (view) * 100.0); gtk_label_set_label (GTK_LABEL (priv->zoom_level_label), zoom_level_percent); } /* Convenience function to reset the menus owned by the view but managed on * the toolbar, and update them with the current state. * It will also update the actions state, which will also update children * actions state if the children subclass nautilus_files_view_update_actions_state */ void nautilus_files_view_update_toolbar_menus (NautilusFilesView *view) { NautilusWindow *window; NautilusFilesViewPrivate *priv; g_assert (NAUTILUS_IS_FILES_VIEW (view)); priv = nautilus_files_view_get_instance_private (view); /* Don't update after destroy (#349551), * or if we are not active. */ if (priv->slot == NULL || !priv->active) { return; } window = nautilus_files_view_get_window (view); nautilus_window_reset_menus (window); nautilus_files_view_update_actions_state (view); nautilus_files_view_reset_view_menu (view); } static GdkRectangle * nautilus_files_view_reveal_for_selection_context_menu (NautilusFilesView *view) { return NAUTILUS_FILES_VIEW_CLASS (G_OBJECT_GET_CLASS (view))->reveal_for_selection_context_menu (view); } /** * nautilus_files_view_pop_up_selection_context_menu * * Pop up a context menu appropriate to the selected items. * @view: NautilusFilesView of interest. * @event: The event that triggered this context menu. * **/ void nautilus_files_view_pop_up_selection_context_menu (NautilusFilesView *view, const GdkEvent *event) { NautilusFilesViewPrivate *priv; g_assert (NAUTILUS_IS_FILES_VIEW (view)); priv = nautilus_files_view_get_instance_private (view); /* Make the context menu items not flash as they update to proper disabled, * etc. states by forcing menus to update now. */ update_context_menus_if_pending (view); if (NULL == priv->selection_menu) { priv->selection_menu = gtk_menu_new (); gtk_menu_attach_to_widget (GTK_MENU (priv->selection_menu), GTK_WIDGET (view), NULL); } gtk_menu_shell_bind_model (GTK_MENU_SHELL (priv->selection_menu), G_MENU_MODEL (priv->selection_menu_model), NULL, TRUE); if (event != NULL) { gtk_menu_popup_at_pointer (GTK_MENU (priv->selection_menu), event); } else { /* If triggered from the keyboard, popup at selection, not pointer */ g_autofree GdkRectangle *rectangle = NULL; rectangle = nautilus_files_view_reveal_for_selection_context_menu (view); g_return_if_fail (rectangle != NULL); gtk_menu_popup_at_rect (GTK_MENU (priv->selection_menu), gtk_widget_get_window (GTK_WIDGET (view)), rectangle, GDK_GRAVITY_SOUTH_WEST, GDK_GRAVITY_NORTH_WEST, NULL); } } /** * nautilus_files_view_pop_up_background_context_menu * * Pop up a context menu appropriate to the location in view. * @view: NautilusFilesView of interest. * **/ void nautilus_files_view_pop_up_background_context_menu (NautilusFilesView *view, const GdkEvent *event) { NautilusFilesViewPrivate *priv; g_assert (NAUTILUS_IS_FILES_VIEW (view)); priv = nautilus_files_view_get_instance_private (view); /* Make the context menu items not flash as they update to proper disabled, * etc. states by forcing menus to update now. */ update_context_menus_if_pending (view); if (NULL == priv->background_menu) { priv->background_menu = gtk_menu_new (); gtk_menu_attach_to_widget (GTK_MENU (priv->background_menu), GTK_WIDGET (view), NULL); } gtk_menu_shell_bind_model (GTK_MENU_SHELL (priv->background_menu), G_MENU_MODEL (priv->background_menu_model), NULL, TRUE); if (event != NULL) { gtk_menu_popup_at_pointer (GTK_MENU (priv->background_menu), event); } else { /* It was triggered from the keyboard, so pop up from the center of view. */ gtk_menu_popup_at_widget (GTK_MENU (priv->background_menu), GTK_WIDGET (view), GDK_GRAVITY_CENTER, GDK_GRAVITY_CENTER, NULL); } } static gboolean popup_menu_callback (NautilusFilesView *view) { g_autolist (NautilusFile) selection = NULL; selection = nautilus_view_get_selection (NAUTILUS_VIEW (view)); if (selection != NULL) { nautilus_files_view_pop_up_selection_context_menu (view, NULL); } else { nautilus_files_view_pop_up_background_context_menu (view, NULL); } return TRUE; } static void schedule_update_context_menus (NautilusFilesView *view) { NautilusFilesViewPrivate *priv; g_assert (NAUTILUS_IS_FILES_VIEW (view)); priv = nautilus_files_view_get_instance_private (view); /* Don't schedule updates after destroy (#349551), * or if we are not active. */ if (priv->slot == NULL || !priv->active) { return; } /* Schedule a menu update with the current update interval */ if (priv->update_context_menus_timeout_id == 0) { priv->update_context_menus_timeout_id = g_timeout_add (priv->update_interval, update_context_menus_timeout_callback, view); } } static void remove_update_status_idle_callback (NautilusFilesView *view) { NautilusFilesViewPrivate *priv; priv = nautilus_files_view_get_instance_private (view); if (priv->update_status_idle_id != 0) { g_source_remove (priv->update_status_idle_id); priv->update_status_idle_id = 0; } } static gboolean update_status_idle_callback (gpointer data) { NautilusFilesViewPrivate *priv; NautilusFilesView *view; view = NAUTILUS_FILES_VIEW (data); priv = nautilus_files_view_get_instance_private (view); nautilus_files_view_display_selection_info (view); priv->update_status_idle_id = 0; return FALSE; } static void schedule_update_status (NautilusFilesView *view) { NautilusFilesViewPrivate *priv; g_assert (NAUTILUS_IS_FILES_VIEW (view)); priv = nautilus_files_view_get_instance_private (view); /* Make sure we haven't already destroyed it */ if (priv->slot == NULL) { return; } if (priv->loading) { /* Don't update status bar while loading the dir */ return; } if (priv->update_status_idle_id == 0) { priv->update_status_idle_id = g_idle_add_full (G_PRIORITY_DEFAULT_IDLE - 20, update_status_idle_callback, view, NULL); } } /** * nautilus_files_view_notify_selection_changed: * * Notify this view that the selection has changed. This is normally * called only by subclasses. * @view: NautilusFilesView whose selection has changed. * **/ void nautilus_files_view_notify_selection_changed (NautilusFilesView *view) { NautilusFilesViewPrivate *priv; GtkWindow *window; g_autolist (NautilusFile) selection = NULL; g_return_if_fail (NAUTILUS_IS_FILES_VIEW (view)); priv = nautilus_files_view_get_instance_private (view); selection = nautilus_view_get_selection (NAUTILUS_VIEW (view)); window = nautilus_files_view_get_containing_window (view); DEBUG_FILES (selection, "Selection changed in window %p", window); priv->selection_was_removed = FALSE; /* Schedule a display of the new selection. */ if (priv->display_selection_idle_id == 0) { priv->display_selection_idle_id = g_idle_add (display_selection_info_idle_callback, view); } if (priv->batching_selection_level != 0) { priv->selection_changed_while_batched = TRUE; } else { /* Here is the work we do only when we're not * batching selection changes. In other words, it's the slower * stuff that we don't want to slow down selection techniques * such as rubberband-selecting in icon view. */ /* Schedule an update of menu item states to match selection */ schedule_update_context_menus (view); } } static void file_changed_callback (NautilusFile *file, gpointer callback_data) { NautilusFilesView *view = NAUTILUS_FILES_VIEW (callback_data); schedule_changes (view); schedule_update_context_menus (view); schedule_update_status (view); } /** * load_directory: * * Switch the displayed location to a new uri. If the uri is not valid, * the location will not be switched; user feedback will be provided instead. * @view: NautilusFilesView whose location will be changed. * @uri: A string representing the uri to switch to. * **/ static void load_directory (NautilusFilesView *view, NautilusDirectory *directory) { NautilusFileAttributes attributes; NautilusFilesViewPrivate *priv; g_assert (NAUTILUS_IS_FILES_VIEW (view)); g_assert (NAUTILUS_IS_DIRECTORY (directory)); priv = nautilus_files_view_get_instance_private (view); nautilus_profile_start (NULL); nautilus_files_view_stop_loading (view); g_signal_emit (view, signals[CLEAR], 0); priv->loading = TRUE; setup_loading_floating_bar (view); /* Update menus when directory is empty, before going to new * location, so they won't have any false lingering knowledge * of old selection. */ schedule_update_context_menus (view); while (priv->subdirectory_list != NULL) { nautilus_files_view_remove_subdirectory (view, priv->subdirectory_list->data); } /* Avoid freeing it and won't be able to ref it */ if (priv->model != directory) { nautilus_directory_unref (priv->model); priv->model = nautilus_directory_ref (directory); } nautilus_file_unref (priv->directory_as_file); priv->directory_as_file = nautilus_directory_get_corresponding_file (directory); g_clear_object (&priv->location); priv->location = nautilus_directory_get_location (directory); g_object_notify (G_OBJECT (view), "location"); g_object_notify (G_OBJECT (view), "loading"); g_object_notify (G_OBJECT (view), "searching"); /* FIXME bugzilla.gnome.org 45062: In theory, we also need to monitor metadata here (as * well as doing a call when ready), in case external forces * change the directory's file metadata. */ attributes = NAUTILUS_FILE_ATTRIBUTE_INFO | NAUTILUS_FILE_ATTRIBUTE_MOUNT | NAUTILUS_FILE_ATTRIBUTE_FILESYSTEM_INFO; priv->metadata_for_directory_as_file_pending = TRUE; priv->metadata_for_files_in_directory_pending = TRUE; nautilus_file_call_when_ready (priv->directory_as_file, attributes, metadata_for_directory_as_file_ready_callback, view); nautilus_directory_call_when_ready (priv->model, attributes, FALSE, metadata_for_files_in_directory_ready_callback, view); /* If capabilities change, then we need to update the menus * because of New Folder, and relative emblems. */ attributes = NAUTILUS_FILE_ATTRIBUTE_INFO | NAUTILUS_FILE_ATTRIBUTE_FILESYSTEM_INFO; nautilus_file_monitor_add (priv->directory_as_file, &priv->directory_as_file, attributes); priv->file_changed_handler_id = g_signal_connect (priv->directory_as_file, "changed", G_CALLBACK (file_changed_callback), view); nautilus_profile_end (NULL); } static void finish_loading (NautilusFilesView *view) { NautilusFileAttributes attributes; NautilusFilesViewPrivate *priv; priv = nautilus_files_view_get_instance_private (view); nautilus_profile_start (NULL); /* Tell interested parties that we've begun loading this directory now. * Subclasses use this to know that the new metadata is now available. */ nautilus_profile_start ("BEGIN_LOADING"); g_signal_emit (view, signals[BEGIN_LOADING], 0); nautilus_profile_end ("BEGIN_LOADING"); nautilus_files_view_check_empty_states (view); if (nautilus_directory_are_all_files_seen (priv->model)) { /* Unschedule a pending update and schedule a new one with the minimal * update interval. This gives the view a short chance at gathering the * (cached) deep counts. */ unschedule_display_of_pending_files (view); schedule_timeout_display_of_pending_files (view, UPDATE_INTERVAL_MIN); } /* Start loading. */ /* Connect handlers to learn about loading progress. */ priv->done_loading_handler_id = g_signal_connect (priv->model, "done-loading", G_CALLBACK (done_loading_callback), view); priv->load_error_handler_id = g_signal_connect (priv->model, "load-error", G_CALLBACK (load_error_callback), view); /* Monitor the things needed to get the right icon. Also * monitor a directory's item count because the "size" * attribute is based on that, and the file's metadata * and possible custom name. */ attributes = NAUTILUS_FILE_ATTRIBUTES_FOR_ICON | NAUTILUS_FILE_ATTRIBUTE_DIRECTORY_ITEM_COUNT | NAUTILUS_FILE_ATTRIBUTE_INFO | NAUTILUS_FILE_ATTRIBUTE_MOUNT | NAUTILUS_FILE_ATTRIBUTE_EXTENSION_INFO; priv->files_added_handler_id = g_signal_connect (priv->model, "files-added", G_CALLBACK (files_added_callback), view); priv->files_changed_handler_id = g_signal_connect (priv->model, "files-changed", G_CALLBACK (files_changed_callback), view); nautilus_directory_file_monitor_add (priv->model, &priv->model, priv->show_hidden_files, attributes, files_added_callback, view); nautilus_profile_end (NULL); } static void finish_loading_if_all_metadata_loaded (NautilusFilesView *view) { NautilusFilesViewPrivate *priv; priv = nautilus_files_view_get_instance_private (view); if (!priv->metadata_for_directory_as_file_pending && !priv->metadata_for_files_in_directory_pending) { finish_loading (view); } } static void metadata_for_directory_as_file_ready_callback (NautilusFile *file, gpointer callback_data) { NautilusFilesView *view; NautilusFilesViewPrivate *priv; view = callback_data; g_assert (NAUTILUS_IS_FILES_VIEW (view)); priv = nautilus_files_view_get_instance_private (view); g_assert (priv->directory_as_file == file); g_assert (priv->metadata_for_directory_as_file_pending); nautilus_profile_start (NULL); priv->metadata_for_directory_as_file_pending = FALSE; finish_loading_if_all_metadata_loaded (view); nautilus_profile_end (NULL); } static void metadata_for_files_in_directory_ready_callback (NautilusDirectory *directory, GList *files, gpointer callback_data) { NautilusFilesView *view; NautilusFilesViewPrivate *priv; view = callback_data; g_assert (NAUTILUS_IS_FILES_VIEW (view)); priv = nautilus_files_view_get_instance_private (view); g_assert (priv->model == directory); g_assert (priv->metadata_for_files_in_directory_pending); nautilus_profile_start (NULL); priv->metadata_for_files_in_directory_pending = FALSE; finish_loading_if_all_metadata_loaded (view); nautilus_profile_end (NULL); } static void disconnect_handler (GObject *object, guint *id) { if (*id != 0) { g_signal_handler_disconnect (object, *id); *id = 0; } } static void disconnect_directory_handler (NautilusFilesView *view, guint *id) { NautilusFilesViewPrivate *priv; priv = nautilus_files_view_get_instance_private (view); disconnect_handler (G_OBJECT (priv->model), id); } static void disconnect_directory_as_file_handler (NautilusFilesView *view, guint *id) { NautilusFilesViewPrivate *priv; priv = nautilus_files_view_get_instance_private (view); disconnect_handler (G_OBJECT (priv->directory_as_file), id); } static void disconnect_model_handlers (NautilusFilesView *view) { NautilusFilesViewPrivate *priv; priv = nautilus_files_view_get_instance_private (view); if (priv->model == NULL) { return; } disconnect_directory_handler (view, &priv->files_added_handler_id); disconnect_directory_handler (view, &priv->files_changed_handler_id); disconnect_directory_handler (view, &priv->done_loading_handler_id); disconnect_directory_handler (view, &priv->load_error_handler_id); disconnect_directory_as_file_handler (view, &priv->file_changed_handler_id); nautilus_file_cancel_call_when_ready (priv->directory_as_file, metadata_for_directory_as_file_ready_callback, view); nautilus_directory_cancel_callback (priv->model, metadata_for_files_in_directory_ready_callback, view); nautilus_directory_file_monitor_remove (priv->model, &priv->model); nautilus_file_monitor_remove (priv->directory_as_file, &priv->directory_as_file); } static void nautilus_files_view_select_file (NautilusFilesView *view, NautilusFile *file) { GList file_list; file_list.data = file; file_list.next = NULL; file_list.prev = NULL; nautilus_files_view_call_set_selection (view, &file_list); } /** * nautilus_files_view_stop_loading: * * Stop the current ongoing process, such as switching to a new uri. * @view: NautilusFilesView in question. * **/ void nautilus_files_view_stop_loading (NautilusFilesView *view) { NautilusFilesViewPrivate *priv; g_return_if_fail (NAUTILUS_IS_FILES_VIEW (view)); priv = nautilus_files_view_get_instance_private (view); unschedule_display_of_pending_files (view); reset_update_interval (view); /* Free extra undisplayed files */ g_list_free_full (priv->new_added_files, file_and_directory_free); priv->new_added_files = NULL; g_list_free_full (priv->new_changed_files, file_and_directory_free); priv->new_changed_files = NULL; g_hash_table_remove_all (priv->non_ready_files); g_list_free_full (priv->old_added_files, file_and_directory_free); priv->old_added_files = NULL; g_list_free_full (priv->old_changed_files, file_and_directory_free); priv->old_changed_files = NULL; g_list_free_full (priv->pending_selection, g_object_unref); priv->pending_selection = NULL; done_loading (view, FALSE); disconnect_model_handlers (view); } gboolean nautilus_files_view_is_editable (NautilusFilesView *view) { NautilusDirectory *directory; directory = nautilus_files_view_get_model (view); if (directory != NULL) { return nautilus_directory_is_editable (directory); } return TRUE; } static gboolean nautilus_files_view_is_read_only (NautilusFilesView *view) { NautilusFile *file; if (!nautilus_files_view_is_editable (view)) { return TRUE; } file = nautilus_files_view_get_directory_as_file (view); if (file != NULL) { return !nautilus_file_can_write (file); } return FALSE; } /** * nautilus_files_view_should_show_file * * Returns whether or not this file should be displayed based on * current filtering options. */ gboolean nautilus_files_view_should_show_file (NautilusFilesView *view, NautilusFile *file) { NautilusFilesViewPrivate *priv; priv = nautilus_files_view_get_instance_private (view); return nautilus_file_should_show (file, priv->show_hidden_files); } void nautilus_files_view_ignore_hidden_file_preferences (NautilusFilesView *view) { NautilusFilesViewPrivate *priv; priv = nautilus_files_view_get_instance_private (view); g_return_if_fail (priv->model == NULL); if (priv->ignore_hidden_file_preferences) { return; } priv->show_hidden_files = FALSE; priv->ignore_hidden_file_preferences = TRUE; } char * nautilus_files_view_get_uri (NautilusFilesView *view) { NautilusFilesViewPrivate *priv; g_return_val_if_fail (NAUTILUS_IS_FILES_VIEW (view), NULL); priv = nautilus_files_view_get_instance_private (view); if (priv->model == NULL) { return NULL; } return nautilus_directory_get_uri (priv->model); } void nautilus_files_view_move_copy_items (NautilusFilesView *view, const GList *item_uris, const char *target_uri, int copy_action) { NautilusFile *target_file; target_file = nautilus_file_get_existing_by_uri (target_uri); if (copy_action == GDK_ACTION_COPY && nautilus_is_file_roller_installed () && target_file != NULL && nautilus_file_is_archive (target_file)) { char *command, *quoted_uri, *tmp; const GList *l; GdkScreen *screen; /* Handle dropping onto a file-roller archiver file, instead of starting a move/copy */ nautilus_file_unref (target_file); quoted_uri = g_shell_quote (target_uri); command = g_strconcat ("file-roller -a ", quoted_uri, NULL); g_free (quoted_uri); for (l = item_uris; l != NULL; l = l->next) { quoted_uri = g_shell_quote ((char *) l->data); tmp = g_strconcat (command, " ", quoted_uri, NULL); g_free (command); command = tmp; g_free (quoted_uri); } screen = gtk_widget_get_screen (GTK_WIDGET (view)); if (screen == NULL) { screen = gdk_screen_get_default (); } nautilus_launch_application_from_command (screen, command, FALSE, NULL); g_free (command); return; } nautilus_file_unref (target_file); nautilus_file_operations_copy_move (item_uris, target_uri, copy_action, GTK_WIDGET (view), copy_move_done_callback, pre_copy_move (view)); } static void nautilus_files_view_trash_state_changed_callback (NautilusTrashMonitor *trash_monitor, gboolean state, gpointer callback_data) { NautilusFilesView *view; view = (NautilusFilesView *) callback_data; g_assert (NAUTILUS_IS_FILES_VIEW (view)); schedule_update_context_menus (view); } void nautilus_files_view_start_batching_selection_changes (NautilusFilesView *view) { NautilusFilesViewPrivate *priv; g_return_if_fail (NAUTILUS_IS_FILES_VIEW (view)); priv = nautilus_files_view_get_instance_private (view); ++priv->batching_selection_level; priv->selection_changed_while_batched = FALSE; } void nautilus_files_view_stop_batching_selection_changes (NautilusFilesView *view) { NautilusFilesViewPrivate *priv; g_return_if_fail (NAUTILUS_IS_FILES_VIEW (view)); priv = nautilus_files_view_get_instance_private (view); g_return_if_fail (priv->batching_selection_level > 0); if (--priv->batching_selection_level == 0) { if (priv->selection_changed_while_batched) { nautilus_files_view_notify_selection_changed (view); } } } static void nautilus_files_view_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) { NautilusFilesView *view = NAUTILUS_FILES_VIEW (object); NautilusFilesViewPrivate *priv = nautilus_files_view_get_instance_private (view); switch (prop_id) { case PROP_LOADING: { g_value_set_boolean (value, nautilus_view_is_loading (NAUTILUS_VIEW (view))); } break; case PROP_SEARCHING: { g_value_set_boolean (value, nautilus_view_is_searching (NAUTILUS_VIEW (view))); } break; case PROP_LOCATION: { g_value_set_object (value, nautilus_view_get_location (NAUTILUS_VIEW (view))); } break; case PROP_SELECTION: { g_value_set_pointer (value, nautilus_view_get_selection (NAUTILUS_VIEW (view))); } break; case PROP_SEARCH_QUERY: { g_value_set_object (value, priv->search_query); } break; case PROP_EXTENSIONS_BACKGROUND_MENU: { g_value_set_object (value, real_get_extensions_background_menu (NAUTILUS_VIEW (view))); } break; case PROP_TEMPLATES_MENU: { g_value_set_object (value, real_get_templates_menu (NAUTILUS_VIEW (view))); } break; default: { g_assert_not_reached (); } break; } } static void nautilus_files_view_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) { NautilusFilesView *directory_view; NautilusFilesViewPrivate *priv; NautilusWindowSlot *slot; directory_view = NAUTILUS_FILES_VIEW (object); priv = nautilus_files_view_get_instance_private (directory_view); switch (prop_id) { case PROP_WINDOW_SLOT: { g_assert (priv->slot == NULL); slot = NAUTILUS_WINDOW_SLOT (g_value_get_object (value)); priv->slot = slot; g_signal_connect_object (priv->slot, "notify::active", G_CALLBACK (slot_active_changed), directory_view, 0); } break; case PROP_SUPPORTS_ZOOMING: { priv->supports_zooming = g_value_get_boolean (value); } break; case PROP_LOCATION: { nautilus_view_set_location (NAUTILUS_VIEW (directory_view), g_value_get_object (value)); } break; case PROP_SEARCH_QUERY: { nautilus_view_set_search_query (NAUTILUS_VIEW (directory_view), g_value_get_object (value)); } break; case PROP_SELECTION: { nautilus_view_set_selection (NAUTILUS_VIEW (directory_view), g_value_get_pointer (value)); } break; case PROP_EXTENSIONS_BACKGROUND_MENU: { real_set_extensions_background_menu (NAUTILUS_VIEW (directory_view), g_value_get_object (value)); } break; case PROP_TEMPLATES_MENU: { real_set_templates_menu (NAUTILUS_VIEW (directory_view), g_value_get_object (value)); } break; default: { G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); } break; } } /* handle Ctrl+Scroll, which will cause a zoom-in/out */ static gboolean on_event (GtkWidget *widget, GdkEvent *event, gpointer user_data) { NautilusFilesView *directory_view; static gdouble total_delta_y = 0; GdkModifierType state; GdkScrollDirection direction; gdouble delta_x, delta_y; directory_view = NAUTILUS_FILES_VIEW (widget); if (gdk_event_get_event_type (event) != GDK_SCROLL) { return GDK_EVENT_PROPAGATE; } if (!gdk_event_get_state (event, &state)) { return GDK_EVENT_PROPAGATE; } if (!(state & GDK_CONTROL_MASK)) { return GDK_EVENT_PROPAGATE; } if (gdk_event_get_scroll_direction (event, &direction)) { if (direction == GDK_SCROLL_UP) { /* Zoom In */ nautilus_files_view_bump_zoom_level (directory_view, 1); return GDK_EVENT_STOP; } else if (direction == GDK_SCROLL_DOWN) { /* Zoom Out */ nautilus_files_view_bump_zoom_level (directory_view, -1); return GDK_EVENT_STOP; } } if (gdk_event_get_scroll_deltas (event, &delta_x, &delta_y)) { /* try to emulate a normal scrolling event by summing deltas */ total_delta_y += delta_y; if (total_delta_y >= 1) { total_delta_y = 0; /* emulate scroll down */ nautilus_files_view_bump_zoom_level (directory_view, -1); return GDK_EVENT_STOP; } else if (total_delta_y <= -1) { total_delta_y = 0; /* emulate scroll up */ nautilus_files_view_bump_zoom_level (directory_view, 1); return GDK_EVENT_STOP; } else { /* eat event */ return GDK_EVENT_STOP; } } return GDK_EVENT_PROPAGATE; } static void action_reload_enabled_changed (GActionGroup *action_group, gchar *action_name, gboolean enabled, NautilusFilesView *view) { NautilusFilesViewPrivate *priv; priv = nautilus_files_view_get_instance_private (view); gtk_widget_set_visible (priv->reload, enabled); } static void action_stop_enabled_changed (GActionGroup *action_group, gchar *action_name, gboolean enabled, NautilusFilesView *view) { NautilusFilesViewPrivate *priv; priv = nautilus_files_view_get_instance_private (view); gtk_widget_set_visible (priv->stop, enabled); } static void nautilus_files_view_parent_set (GtkWidget *widget, GtkWidget *old_parent) { NautilusWindow *window; NautilusFilesView *view; NautilusFilesViewPrivate *priv; GtkWidget *parent; view = NAUTILUS_FILES_VIEW (widget); priv = nautilus_files_view_get_instance_private (view); parent = gtk_widget_get_parent (widget); window = nautilus_files_view_get_window (view); g_assert (parent == NULL || old_parent == NULL); if (GTK_WIDGET_CLASS (nautilus_files_view_parent_class)->parent_set != NULL) { GTK_WIDGET_CLASS (nautilus_files_view_parent_class)->parent_set (widget, old_parent); } if (priv->stop_signal_handler > 0) { g_signal_handler_disconnect (window, priv->stop_signal_handler); priv->stop_signal_handler = 0; } if (priv->reload_signal_handler > 0) { g_signal_handler_disconnect (window, priv->reload_signal_handler); priv->reload_signal_handler = 0; } if (parent != NULL) { g_assert (old_parent == NULL); if (priv->slot == nautilus_window_get_active_slot (window)) { priv->active = TRUE; gtk_widget_insert_action_group (GTK_WIDGET (nautilus_files_view_get_window (view)), "view", G_ACTION_GROUP (priv->view_action_group)); } priv->stop_signal_handler = g_signal_connect (window, "action-enabled-changed::stop", G_CALLBACK (action_stop_enabled_changed), view); priv->reload_signal_handler = g_signal_connect (window, "action-enabled-changed::reload", G_CALLBACK (action_reload_enabled_changed), view); } else { remove_update_context_menus_timeout_callback (view); /* Only remove the action group if it matchs the current view * action group. If not, we can remove an action group set by * a different view i.e. if the slot_active function is called * before this one */ if (gtk_widget_get_action_group (GTK_WIDGET (window), "view") == priv->view_action_group) { gtk_widget_insert_action_group (GTK_WIDGET (nautilus_files_view_get_window (view)), "view", NULL); } } } static gboolean nautilus_files_view_event (GtkWidget *widget, GdkEvent *event) { NautilusFilesView *view; NautilusFilesViewPrivate *priv; guint keyval; if (gdk_event_get_event_type (event) != GDK_KEY_PRESS) { return GDK_EVENT_PROPAGATE; } view = NAUTILUS_FILES_VIEW (widget); priv = nautilus_files_view_get_instance_private (view); if (G_UNLIKELY (!gdk_event_get_keyval (event, &keyval))) { g_return_val_if_reached (GDK_EVENT_PROPAGATE); } for (gint i = 0; i < G_N_ELEMENTS (extra_view_keybindings); i++) { if (extra_view_keybindings[i].keyval == keyval) { GAction *action; action = g_action_map_lookup_action (G_ACTION_MAP (priv->view_action_group), extra_view_keybindings[i].action); if (g_action_get_enabled (action)) { g_action_activate (action, NULL); return GDK_EVENT_STOP; } break; } } return GDK_EVENT_PROPAGATE; } static NautilusQuery * nautilus_files_view_get_search_query (NautilusView *view) { NautilusFilesViewPrivate *priv; priv = nautilus_files_view_get_instance_private (NAUTILUS_FILES_VIEW (view)); return priv->search_query; } static void set_search_query_internal (NautilusFilesView *files_view, NautilusQuery *query, NautilusDirectory *base_model) { GFile *location; NautilusFilesViewPrivate *priv; location = NULL; priv = nautilus_files_view_get_instance_private (files_view); g_set_object (&priv->search_query, query); g_object_notify (G_OBJECT (files_view), "search-query"); if (!nautilus_query_is_empty (query)) { if (nautilus_view_is_searching (NAUTILUS_VIEW (files_view))) { /* * Reuse the search directory and reload it. */ nautilus_search_directory_set_query (NAUTILUS_SEARCH_DIRECTORY (priv->model), query); /* It's important to use load_directory instead of set_location, * since the location is already correct, however we need * to reload the directory with the new query set. But * set_location has a check for wheter the location is a * search directory, so setting the location to a search * directory when is already serching will enter a loop. */ load_directory (files_view, priv->model); } else { NautilusDirectory *directory; gchar *uri; uri = nautilus_search_directory_generate_new_uri (); location = g_file_new_for_uri (uri); directory = nautilus_directory_get (location); g_assert (NAUTILUS_IS_SEARCH_DIRECTORY (directory)); nautilus_search_directory_set_base_model (NAUTILUS_SEARCH_DIRECTORY (directory), base_model); nautilus_search_directory_set_query (NAUTILUS_SEARCH_DIRECTORY (directory), query); load_directory (files_view, directory); g_object_notify (G_OBJECT (files_view), "searching"); nautilus_directory_unref (directory); g_free (uri); } } else { if (nautilus_view_is_searching (NAUTILUS_VIEW (files_view))) { location = nautilus_directory_get_location (base_model); nautilus_view_set_location (NAUTILUS_VIEW (files_view), location); } } g_clear_object (&location); } static void nautilus_files_view_set_search_query (NautilusView *view, NautilusQuery *query) { NautilusDirectory *base_model; NautilusFilesView *files_view; NautilusFilesViewPrivate *priv; files_view = NAUTILUS_FILES_VIEW (view); priv = nautilus_files_view_get_instance_private (files_view); if (nautilus_view_is_searching (view)) { base_model = nautilus_search_directory_get_base_model (NAUTILUS_SEARCH_DIRECTORY (priv->model)); } else { base_model = priv->model; } set_search_query_internal (NAUTILUS_FILES_VIEW (view), query, base_model); } static GFile * nautilus_files_view_get_location (NautilusView *view) { NautilusFilesViewPrivate *priv; NautilusFilesView *files_view; files_view = NAUTILUS_FILES_VIEW (view); priv = nautilus_files_view_get_instance_private (files_view); return priv->location; } static gboolean nautilus_files_view_is_loading (NautilusView *view) { NautilusFilesViewPrivate *priv; NautilusFilesView *files_view; files_view = NAUTILUS_FILES_VIEW (view); priv = nautilus_files_view_get_instance_private (files_view); return priv->loading; } static void nautilus_files_view_iface_init (NautilusViewInterface *iface) { iface->get_location = nautilus_files_view_get_location; iface->set_location = nautilus_files_view_set_location; iface->get_selection = nautilus_files_view_get_selection; iface->set_selection = nautilus_files_view_set_selection; iface->get_search_query = nautilus_files_view_get_search_query; iface->set_search_query = nautilus_files_view_set_search_query; iface->get_toolbar_menu_sections = nautilus_files_view_get_toolbar_menu_sections; iface->is_searching = nautilus_files_view_is_searching; iface->is_loading = nautilus_files_view_is_loading; iface->get_view_id = nautilus_files_view_get_view_id; iface->get_templates_menu = nautilus_files_view_get_templates_menu; iface->set_templates_menu = nautilus_files_view_set_templates_menu; iface->get_extensions_background_menu = nautilus_files_view_get_extensions_background_menu; iface->set_extensions_background_menu = nautilus_files_view_set_extensions_background_menu; } static void nautilus_files_view_class_init (NautilusFilesViewClass *klass) { GObjectClass *oclass; GtkWidgetClass *widget_class; widget_class = GTK_WIDGET_CLASS (klass); oclass = G_OBJECT_CLASS (klass); oclass->finalize = nautilus_files_view_finalize; oclass->get_property = nautilus_files_view_get_property; oclass->set_property = nautilus_files_view_set_property; widget_class->destroy = nautilus_files_view_destroy; widget_class->event = nautilus_files_view_event; widget_class->parent_set = nautilus_files_view_parent_set; widget_class->grab_focus = nautilus_files_view_grab_focus; signals[ADD_FILES] = g_signal_new ("add-files", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (NautilusFilesViewClass, add_files), NULL, NULL, g_cclosure_marshal_generic, G_TYPE_NONE, 1, G_TYPE_POINTER); signals[BEGIN_FILE_CHANGES] = g_signal_new ("begin-file-changes", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (NautilusFilesViewClass, begin_file_changes), NULL, NULL, g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0); signals[BEGIN_LOADING] = g_signal_new ("begin-loading", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (NautilusFilesViewClass, begin_loading), NULL, NULL, g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0); signals[CLEAR] = g_signal_new ("clear", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (NautilusFilesViewClass, clear), NULL, NULL, g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0); signals[END_FILE_CHANGES] = g_signal_new ("end-file-changes", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (NautilusFilesViewClass, end_file_changes), NULL, NULL, g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0); signals[END_LOADING] = g_signal_new ("end-loading", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (NautilusFilesViewClass, end_loading), NULL, NULL, g_cclosure_marshal_VOID__BOOLEAN, G_TYPE_NONE, 1, G_TYPE_BOOLEAN); signals[FILE_CHANGED] = g_signal_new ("file-changed", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (NautilusFilesViewClass, file_changed), NULL, NULL, g_cclosure_marshal_generic, G_TYPE_NONE, 2, NAUTILUS_TYPE_FILE, NAUTILUS_TYPE_DIRECTORY); signals[REMOVE_FILE] = g_signal_new ("remove-file", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (NautilusFilesViewClass, remove_file), NULL, NULL, g_cclosure_marshal_generic, G_TYPE_NONE, 2, NAUTILUS_TYPE_FILE, NAUTILUS_TYPE_DIRECTORY); signals[SELECTION_CHANGED] = g_signal_new ("selection-changed", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_LAST, 0, NULL, NULL, g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0); klass->get_backing_uri = real_get_backing_uri; klass->get_window = nautilus_files_view_get_window; klass->update_context_menus = real_update_context_menus; klass->update_actions_state = real_update_actions_state; klass->check_empty_states = real_check_empty_states; g_object_class_install_property ( oclass, PROP_WINDOW_SLOT, g_param_spec_object ("window-slot", "Window Slot", "The parent window slot reference", NAUTILUS_TYPE_WINDOW_SLOT, G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY)); g_object_class_install_property ( oclass, PROP_SUPPORTS_ZOOMING, g_param_spec_boolean ("supports-zooming", "Supports zooming", "Whether the view supports zooming", TRUE, G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS)); g_object_class_override_property (oclass, PROP_LOADING, "loading"); g_object_class_override_property (oclass, PROP_SEARCHING, "searching"); g_object_class_override_property (oclass, PROP_LOCATION, "location"); g_object_class_override_property (oclass, PROP_SELECTION, "selection"); g_object_class_override_property (oclass, PROP_SEARCH_QUERY, "search-query"); g_object_class_override_property (oclass, PROP_EXTENSIONS_BACKGROUND_MENU, "extensions-background-menu"); g_object_class_override_property (oclass, PROP_TEMPLATES_MENU, "templates-menu"); } static void nautilus_files_view_init (NautilusFilesView *view) { NautilusFilesViewPrivate *priv; GtkBuilder *builder; AtkObject *atk_object; NautilusDirectory *scripts_directory; NautilusDirectory *templates_directory; gchar *templates_uri; GtkClipboard *clipboard; GApplication *app; const gchar *open_accels[] = { "Return", "KP_Enter", "o", "Down", NULL }; const gchar *open_properties[] = { "i", "Return", NULL }; const gchar *zoom_in_accels[] = { "equal", "plus", "KP_Add", NULL }; const gchar *zoom_out_accels[] = { "minus", "KP_Subtract", NULL }; const gchar *move_to_trash_accels[] = { "Delete", "KP_Delete", NULL }; const gchar *delete_permanently_accels[] = { "Delete", "KP_Delete", NULL }; nautilus_profile_start (NULL); priv = nautilus_files_view_get_instance_private (view); /* Toolbar menu */ builder = gtk_builder_new_from_resource ("/org/gnome/nautilus/ui/nautilus-toolbar-view-menu.ui"); priv->toolbar_menu_sections = g_new0 (NautilusToolbarMenuSections, 1); priv->toolbar_menu_sections->supports_undo_redo = TRUE; priv->toolbar_menu_sections->zoom_section = GTK_WIDGET (g_object_ref_sink (gtk_builder_get_object (builder, "zoom_section"))); priv->toolbar_menu_sections->extended_section = GTK_WIDGET (g_object_ref_sink (gtk_builder_get_object (builder, "extended_section"))); priv->zoom_controls_box = GTK_WIDGET (gtk_builder_get_object (builder, "zoom_controls_box")); priv->zoom_level_label = GTK_WIDGET (gtk_builder_get_object (builder, "zoom_level_label")); priv->sort_menu = GTK_WIDGET (gtk_builder_get_object (builder, "sort_menu")); priv->sort_trash_time = GTK_WIDGET (gtk_builder_get_object (builder, "sort_trash_time")); priv->visible_columns = GTK_WIDGET (gtk_builder_get_object (builder, "visible_columns")); priv->reload = GTK_WIDGET (gtk_builder_get_object (builder, "reload")); priv->stop = GTK_WIDGET (gtk_builder_get_object (builder, "stop")); g_signal_connect (view, "end-file-changes", G_CALLBACK (on_end_file_changes), view); g_object_unref (builder); /* Main widgets */ gtk_orientable_set_orientation (GTK_ORIENTABLE (view), GTK_ORIENTATION_VERTICAL); priv->overlay = gtk_overlay_new (); gtk_widget_set_vexpand (priv->overlay, TRUE); gtk_widget_set_hexpand (priv->overlay, TRUE); gtk_container_add (GTK_CONTAINER (view), priv->overlay); gtk_widget_show (priv->overlay); /* NautilusFloatingBar listen to its parent's 'event' signal * and GtkOverlay doesn't have it enabled by default, so we have to add them * here. */ gtk_widget_add_events (GTK_WIDGET (priv->overlay), GDK_ENTER_NOTIFY_MASK | GDK_LEAVE_NOTIFY_MASK); /* Scrolled Window */ priv->scrolled_window = gtk_scrolled_window_new (NULL, NULL); gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (priv->scrolled_window), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC); gtk_widget_show (priv->scrolled_window); g_signal_connect_swapped (priv->scrolled_window, "event", G_CALLBACK (on_event), view); g_signal_connect_swapped (priv->scrolled_window, "popup-menu", G_CALLBACK (popup_menu_callback), view); gtk_container_add (GTK_CONTAINER (priv->overlay), priv->scrolled_window); /* Empty states */ builder = gtk_builder_new_from_resource ("/org/gnome/nautilus/ui/nautilus-no-search-results.ui"); priv->no_search_results_widget = GTK_WIDGET (gtk_builder_get_object (builder, "no_search_results")); gtk_overlay_add_overlay (GTK_OVERLAY (priv->overlay), priv->no_search_results_widget); gtk_overlay_set_overlay_pass_through (GTK_OVERLAY (priv->overlay), priv->no_search_results_widget, TRUE); g_object_unref (builder); builder = gtk_builder_new_from_resource ("/org/gnome/nautilus/ui/nautilus-folder-is-empty.ui"); priv->folder_is_empty_widget = GTK_WIDGET (gtk_builder_get_object (builder, "folder_is_empty")); gtk_overlay_add_overlay (GTK_OVERLAY (priv->overlay), priv->folder_is_empty_widget); gtk_overlay_set_overlay_pass_through (GTK_OVERLAY (priv->overlay), priv->folder_is_empty_widget, TRUE); g_object_unref (builder); builder = gtk_builder_new_from_resource ("/org/gnome/nautilus/ui/nautilus-starred-is-empty.ui"); priv->starred_is_empty_widget = GTK_WIDGET (gtk_builder_get_object (builder, "starred_is_empty")); gtk_overlay_add_overlay (GTK_OVERLAY (priv->overlay), priv->starred_is_empty_widget); gtk_overlay_set_overlay_pass_through (GTK_OVERLAY (priv->overlay), priv->starred_is_empty_widget, TRUE); g_object_unref (builder); builder = gtk_builder_new_from_resource ("/org/gnome/nautilus/ui/nautilus-trash-is-empty.ui"); priv->trash_is_empty_widget = GTK_WIDGET (gtk_builder_get_object (builder, "trash_is_empty")); gtk_overlay_add_overlay (GTK_OVERLAY (priv->overlay), priv->trash_is_empty_widget); gtk_overlay_set_overlay_pass_through (GTK_OVERLAY (priv->overlay), priv->trash_is_empty_widget, TRUE); g_object_unref (builder); /* Floating bar */ priv->floating_bar = nautilus_floating_bar_new (NULL, NULL, FALSE); gtk_widget_set_halign (priv->floating_bar, GTK_ALIGN_END); gtk_widget_set_valign (priv->floating_bar, GTK_ALIGN_END); gtk_overlay_add_overlay (GTK_OVERLAY (priv->overlay), priv->floating_bar); g_signal_connect (priv->floating_bar, "action", G_CALLBACK (floating_bar_action_cb), view); priv->non_ready_files = g_hash_table_new_full (file_and_directory_hash, file_and_directory_equal, file_and_directory_free, NULL); priv->pending_reveal = g_hash_table_new (NULL, NULL); gtk_style_context_set_junction_sides (gtk_widget_get_style_context (GTK_WIDGET (view)), GTK_JUNCTION_TOP | GTK_JUNCTION_LEFT); if (set_up_scripts_directory_global ()) { scripts_directory = nautilus_directory_get_by_uri (scripts_directory_uri); add_directory_to_scripts_directory_list (view, scripts_directory); nautilus_directory_unref (scripts_directory); } else { g_warning ("Ignoring scripts directory, it may be a broken link\n"); } if (nautilus_should_use_templates_directory ()) { templates_uri = nautilus_get_templates_directory_uri (); templates_directory = nautilus_directory_get_by_uri (templates_uri); g_free (templates_uri); add_directory_to_templates_directory_list (view, templates_directory); nautilus_directory_unref (templates_directory); } update_templates_directory (view); priv->sort_directories_first = g_settings_get_boolean (gtk_filechooser_preferences, NAUTILUS_PREFERENCES_SORT_DIRECTORIES_FIRST); priv->show_hidden_files = g_settings_get_boolean (gtk_filechooser_preferences, NAUTILUS_PREFERENCES_SHOW_HIDDEN_FILES); g_signal_connect_object (nautilus_trash_monitor_get (), "trash-state-changed", G_CALLBACK (nautilus_files_view_trash_state_changed_callback), view, 0); /* React to clipboard changes */ clipboard = gtk_clipboard_get (GDK_SELECTION_CLIPBOARD); g_signal_connect (clipboard, "owner-change", G_CALLBACK (on_clipboard_owner_changed), view); /* Register to menu provider extension signal managing menu updates */ g_signal_connect_object (nautilus_signaller_get_current (), "popup-menu-changed", G_CALLBACK (schedule_update_context_menus), view, G_CONNECT_SWAPPED); gtk_widget_show (GTK_WIDGET (view)); g_signal_connect_swapped (nautilus_preferences, "changed::" NAUTILUS_PREFERENCES_CLICK_POLICY, G_CALLBACK (click_policy_changed_callback), view); g_signal_connect_swapped (gtk_filechooser_preferences, "changed::" NAUTILUS_PREFERENCES_SORT_DIRECTORIES_FIRST, G_CALLBACK (sort_directories_first_changed_callback), view); g_signal_connect_swapped (gtk_filechooser_preferences, "changed::" NAUTILUS_PREFERENCES_SHOW_HIDDEN_FILES, G_CALLBACK (show_hidden_files_changed_callback), view); g_signal_connect_swapped (gnome_lockdown_preferences, "changed::" NAUTILUS_PREFERENCES_LOCKDOWN_COMMAND_LINE, G_CALLBACK (schedule_update_context_menus), view); priv->in_destruction = FALSE; /* Accessibility */ atk_object = gtk_widget_get_accessible (GTK_WIDGET (view)); atk_object_set_name (atk_object, _("Content View")); atk_object_set_description (atk_object, _("View of the current folder")); priv->view_action_group = G_ACTION_GROUP (g_simple_action_group_new ()); g_action_map_add_action_entries (G_ACTION_MAP (priv->view_action_group), view_entries, G_N_ELEMENTS (view_entries), view); gtk_widget_insert_action_group (GTK_WIDGET (view), "view", G_ACTION_GROUP (priv->view_action_group)); app = g_application_get_default (); /* Toolbar menu */ nautilus_application_set_accelerators (app, "view.zoom-in", zoom_in_accels); nautilus_application_set_accelerators (app, "view.zoom-out", zoom_out_accels); nautilus_application_set_accelerator (app, "view.show-hidden-files", "h"); /* Background menu */ nautilus_application_set_accelerator (app, "view.select-all", "a"); nautilus_application_set_accelerator (app, "view.paste_accel", "v"); nautilus_application_set_accelerator (app, "view.create-link", "m"); /* Selection menu */ nautilus_application_set_accelerators (app, "view.open-with-default-application", open_accels); nautilus_application_set_accelerator (app, "view.open-item-new-tab", "Return"); nautilus_application_set_accelerator (app, "view.open-item-new-window", "Return"); nautilus_application_set_accelerators (app, "view.move-to-trash", move_to_trash_accels); nautilus_application_set_accelerators (app, "view.delete-from-trash", move_to_trash_accels); nautilus_application_set_accelerators (app, "view.delete-permanently-shortcut", delete_permanently_accels); /* When trash is not available, allow the "Delete" keys to delete permanently, that is, when * the menu item is available, since we never make both the trash and delete-permanently-menu-item * actions active */ nautilus_application_set_accelerators (app, "view.delete-permanently-menu-item", move_to_trash_accels); nautilus_application_set_accelerators (app, "view.permanent-delete-permanently-menu-item", delete_permanently_accels); nautilus_application_set_accelerators (app, "view.properties", open_properties); nautilus_application_set_accelerator (app, "view.open-item-location", "o"); nautilus_application_set_accelerator (app, "view.rename", "F2"); nautilus_application_set_accelerator (app, "view.cut", "x"); nautilus_application_set_accelerator (app, "view.copy", "c"); nautilus_application_set_accelerator (app, "view.create-link-in-place", "m"); nautilus_application_set_accelerator (app, "view.new-folder", "n"); /* Only accesible by shorcuts */ nautilus_application_set_accelerator (app, "view.select-pattern", "s"); nautilus_application_set_accelerator (app, "view.zoom-standard", "0"); nautilus_application_set_accelerator (app, "view.invert-selection", "i"); priv->starred_cancellable = g_cancellable_new (); priv->tag_manager = nautilus_tag_manager_get (); priv->rename_file_controller = nautilus_rename_file_popover_controller_new (); nautilus_profile_end (NULL); } NautilusFilesView * nautilus_files_view_new (guint id, NautilusWindowSlot *slot) { NautilusFilesView *view = NULL; gboolean use_experimental_views; use_experimental_views = g_settings_get_boolean (nautilus_preferences, NAUTILUS_PREFERENCES_USE_EXPERIMENTAL_VIEWS); switch (id) { case NAUTILUS_VIEW_GRID_ID: { if (use_experimental_views) { view = NAUTILUS_FILES_VIEW (nautilus_view_icon_controller_new (slot)); } else { view = nautilus_canvas_view_new (slot); } } break; case NAUTILUS_VIEW_LIST_ID: { view = nautilus_list_view_new (slot); } break; } if (view == NULL) { g_critical ("Unknown view type ID: %d", id); } else if (g_object_is_floating (view)) { g_object_ref_sink (view); } return view; }