/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */ /* nautilus-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 #include "nautilus-view.h" #include "nautilus-application.h" #include "nautilus-desktop-canvas-view.h" #include "nautilus-error-reporting.h" #include "nautilus-floating-bar.h" #include "nautilus-list-view.h" #include "nautilus-mime-actions.h" #include "nautilus-previewer.h" #include "nautilus-properties-window.h" #include "nautilus-window.h" #include "nautilus-toolbar.h" #if ENABLE_EMPTY_VIEW #include "nautilus-empty-view.h" #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define GNOME_DESKTOP_USE_UNSTABLE_API #include #define DEBUG_FLAG NAUTILUS_DEBUG_DIRECTORY_VIEW #include /* 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 /* Time to show the duplicated folder label */ #define DIALOG_DUPLICATED_NAME_ERROR_LABEL_TIMEOUT 500 /* Delay to show the Loading... floating bar */ #define FLOATING_BAR_LOADING_DELAY 500 /* ms */ enum { ADD_FILE, 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_VIEW_WIDGET, NUM_PROPERTIES }; static guint signals[LAST_SIGNAL]; static GParamSpec *properties[NUM_PROPERTIES] = { NULL, }; static GdkAtom copied_files_atom; static char *scripts_directory_uri = NULL; static int scripts_directory_uri_length; struct NautilusViewDetails { NautilusWindowSlot *slot; NautilusDirectory *model; NautilusFile *directory_as_file; guint dir_merge_id; gint dialog_duplicated_name_label_timeout_id; 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; GList *new_added_files; GList *new_changed_files; GHashTable *non_ready_files; GList *old_added_files; GList *old_changed_files; GList *pending_selection; /* 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. NautilusView 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 sort_directories_first; gboolean show_foreign_files; 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; GdkPoint context_menu_position; GMenu *selection_menu; GMenu *background_menu; GActionGroup *view_action_group; GtkWidget *scrolled_window; /* Empty states */ GtkWidget *folder_is_empty_widget; GtkWidget *no_search_results_widget; /* Floating bar */ guint floating_bar_set_status_timeout_id; guint floating_bar_loading_timeout_id; GtkWidget *floating_bar; /* View menu */ GtkWidget *view_menu_widget; GtkWidget *view_icon; GtkWidget *sort_menu; GtkWidget *sort_trash_time; GtkWidget *sort_search_relevance; GtkWidget *visible_columns; GtkWidget *stop; GtkWidget *reload; GtkAdjustment *zoom_adjustment; GtkWidget *zoom_level_scale; gulong stop_signal_handler; gulong reload_signal_handler; }; 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, NautilusView *view); static void load_directory (NautilusView *view, NautilusDirectory *directory); static void clipboard_changed_callback (NautilusClipboardMonitor *monitor, NautilusView *view); static void open_one_in_new_window (gpointer data, gpointer callback_data); static void schedule_update_context_menus (NautilusView *view); static void remove_update_context_menus_timeout_callback (NautilusView *view); static void schedule_update_status (NautilusView *view); static void remove_update_status_idle_callback (NautilusView *view); static void reset_update_interval (NautilusView *view); static void schedule_idle_display_of_pending_files (NautilusView *view); static void unschedule_display_of_pending_files (NautilusView *view); static void disconnect_model_handlers (NautilusView *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_view_trash_state_changed_callback (NautilusTrashMonitor *trash, gboolean state, gpointer callback_data); static void nautilus_view_select_file (NautilusView *view, NautilusFile *file); static void update_templates_directory (NautilusView *view); static void check_empty_states (NautilusView *view); G_DEFINE_TYPE (NautilusView, nautilus_view, GTK_TYPE_OVERLAY); static void check_empty_states (NautilusView *view) { GList *files; GList *filtered; gboolean show_hidden_files; gtk_widget_hide (view->details->no_search_results_widget); gtk_widget_hide (view->details->folder_is_empty_widget); if (!view->details->loading && view->details->model) { files = nautilus_directory_get_file_list (view->details->model); show_hidden_files = g_settings_get_boolean (gtk_filechooser_preferences, NAUTILUS_PREFERENCES_SHOW_HIDDEN_FILES); filtered = nautilus_file_list_filter_hidden (files, show_hidden_files); if (g_list_length (filtered) == 0) { if (nautilus_view_is_search (view)) { gtk_widget_show (view->details->no_search_results_widget); } else { gtk_widget_show (view->details->folder_is_empty_widget); } } nautilus_file_list_unref (filtered); nautilus_file_list_unref (files); } } /* * Floating Bar code */ static void remove_loading_floating_bar (NautilusView *view) { if (view->details->floating_bar_loading_timeout_id != 0) { g_source_remove (view->details->floating_bar_loading_timeout_id); view->details->floating_bar_loading_timeout_id = 0; } gtk_widget_hide (view->details->floating_bar); nautilus_floating_bar_cleanup_actions (NAUTILUS_FLOATING_BAR (view->details->floating_bar)); } static void real_setup_loading_floating_bar (NautilusView *view) { gboolean disable_chrome; g_object_get (nautilus_view_get_window (view), "disable-chrome", &disable_chrome, NULL); if (disable_chrome) { gtk_widget_hide (view->details->floating_bar); return; } nautilus_floating_bar_cleanup_actions (NAUTILUS_FLOATING_BAR (view->details->floating_bar)); nautilus_floating_bar_set_primary_label (NAUTILUS_FLOATING_BAR (view->details->floating_bar), nautilus_view_is_search (view) ? _("Searching…") : _("Loading…")); nautilus_floating_bar_set_details_label (NAUTILUS_FLOATING_BAR (view->details->floating_bar), NULL); nautilus_floating_bar_set_show_spinner (NAUTILUS_FLOATING_BAR (view->details->floating_bar), view->details->loading); nautilus_floating_bar_add_action (NAUTILUS_FLOATING_BAR (view->details->floating_bar), "process-stop-symbolic", NAUTILUS_FLOATING_BAR_ACTION_ID_STOP); gtk_widget_set_halign (view->details->floating_bar, GTK_ALIGN_END); gtk_widget_show (view->details->floating_bar); } static gboolean setup_loading_floating_bar_timeout_cb (gpointer user_data) { NautilusView *view = user_data; view->details->floating_bar_loading_timeout_id = 0; real_setup_loading_floating_bar (view); return FALSE; } static void setup_loading_floating_bar (NautilusView *view) { /* setup loading overlay */ if (view->details->floating_bar_set_status_timeout_id != 0) { g_source_remove (view->details->floating_bar_set_status_timeout_id); view->details->floating_bar_set_status_timeout_id = 0; } if (view->details->floating_bar_loading_timeout_id != 0) { g_source_remove (view->details->floating_bar_loading_timeout_id); view->details->floating_bar_loading_timeout_id = 0; } view->details->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, NautilusView *view) { if (action == NAUTILUS_FLOATING_BAR_ACTION_ID_STOP) { remove_loading_floating_bar (view); nautilus_window_slot_stop_loading (view->details->slot); } } static void real_floating_bar_set_short_status (NautilusView *view, const gchar *primary_status, const gchar *detail_status) { gboolean disable_chrome; nautilus_floating_bar_cleanup_actions (NAUTILUS_FLOATING_BAR (view->details->floating_bar)); nautilus_floating_bar_set_show_spinner (NAUTILUS_FLOATING_BAR (view->details->floating_bar), view->details->loading); g_object_get (nautilus_view_get_window (view), "disable-chrome", &disable_chrome, NULL); if ((primary_status == NULL && detail_status == NULL) || disable_chrome) { gtk_widget_hide (view->details->floating_bar); return; } nautilus_floating_bar_set_labels (NAUTILUS_FLOATING_BAR (view->details->floating_bar), primary_status, detail_status); gtk_widget_show (view->details->floating_bar); } typedef struct { gchar *primary_status; gchar *detail_status; NautilusView *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) { FloatingBarSetStatusData *status_data = data; status_data->view->details->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 void set_floating_bar_status (NautilusView *view, const gchar *primary_status, const gchar *detail_status) { GtkSettings *settings; gint double_click_time; FloatingBarSetStatusData *status_data; if (view->details->floating_bar_set_status_timeout_id != 0) { g_source_remove (view->details->floating_bar_set_status_timeout_id); view->details->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; /* 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. */ view->details->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 (NautilusView *view) { NautilusDirectory *directory; char *uri; g_return_val_if_fail (NAUTILUS_IS_VIEW (view), NULL); if (view->details->model == NULL) { return NULL; } directory = view->details->model; if (NAUTILUS_IS_DESKTOP_DIRECTORY (directory)) { directory = nautilus_desktop_directory_get_real_directory (NAUTILUS_DESKTOP_DIRECTORY (directory)); } else { nautilus_directory_ref (directory); } uri = nautilus_directory_get_uri (directory); nautilus_directory_unref (directory); return uri; } /** * * nautilus_view_get_backing_uri: * * Returns the URI for the target location of new directory, new file, new * link and paste operations. */ char * nautilus_view_get_backing_uri (NautilusView *view) { g_return_val_if_fail (NAUTILUS_IS_VIEW (view), NULL); return NAUTILUS_VIEW_CLASS (G_OBJECT_GET_CLASS (view))->get_backing_uri (view); } /** * nautilus_view_select_all: * * select all the items in the view * **/ static void nautilus_view_select_all (NautilusView *view) { g_return_if_fail (NAUTILUS_IS_VIEW (view)); NAUTILUS_VIEW_CLASS (G_OBJECT_GET_CLASS (view))->select_all (view); } static void nautilus_view_select_first (NautilusView *view) { g_return_if_fail (NAUTILUS_IS_VIEW (view)); NAUTILUS_VIEW_CLASS (G_OBJECT_GET_CLASS (view))->select_first (view); } static void nautilus_view_call_set_selection (NautilusView *view, GList *selection) { g_return_if_fail (NAUTILUS_IS_VIEW (view)); NAUTILUS_VIEW_CLASS (G_OBJECT_GET_CLASS (view))->set_selection (view, selection); } static GList * nautilus_view_get_selection_for_file_transfer (NautilusView *view) { g_return_val_if_fail (NAUTILUS_IS_VIEW (view), NULL); return NAUTILUS_VIEW_CLASS (G_OBJECT_GET_CLASS (view))->get_selection_for_file_transfer (view); } static void nautilus_view_invert_selection (NautilusView *view) { g_return_if_fail (NAUTILUS_IS_VIEW (view)); NAUTILUS_VIEW_CLASS (G_OBJECT_GET_CLASS (view))->invert_selection (view); } /** * nautilus_view_reveal_selection: * * Scroll as necessary to reveal the selected items. **/ static void nautilus_view_reveal_selection (NautilusView *view) { g_return_if_fail (NAUTILUS_IS_VIEW (view)); NAUTILUS_VIEW_CLASS (G_OBJECT_GET_CLASS (view))->reveal_selection (view); } static gboolean nautilus_view_using_manual_layout (NautilusView *view) { g_return_val_if_fail (NAUTILUS_IS_VIEW (view), FALSE); return NAUTILUS_VIEW_CLASS (G_OBJECT_GET_CLASS (view))->using_manual_layout (view); } /** * nautilus_view_get_icon: * @view: a #NautilusView * * Retrieves the #GIcon that represents @view. * * Returns: (transfer none): the #Gicon that represents @view */ GIcon* nautilus_view_get_icon (NautilusView *view) { g_return_val_if_fail (NAUTILUS_IS_VIEW (view), NULL); return NAUTILUS_VIEW_CLASS (G_OBJECT_GET_CLASS (view))->get_icon (view); } /** * nautilus_view_get_view_widget: * @view: a #NautilusView * * Retrieves the view menu, as a #GtkWidget. If it's %NULL, * the button renders insensitive. * * Returns: (transfer none): a #GtkWidget for the view menu */ GtkWidget* nautilus_view_get_view_widget (NautilusView *view) { g_return_val_if_fail (NAUTILUS_IS_VIEW (view), NULL); return view->details->view_menu_widget; } /** * nautilus_view_can_rename_file * * Determine whether a file can be renamed. * @file: A NautilusFile * * Return value: TRUE if @file can be renamed, FALSE otherwise. * **/ static gboolean nautilus_view_can_rename_file (NautilusView *view, NautilusFile *file) { return NAUTILUS_VIEW_CLASS (G_OBJECT_GET_CLASS (view))->can_rename_file (view, file); } static gboolean nautilus_view_is_read_only (NautilusView *view) { g_return_val_if_fail (NAUTILUS_IS_VIEW (view), FALSE); return NAUTILUS_VIEW_CLASS (G_OBJECT_GET_CLASS (view))->is_read_only (view); } static gboolean showing_trash_directory (NautilusView *view) { NautilusFile *file; file = nautilus_view_get_directory_as_file (view); if (file != NULL) { return nautilus_file_is_in_trash (file); } return FALSE; } static gboolean showing_network_directory (NautilusView *view) { NautilusFile *file; file = nautilus_view_get_directory_as_file (view); if (file != NULL) { return nautilus_file_is_in_network (file); } return FALSE; } static gboolean showing_recent_directory (NautilusView *view) { NautilusFile *file; file = nautilus_view_get_directory_as_file (view); if (file != NULL) { return nautilus_file_is_in_recent (file); } return FALSE; } static gboolean nautilus_view_supports_creating_files (NautilusView *view) { g_return_val_if_fail (NAUTILUS_IS_VIEW (view), FALSE); return !nautilus_view_is_read_only (view) && !showing_trash_directory (view) && !showing_recent_directory (view); } static gboolean nautilus_view_is_empty (NautilusView *view) { g_return_val_if_fail (NAUTILUS_IS_VIEW (view), FALSE); return NAUTILUS_VIEW_CLASS (G_OBJECT_GET_CLASS (view))->is_empty (view); } /** * nautilus_view_bump_zoom_level: * * bump the current zoom level by invoking the relevant subclass through the slot * **/ void nautilus_view_bump_zoom_level (NautilusView *view, int zoom_increment) { g_return_if_fail (NAUTILUS_IS_VIEW (view)); if (!nautilus_view_supports_zooming (view)) { return; } NAUTILUS_VIEW_CLASS (G_OBJECT_GET_CLASS (view))->bump_zoom_level (view, zoom_increment); } /** * nautilus_view_can_zoom_in: * * Determine whether the view can be zoomed any closer. * @view: The zoomable NautilusView. * * Return value: TRUE if @view can be zoomed any closer, FALSE otherwise. * **/ gboolean nautilus_view_can_zoom_in (NautilusView *view) { g_return_val_if_fail (NAUTILUS_IS_VIEW (view), FALSE); if (!nautilus_view_supports_zooming (view)) { return FALSE; } return NAUTILUS_VIEW_CLASS (G_OBJECT_GET_CLASS (view))->can_zoom_in (view); } /** * nautilus_view_can_zoom_out: * * Determine whether the view can be zoomed any further away. * @view: The zoomable NautilusView. * * Return value: TRUE if @view can be zoomed any further away, FALSE otherwise. * **/ gboolean nautilus_view_can_zoom_out (NautilusView *view) { g_return_val_if_fail (NAUTILUS_IS_VIEW (view), FALSE); if (!nautilus_view_supports_zooming (view)) { return FALSE; } return NAUTILUS_VIEW_CLASS (G_OBJECT_GET_CLASS (view))->can_zoom_out (view); } gboolean nautilus_view_supports_zooming (NautilusView *view) { g_return_val_if_fail (NAUTILUS_IS_VIEW (view), FALSE); return view->details->supports_zooming; } /** * nautilus_view_restore_default_zoom_level: * * restore to the default zoom level by invoking the relevant subclass through the slot * **/ void nautilus_view_restore_default_zoom_level (NautilusView *view) { g_return_if_fail (NAUTILUS_IS_VIEW (view)); if (!nautilus_view_supports_zooming (view)) { return; } NAUTILUS_VIEW_CLASS (G_OBJECT_GET_CLASS (view))->restore_default_zoom_level (view); } gboolean nautilus_view_is_search (NautilusView *view) { if (!view->details->model) return FALSE; return NAUTILUS_IS_SEARCH_DIRECTORY (view->details->model); } const char * nautilus_view_get_view_id (NautilusView *view) { return NAUTILUS_VIEW_CLASS (G_OBJECT_GET_CLASS (view))->get_view_id (view); } char * nautilus_view_get_first_visible_file (NautilusView *view) { return NAUTILUS_VIEW_CLASS (G_OBJECT_GET_CLASS (view))->get_first_visible_file (view); } void nautilus_view_scroll_to_file (NautilusView *view, const char *uri) { NAUTILUS_VIEW_CLASS (G_OBJECT_GET_CLASS (view))->scroll_to_file (view, uri); } /** * nautilus_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 (but not its data). * @view: NautilusView whose selected items are of interest. * * Return value: GList of NautilusFile pointers representing the selection. * **/ GList * nautilus_view_get_selection (NautilusView *view) { g_return_val_if_fail (NAUTILUS_IS_VIEW (view), NULL); return NAUTILUS_VIEW_CLASS (G_OBJECT_GET_CLASS (view))->get_selection (view); } typedef struct { NautilusFile *file; NautilusView *directory_view; } ScriptLaunchParameters; typedef struct { NautilusFile *file; NautilusView *directory_view; } CreateTemplateParameters; static GList * file_and_directory_list_to_files (GList *fad_list) { GList *res, *l; FileAndDirectory *fad; res = NULL; for (l = fad_list; l != NULL; l = l->next) { fad = l->data; res = g_list_prepend (res, nautilus_file_ref (fad->file)); } return g_list_reverse (res); } static GList * file_and_directory_list_from_files (NautilusDirectory *directory, GList *files) { GList *res, *l; FileAndDirectory *fad; res = NULL; for (l = files; l != NULL; l = l->next) { fad = g_new0 (FileAndDirectory, 1); fad->directory = nautilus_directory_ref (directory); fad->file = nautilus_file_ref (l->data); res = g_list_prepend (res, fad); } return g_list_reverse (res); } static void file_and_directory_free (FileAndDirectory *fad) { nautilus_directory_unref (fad->directory); nautilus_file_unref (fad->file); g_free (fad); } static void file_and_directory_list_free (GList *list) { GList *l; for (l = list; l != NULL; l = l->next) { file_and_directory_free (l->data); } g_list_free (list); } 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, NautilusView *directory_view) { ScriptLaunchParameters *result; result = g_new0 (ScriptLaunchParameters, 1); g_object_ref (directory_view); result->directory_view = directory_view; nautilus_file_ref (file); result->file = file; return result; } static void script_launch_parameters_free (ScriptLaunchParameters *parameters) { g_object_unref (parameters->directory_view); nautilus_file_unref (parameters->file); g_free (parameters); } static CreateTemplateParameters * create_template_parameters_new (NautilusFile *file, NautilusView *directory_view) { CreateTemplateParameters *result; result = g_new0 (CreateTemplateParameters, 1); g_object_ref (directory_view); result->directory_view = directory_view; nautilus_file_ref (file); result->file = file; return result; } static void create_templates_parameters_free (CreateTemplateParameters *parameters) { g_object_unref (parameters->directory_view); nautilus_file_unref (parameters->file); g_free (parameters); } NautilusWindow * nautilus_view_get_window (NautilusView *view) { return nautilus_window_slot_get_window (view->details->slot); } NautilusWindowSlot * nautilus_view_get_nautilus_window_slot (NautilusView *view) { g_assert (view->details->slot != NULL); return view->details->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_view_get_containing_window (NautilusView *view) { GtkWidget *window; g_assert (NAUTILUS_IS_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_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 gboolean selection_not_empty_in_menu_callback (NautilusView *view, GList *selection) { if (selection != NULL) { return TRUE; } return FALSE; } static char * get_view_directory (NautilusView *view) { char *uri, *path; GFile *f; uri = nautilus_directory_get_uri (view->details->model); if (eel_uri_is_desktop (uri)) { g_free (uri); uri = nautilus_get_desktop_directory_uri (); } f = g_file_new_for_uri (uri); path = g_file_get_path (f); g_object_unref (f); g_free (uri); return path; } void nautilus_view_preview_files (NautilusView *view, GList *files, GArray *locations) { gchar *uri; guint xid; GtkWidget *toplevel; uri = nautilus_file_get_uri (files->data); toplevel = gtk_widget_get_toplevel (GTK_WIDGET (view)); xid = gdk_x11_window_get_xid (gtk_widget_get_window (toplevel)); nautilus_previewer_call_show_file (uri, xid, TRUE); g_free (uri); } void nautilus_view_activate_selection (NautilusView *view) { GList *selection; selection = nautilus_view_get_selection (view); nautilus_view_activate_files (view, selection, 0, TRUE); nautilus_file_list_free (selection); } void nautilus_view_activate_files (NautilusView *view, GList *files, NautilusWindowOpenFlags flags, gboolean confirm_multiple) { char *path; path = get_view_directory (view); nautilus_mime_activate_files (nautilus_view_get_containing_window (view), view->details->slot, files, path, flags, confirm_multiple); g_free (path); } static void nautilus_view_activate_file (NautilusView *view, NautilusFile *file, NautilusWindowOpenFlags flags) { char *path; path = get_view_directory (view); nautilus_mime_activate_file (nautilus_view_get_containing_window (view), view->details->slot, file, path, flags); g_free (path); } static void action_open_with_default_application (GSimpleAction *action, GVariant *state, gpointer user_data) { NautilusView *view; view = NAUTILUS_VIEW (user_data); nautilus_view_activate_selection (view); } static void action_open_file_and_close_window (GSimpleAction *action, GVariant *state, gpointer user_data) { GList *selection; NautilusView *view; view = NAUTILUS_VIEW (user_data); selection = nautilus_view_get_selection (view); nautilus_view_activate_files (view, selection, NAUTILUS_WINDOW_OPEN_FLAG_CLOSE_BEHIND, TRUE); nautilus_file_list_free (selection); } static void got_it_clicked (GtkDialog *dialog, gint response_id, gpointer user_data) { g_settings_set_boolean (nautilus_preferences, NAUTILUS_PREFERENCES_SHOW_MOVE_TO_TRASH_SHORTCUT_CHANGED_DIALOG, FALSE); } static void action_show_move_to_trash_shortcut_changed_dialog (GSimpleAction *action, GVariant *state, gpointer user_data) { NautilusView *view; GtkWindow *dialog; GtkBuilder *builder; gboolean show_dialog_preference; view = NAUTILUS_VIEW (user_data); show_dialog_preference = g_settings_get_boolean (nautilus_preferences, NAUTILUS_PREFERENCES_SHOW_MOVE_TO_TRASH_SHORTCUT_CHANGED_DIALOG); if (show_dialog_preference) { builder = gtk_builder_new_from_resource ("/org/gnome/nautilus/nautilus-move-to-trash-shortcut-changed.ui"); dialog = GTK_WINDOW (gtk_builder_get_object (builder, "move_to_trash_shortcut_changed_dialog")); gtk_window_set_transient_for (dialog, GTK_WINDOW (nautilus_view_get_window (view))); g_signal_connect (dialog, "response", G_CALLBACK (got_it_clicked), view); gtk_widget_show (GTK_WIDGET (dialog)); gtk_dialog_run(GTK_DIALOG (dialog)); gtk_widget_destroy (GTK_WIDGET (dialog)); g_object_unref (builder); } } static void action_open_item_location (GSimpleAction *action, GVariant *state, gpointer user_data) { NautilusView *view; GList *selection; NautilusFile *item; GFile *activation_location; NautilusFile *activation_file; NautilusFile *location; view = NAUTILUS_VIEW (user_data); selection = nautilus_view_get_selection (view); if (!selection) return; item = NAUTILUS_FILE (selection->data); activation_location = nautilus_file_get_activation_location (item); activation_file = nautilus_file_get (activation_location); location = nautilus_file_get_parent (activation_file); nautilus_view_activate_file (view, location, 0); nautilus_file_unref (location); nautilus_file_unref (activation_file); g_object_unref (activation_location); nautilus_file_list_free (selection); } static void action_open_item_new_tab (GSimpleAction *action, GVariant *state, gpointer user_data) { NautilusView *view; GList *selection; GtkWindow *window; view = NAUTILUS_VIEW (user_data); selection = nautilus_view_get_selection (view); window = nautilus_view_get_containing_window (view); if (nautilus_view_confirm_multiple (window, g_list_length (selection), TRUE)) { nautilus_view_activate_files (view, selection, NAUTILUS_WINDOW_OPEN_FLAG_NEW_TAB, FALSE); } nautilus_file_list_free (selection); } 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 (NautilusView *view, GList *files) { GtkWidget *dialog; gchar *mime_type; GtkWindow *parent_window; g_assert (NAUTILUS_IS_VIEW (view)); mime_type = nautilus_file_get_mime_type (files->data); parent_window = nautilus_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 (NautilusView *view) { GList *selection; g_assert (NAUTILUS_IS_VIEW (view)); selection = nautilus_view_get_selection (view); choose_program (view, selection); } static void action_open_with_other_application (GSimpleAction *action, GVariant *state, gpointer user_data) { g_assert (NAUTILUS_IS_VIEW (user_data)); open_with_other_program (NAUTILUS_VIEW (user_data)); } static void trash_or_delete_selected_files (NautilusView *view) { GList *selection; /* 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 (!view->details->selection_was_removed) { selection = nautilus_view_get_selection_for_file_transfer (view); trash_or_delete_files (nautilus_view_get_containing_window (view), selection, view); nautilus_file_list_free (selection); view->details->selection_was_removed = TRUE; } } static void action_move_to_trash (GSimpleAction *action, GVariant *state, gpointer user_data) { trash_or_delete_selected_files (NAUTILUS_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_VIEW (user_data)); } static void delete_selected_files (NautilusView *view) { GList *selection; GList *node; GList *locations; selection = nautilus_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 (locations, nautilus_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_VIEW (user_data)); } static void action_restore_from_trash (GSimpleAction *action, GVariant *state, gpointer user_data) { NautilusView *view; GList *selection; view = NAUTILUS_VIEW (user_data); selection = nautilus_view_get_selection_for_file_transfer (view); nautilus_restore_files_from_trash (selection, nautilus_view_get_containing_window (view)); nautilus_file_list_free (selection); } static void action_select_all (GSimpleAction *action, GVariant *state, gpointer user_data) { NautilusView *view; g_assert (NAUTILUS_IS_VIEW (user_data)); view = NAUTILUS_VIEW (user_data); nautilus_view_select_all (view); } static void action_invert_selection (GSimpleAction *action, GVariant *state, gpointer user_data) { g_assert (NAUTILUS_IS_VIEW (user_data)); nautilus_view_invert_selection (user_data); } static void pattern_select_response_cb (GtkWidget *dialog, int response, gpointer user_data) { NautilusView *view; NautilusDirectory *directory; GtkWidget *entry; GList *selection; view = NAUTILUS_VIEW (user_data); switch (response) { case GTK_RESPONSE_OK : entry = g_object_get_data (G_OBJECT (dialog), "entry"); directory = nautilus_view_get_model (view); selection = nautilus_directory_match_pattern (directory, gtk_entry_get_text (GTK_ENTRY (entry))); if (selection) { nautilus_view_call_set_selection (view, selection); nautilus_file_list_free (selection); nautilus_view_reveal_selection(view); } /* 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 (NautilusView *view) { GtkWidget *dialog; GtkWidget *label; GtkWidget *example; GtkWidget *grid; GtkWidget *entry; char *example_pattern; dialog = gtk_dialog_new_with_buttons (_("Select Items Matching"), nautilus_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_VIEW (user_data)); select_pattern(user_data); } static void zoom_level_changed (GtkRange *range, NautilusView *view) { g_action_group_change_action_state (view->details->view_action_group, "zoom-to-level", g_variant_new_int32 (gtk_range_get_value (range))); } static void reveal_newly_added_folder (NautilusView *view, NautilusFile *new_file, NautilusDirectory *directory, GFile *target_location) { GFile *location; location = nautilus_file_get_location (new_file); if (g_file_equal (location, target_location)) { g_signal_handlers_disconnect_by_func (view, G_CALLBACK (reveal_newly_added_folder), (void *) target_location); nautilus_view_select_file (view, new_file); } g_object_unref (location); } typedef struct { NautilusView *directory_view; GHashTable *added_locations; GList *selection; } NewFolderData; typedef struct { NautilusView *directory_view; GHashTable *to_remove_locations; NautilusFile *new_folder; } NewFolderSelectionData; static void track_newly_added_locations (NautilusView *view, NautilusFile *new_file, NautilusDirectory *directory, gpointer user_data) { NewFolderData *data; data = user_data; g_hash_table_insert (data->added_locations, nautilus_file_get_location (new_file), NULL); } static void new_folder_done (GFile *new_folder, gboolean success, gpointer user_data) { NautilusView *directory_view; NautilusFile *file; char screen_string[32]; GdkScreen *screen; NewFolderData *data; data = (NewFolderData *)user_data; directory_view = data->directory_view; if (directory_view == NULL) { goto fail; } g_signal_handlers_disconnect_by_func (directory_view, G_CALLBACK (track_newly_added_locations), (void *) data); if (new_folder == NULL) { goto fail; } screen = gtk_widget_get_screen (GTK_WIDGET (directory_view)); g_snprintf (screen_string, sizeof (screen_string), "%d", gdk_screen_get_number (screen)); file = nautilus_file_get (new_folder); nautilus_file_set_metadata (file, NAUTILUS_METADATA_KEY_SCREEN, NULL, screen_string); if (data->selection != NULL) { NewFolderSelectionData *sdata; GList *uris, *l; char *target_uri; sdata = g_new (NewFolderSelectionData, 1); sdata->directory_view = directory_view; sdata->to_remove_locations = g_hash_table_new_full (g_file_hash, (GEqualFunc)g_file_equal, g_object_unref, NULL); sdata->new_folder = g_object_ref (file); uris = NULL; for (l = data->selection; l != NULL; l = l->next) { GFile *old_location; GFile *new_location; char *basename; uris = g_list_prepend (uris, nautilus_file_get_uri ((NautilusFile *) l->data)); old_location = nautilus_file_get_location (l->data); basename = g_file_get_basename (old_location); new_location = g_file_resolve_relative_path (new_folder, basename); g_hash_table_insert (sdata->to_remove_locations, new_location, NULL); g_free (basename); g_object_unref (old_location); } uris = g_list_reverse (uris); target_uri = nautilus_file_get_uri (file); nautilus_view_move_copy_items (directory_view, uris, NULL, target_uri, GDK_ACTION_MOVE, 0, 0); g_list_free_full (uris, g_free); g_free (target_uri); } else { if (g_hash_table_lookup_extended (data->added_locations, new_folder, NULL, NULL)) { /* The file was already added */ nautilus_view_select_file (directory_view, file); } else { /* We need to run after the default handler adds the folder we want to * operate on. The ADD_FILE signal is registered as G_SIGNAL_RUN_LAST, so we * must use connect_after. */ g_signal_connect_data (directory_view, "add-file", G_CALLBACK (reveal_newly_added_folder), g_object_ref (new_folder), (GClosureNotify)g_object_unref, G_CONNECT_AFTER); } } 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 (NautilusView *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_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 GdkPoint * context_menu_to_file_operation_position (NautilusView *view) { g_return_val_if_fail (NAUTILUS_IS_VIEW (view), NULL); if (nautilus_view_using_manual_layout (view) && view->details->context_menu_position.x >= 0 && view->details->context_menu_position.y >= 0) { NAUTILUS_VIEW_CLASS (G_OBJECT_GET_CLASS (view))->widget_to_file_operation_position (view, &view->details->context_menu_position); return &view->details->context_menu_position; } else { return NULL; } } typedef struct { NautilusView *view; GtkWidget *dialog; GtkWidget *error_label; GtkWidget *name_entry; gboolean target_is_folder; NautilusFile *target_file; gboolean duplicated_is_folder; } FileNameDialogData; typedef struct { NautilusView *view; GtkWidget *name_entry; NautilusFile *target_file; } RenameDialogData; typedef struct { NautilusView *view; GtkWidget *name_entry; gboolean with_selection; } NewFolderDialogData; static gboolean duplicated_file_label_show (FileNameDialogData *data) { if (data->duplicated_is_folder) gtk_label_set_label (GTK_LABEL (data->error_label), _("A folder with that name already exists.")); else gtk_label_set_label (GTK_LABEL (data->error_label), _("A file with that name already exists.")); data->view->details->dialog_duplicated_name_label_timeout_id = 0; return FALSE; } static void nautilus_view_file_name_dialog_validate_name (FileNameDialogData *data) { gboolean duplicated_name; gboolean valid_name; gchar *name; GList *files; GList *node; NautilusFile *file; g_assert (data != NULL); g_assert (GTK_IS_ENTRY (data->name_entry)); g_assert (GTK_IS_LABEL (data->error_label)); g_assert (GTK_IS_DIALOG (data->dialog)); g_assert (NAUTILUS_IS_VIEW (data->view)); name = g_strstrip (g_strdup (gtk_entry_get_text (GTK_ENTRY (data->name_entry)))); duplicated_name = FALSE; valid_name = FALSE; files = nautilus_directory_get_file_list (data->view->details->model); for (node = files; node != NULL; node = node->next) { file = node->data; if (nautilus_file_compare_display_name (file, name) == 0) { /* If we are renaming, we don't want to block the user to * rename the file with the current file name */ if (data->target_file == NULL || nautilus_file_compare_display_name (data->target_file, name) != 0) { duplicated_name = TRUE; data->duplicated_is_folder = nautilus_file_is_directory (file); break; } } } nautilus_file_list_free (files); /* Remove any sources left behind by * previous calls of this function. */ if (data->view->details->dialog_duplicated_name_label_timeout_id > 0) { g_source_remove (data->view->details->dialog_duplicated_name_label_timeout_id); data->view->details->dialog_duplicated_name_label_timeout_id = 0; } if (duplicated_name) { data->view->details->dialog_duplicated_name_label_timeout_id = g_timeout_add (DIALOG_DUPLICATED_NAME_ERROR_LABEL_TIMEOUT, (GSourceFunc)duplicated_file_label_show, data); } else if (strstr (name, "/") != NULL) { if (data->target_is_folder) gtk_label_set_label (GTK_LABEL (data->error_label), _("Folder names cannot contain “/”.")); else gtk_label_set_label (GTK_LABEL (data->error_label), _("Files names cannot contain “/”.")); } else if (strcmp (name, ".") == 0){ if (data->target_is_folder) gtk_label_set_label (GTK_LABEL (data->error_label), _("A folder can not be called “.”.")); else gtk_label_set_label (GTK_LABEL (data->error_label), _("A file can not be called “.”.")); } else if (strcmp (name, "..") == 0){ if (data->target_is_folder) gtk_label_set_label (GTK_LABEL (data->error_label), _("A folder can not be called “..”.")); else gtk_label_set_label (GTK_LABEL (data->error_label), _("A file can not be called “..”.")); } else { /* No errors detected, empty the label */ gtk_label_set_label (GTK_LABEL (data->error_label), NULL); valid_name = strlen (name) > 0; } gtk_dialog_set_response_sensitive (GTK_DIALOG (data->dialog), GTK_RESPONSE_OK, valid_name); g_free (name); } static void nautilus_view_file_name_dialog_entry_on_validate (GObject *object, GParamSpec *params, gpointer user_data) { FileNameDialogData *data; data = (FileNameDialogData *) user_data; nautilus_view_file_name_dialog_validate_name (data); } static void nautilus_view_file_name_dialog_entry_on_activate (GtkWidget *entry, gpointer user_data) { FileNameDialogData *data; GtkWidget *create_button; data = (FileNameDialogData *) user_data; create_button = gtk_dialog_get_widget_for_response (GTK_DIALOG (data->dialog), GTK_RESPONSE_OK); /* nautilus_view_new_folder_dialog_validate_name performs * all the necessary validation, and it's not needed to check * it all again. Checking if the "Create" button is sensitive * is enough. */ if (gtk_widget_get_sensitive (create_button)) { gtk_dialog_response (GTK_DIALOG (data->dialog), GTK_RESPONSE_OK); } else { NautilusView *view = data->view; /* Since typos are immediately shown, only * handle name collisions here. */ if (view->details->dialog_duplicated_name_label_timeout_id > 0) { g_source_remove (view->details->dialog_duplicated_name_label_timeout_id); duplicated_file_label_show (data); } } } static void nautilus_view_rename_dialog_on_response (GtkDialog *dialog, gint response_id, gpointer user_data) { RenameDialogData *rename_data; gchar *name; rename_data = (RenameDialogData *) user_data; g_assert (rename_data != NULL); g_assert (GTK_IS_ENTRY (rename_data->name_entry)); g_assert (NAUTILUS_IS_FILE (rename_data->target_file)); if (response_id == GTK_RESPONSE_OK) { name = g_strstrip (g_strdup (gtk_entry_get_text (GTK_ENTRY (rename_data->name_entry)))); nautilus_rename_file (rename_data->target_file, name, NULL, NULL); g_free (name); } nautilus_view_select_file (rename_data->view, rename_data->target_file); nautilus_view_reveal_selection (rename_data->view); /* If there's any resources left from the delayed * message, it should be removed before it gets * triggered. */ if (rename_data->view->details->dialog_duplicated_name_label_timeout_id > 0) { g_source_remove (rename_data->view->details->dialog_duplicated_name_label_timeout_id); rename_data->view->details->dialog_duplicated_name_label_timeout_id = 0; } g_free (user_data); gtk_widget_destroy (GTK_WIDGET (dialog)); } static void nautilus_view_rename_dialog_new (NautilusView *view, NautilusFile *target_file) { FileNameDialogData *dialog_data; RenameDialogData *user_data; GtkWidget *button_ok; GtkWidget *label_file_name; GtkBuilder *builder; gint start_offset, end_offset; gchar *file_name; gchar *title; builder = gtk_builder_new_from_resource ("/org/gnome/nautilus/nautilus-file-name-dialog.ui"); button_ok = GTK_WIDGET (gtk_builder_get_object (builder, "ok_button")); label_file_name = GTK_WIDGET (gtk_builder_get_object (builder, "name_label")); dialog_data = g_new (FileNameDialogData, 1); dialog_data->view = view; dialog_data->dialog = GTK_WIDGET (gtk_builder_get_object (builder, "file_name_dialog")); dialog_data->error_label = GTK_WIDGET (gtk_builder_get_object (builder, "error_label")); dialog_data->name_entry = GTK_WIDGET (gtk_builder_get_object (builder, "name_entry")); dialog_data->target_is_folder = nautilus_file_is_directory (target_file); dialog_data->target_file = target_file; user_data = g_new (RenameDialogData, 1); user_data->view = view; user_data->target_file = target_file; user_data->name_entry = dialog_data->name_entry; gtk_window_set_transient_for (GTK_WINDOW (dialog_data->dialog), GTK_WINDOW (nautilus_view_get_window (view))); /* Connect signals */ gtk_builder_add_callback_symbols (builder, "nautilus_view_file_name_dialog_entry_on_validate", G_CALLBACK (nautilus_view_file_name_dialog_entry_on_validate), "nautilus_view_file_name_dialog_entry_on_activate", G_CALLBACK (nautilus_view_file_name_dialog_entry_on_activate), NULL); dialog_data->target_is_folder = nautilus_file_is_directory (target_file); gtk_button_set_label (GTK_BUTTON (button_ok), _("Rename")); file_name = nautilus_file_get_display_name (target_file); title = g_strdup_printf (_("Rename “%s”"), file_name); gtk_window_set_title (GTK_WINDOW (dialog_data->dialog), title); g_free (file_name); g_free (title); if (dialog_data->target_is_folder) gtk_label_set_text (GTK_LABEL (label_file_name), _("Folder name")); else gtk_label_set_text (GTK_LABEL (label_file_name), _("File name")); gtk_entry_set_text (GTK_ENTRY (dialog_data->name_entry), nautilus_file_get_name (target_file)); gtk_builder_connect_signals (builder, dialog_data); g_signal_connect (dialog_data->dialog, "response", G_CALLBACK (nautilus_view_rename_dialog_on_response), user_data); gtk_widget_show_all (dialog_data->dialog); /* Select the name part withouth the file extension */ eel_filename_get_rename_region (nautilus_file_get_name (target_file), &start_offset, &end_offset); gtk_editable_select_region (GTK_EDITABLE (dialog_data->name_entry), start_offset, end_offset); /* Update the ok button status */ nautilus_view_file_name_dialog_validate_name (dialog_data); g_object_unref (builder); } static void nautilus_view_new_folder_dialog_on_response (GtkDialog *dialog, gint response_id, gpointer user_data) { NewFolderDialogData *new_folder_data; new_folder_data = (NewFolderDialogData *) user_data; g_assert (new_folder_data != NULL); g_assert (GTK_IS_ENTRY (new_folder_data->name_entry)); if (response_id == GTK_RESPONSE_OK) { NewFolderData *data; GdkPoint *pos; char *parent_uri; gchar *name; data = new_folder_data_new (new_folder_data->view, new_folder_data->with_selection); name = g_strstrip (g_strdup (gtk_entry_get_text (GTK_ENTRY (new_folder_data->name_entry)))); g_signal_connect_data (new_folder_data->view, "add-file", G_CALLBACK (track_newly_added_locations), data, (GClosureNotify)NULL, G_CONNECT_AFTER); pos = context_menu_to_file_operation_position (new_folder_data->view); parent_uri = nautilus_view_get_backing_uri (new_folder_data->view); nautilus_file_operations_new_folder (GTK_WIDGET (new_folder_data->view), pos, parent_uri, name, new_folder_done, data); g_free (parent_uri); g_free (name); } /* If there's any resources left from the delayed * message, it should be removed before it gets * triggered. */ if (new_folder_data->view->details->dialog_duplicated_name_label_timeout_id > 0) { g_source_remove (new_folder_data->view->details->dialog_duplicated_name_label_timeout_id); new_folder_data->view->details->dialog_duplicated_name_label_timeout_id = 0; } g_free (user_data); gtk_widget_destroy (GTK_WIDGET (dialog)); } static void nautilus_view_new_folder_dialog_new (NautilusView *view, gboolean with_selection) { FileNameDialogData *dialog_data; NewFolderDialogData *user_data; GtkWidget *button_ok; GtkWidget *label_file_name; GtkBuilder *builder; builder = gtk_builder_new_from_resource ("/org/gnome/nautilus/nautilus-file-name-dialog.ui"); button_ok = GTK_WIDGET (gtk_builder_get_object (builder, "ok_button")); label_file_name = GTK_WIDGET (gtk_builder_get_object (builder, "name_label")); dialog_data = g_new (FileNameDialogData, 1); dialog_data->view = view; dialog_data->dialog = GTK_WIDGET (gtk_builder_get_object (builder, "file_name_dialog")); dialog_data->error_label = GTK_WIDGET (gtk_builder_get_object (builder, "error_label")); dialog_data->name_entry = GTK_WIDGET (gtk_builder_get_object (builder, "name_entry")); dialog_data->target_is_folder = TRUE; dialog_data->target_file = NULL; user_data = g_new (NewFolderDialogData, 1); user_data->view = view; user_data->with_selection = with_selection; user_data->name_entry = dialog_data->name_entry; gtk_window_set_transient_for (GTK_WINDOW (dialog_data->dialog), GTK_WINDOW (nautilus_view_get_window (view))); /* Connect signals */ gtk_builder_add_callback_symbols (builder, "nautilus_view_file_name_dialog_entry_on_validate", G_CALLBACK (nautilus_view_file_name_dialog_entry_on_validate), "nautilus_view_file_name_dialog_entry_on_activate", G_CALLBACK (nautilus_view_file_name_dialog_entry_on_activate), NULL); gtk_builder_connect_signals (builder, dialog_data); gtk_button_set_label (GTK_BUTTON (button_ok), _("Create")); gtk_label_set_text (GTK_LABEL (label_file_name), _("Folder name")); gtk_window_set_title (GTK_WINDOW (dialog_data->dialog), _("New Folder")); g_signal_connect (dialog_data->dialog, "response", G_CALLBACK (nautilus_view_new_folder_dialog_on_response), user_data); gtk_widget_show_all (dialog_data->dialog); /* Update the ok button status */ nautilus_view_file_name_dialog_validate_name (dialog_data); g_object_unref (builder); } static void nautilus_view_new_folder (NautilusView *directory_view, gboolean with_selection) { nautilus_view_new_folder_dialog_new (directory_view, with_selection); } static NewFolderData * setup_new_folder_data (NautilusView *directory_view) { NewFolderData *data; data = new_folder_data_new (directory_view, FALSE); g_signal_connect_data (directory_view, "add-file", G_CALLBACK (track_newly_added_locations), data, (GClosureNotify)NULL, G_CONNECT_AFTER); return data; } void nautilus_view_new_file_with_initial_contents (NautilusView *view, const char *parent_uri, const char *filename, const char *initial_contents, int length, GdkPoint *pos) { NewFolderData *data; g_assert (parent_uri != NULL); data = setup_new_folder_data (view); if (pos == NULL) { pos = context_menu_to_file_operation_position (view); } nautilus_file_operations_new_file (GTK_WIDGET (view), pos, parent_uri, filename, initial_contents, length, new_folder_done, data); } static void nautilus_view_new_file (NautilusView *directory_view, const char *parent_uri, NautilusFile *source) { GdkPoint *pos; NewFolderData *data; char *source_uri; char *container_uri; container_uri = NULL; if (parent_uri == NULL) { container_uri = nautilus_view_get_backing_uri (directory_view); g_assert (container_uri != NULL); } if (source == NULL) { nautilus_view_new_file_with_initial_contents (directory_view, parent_uri != NULL ? parent_uri : container_uri, NULL, NULL, 0, NULL); g_free (container_uri); return; } g_return_if_fail (nautilus_file_is_local (source)); pos = context_menu_to_file_operation_position (directory_view); 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), pos, 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_VIEW (user_data)); nautilus_view_new_folder (NAUTILUS_VIEW (user_data), FALSE); } static void action_new_folder_with_selection (GSimpleAction *action, GVariant *state, gpointer user_data) { g_assert (NAUTILUS_IS_VIEW (user_data)); nautilus_view_new_folder (NAUTILUS_VIEW (user_data), TRUE); } static void action_properties (GSimpleAction *action, GVariant *state, gpointer user_data) { NautilusView *view; GList *selection; GList *files; g_assert (NAUTILUS_IS_VIEW (user_data)); view = NAUTILUS_VIEW (user_data); selection = nautilus_view_get_selection (view); if (g_list_length (selection) == 0) { if (view->details->directory_as_file != NULL) { files = g_list_append (NULL, nautilus_file_ref (view->details->directory_as_file)); nautilus_properties_window_present (files, GTK_WIDGET (view), NULL); nautilus_file_list_free (files); } } else { nautilus_properties_window_present (selection, GTK_WIDGET (view), NULL); } nautilus_file_list_free (selection); } static void nautilus_view_set_show_hidden_files (NautilusView *view, gboolean show_hidden) { if (view->details->ignore_hidden_file_preferences) { return; } if (show_hidden != view->details->show_hidden_files) { view->details->show_hidden_files = show_hidden; g_settings_set_boolean (gtk_filechooser_preferences, NAUTILUS_PREFERENCES_SHOW_HIDDEN_FILES, show_hidden); if (view->details->model != NULL) { load_directory (view, view->details->model); } } } static void action_show_hidden_files (GSimpleAction *action, GVariant *state, gpointer user_data) { gboolean show_hidden; NautilusView *view; g_assert (NAUTILUS_IS_VIEW (user_data)); view = NAUTILUS_VIEW (user_data); show_hidden = g_variant_get_boolean (state); nautilus_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) { NautilusView *view; g_assert (NAUTILUS_IS_VIEW (user_data)); view = NAUTILUS_VIEW (user_data); nautilus_view_bump_zoom_level (view, 1); } static void action_zoom_out (GSimpleAction *action, GVariant *state, gpointer user_data) { NautilusView *view; g_assert (NAUTILUS_IS_VIEW (user_data)); view = NAUTILUS_VIEW (user_data); nautilus_view_bump_zoom_level (view, -1); } static void action_zoom_default (GSimpleAction *action, GVariant *state, gpointer user_data) { nautilus_view_restore_default_zoom_level (user_data); } static void action_open_item_new_window (GSimpleAction *action, GVariant *state, gpointer user_data) { NautilusView *view; GtkWindow *window; GList *selection; view = NAUTILUS_VIEW (user_data); selection = nautilus_view_get_selection (view); window = GTK_WINDOW (nautilus_view_get_containing_window (view)); if (nautilus_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 paste_clipboard_data (NautilusView *view, GtkSelectionData *selection_data, char *destination_uri) { gboolean cut; GList *item_uris; cut = FALSE; item_uris = nautilus_clipboard_get_uri_list_from_selection_data (selection_data, &cut, copied_files_atom); if (item_uris != NULL && destination_uri != NULL) { nautilus_view_move_copy_items (view, item_uris, NULL, destination_uri, cut ? GDK_ACTION_MOVE : GDK_ACTION_COPY, 0, 0); /* If items are cut then remove from clipboard */ if (cut) { gtk_clipboard_clear (nautilus_clipboard_get (GTK_WIDGET (view))); } g_list_free_full (item_uris, g_free); } } static void paste_clipboard_received_callback (GtkClipboard *clipboard, GtkSelectionData *selection_data, gpointer data) { NautilusView *view; char *view_uri; view = NAUTILUS_VIEW (data); view_uri = nautilus_view_get_backing_uri (view); if (view->details->slot != NULL) { paste_clipboard_data (view, selection_data, view_uri); } g_free (view_uri); g_object_unref (view); } static void action_paste_files (GSimpleAction *action, GVariant *state, gpointer user_data) { NautilusView *view; g_assert (NAUTILUS_IS_VIEW (user_data)); view = NAUTILUS_VIEW (user_data); g_object_ref (view); gtk_clipboard_request_contents (nautilus_clipboard_get (GTK_WIDGET (view)), copied_files_atom, paste_clipboard_received_callback, view); } static void click_policy_changed_callback (gpointer callback_data) { NautilusView *view; view = NAUTILUS_VIEW (callback_data); NAUTILUS_VIEW_CLASS (G_OBJECT_GET_CLASS (view))->click_policy_changed (view); } gboolean nautilus_view_should_sort_directories_first (NautilusView *view) { return view->details->sort_directories_first; } static void sort_directories_first_changed_callback (gpointer callback_data) { NautilusView *view; gboolean preference_value; view = NAUTILUS_VIEW (callback_data); preference_value = g_settings_get_boolean (nautilus_preferences, NAUTILUS_PREFERENCES_SORT_DIRECTORIES_FIRST); if (preference_value != view->details->sort_directories_first) { view->details->sort_directories_first = preference_value; NAUTILUS_VIEW_CLASS (G_OBJECT_GET_CLASS (view))->sort_directories_first_changed (view); } } static void show_hidden_files_changed_callback (gpointer callback_data) { NautilusView *view; gboolean preference_value; view = NAUTILUS_VIEW (callback_data); preference_value = g_settings_get_boolean (gtk_filechooser_preferences, NAUTILUS_PREFERENCES_SHOW_HIDDEN_FILES); nautilus_view_set_show_hidden_files (view, preference_value); } static gboolean set_up_scripts_directory_global (void) { char *old_scripts_directory_path; char *scripts_directory_path; const char *override; 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); } if (g_file_test (old_scripts_directory_path, G_FILE_TEST_IS_DIR) && !g_file_test (scripts_directory_path, G_FILE_TEST_EXISTS)) { char *updated; const char *message; /* test if we already attempted to migrate first */ updated = g_build_filename (old_scripts_directory_path, "DEPRECATED-DIRECTORY", NULL); message = _("Nautilus 3.6 deprecated this directory and tried migrating " "this configuration to ~/.local/share/nautilus"); if (!g_file_test (updated, G_FILE_TEST_EXISTS)) { char *parent_dir; parent_dir = g_path_get_dirname (scripts_directory_path); if (g_mkdir_with_parents (parent_dir, 0700) == 0) { int fd, res; /* rename() works fine if the destination directory is * empty. */ res = g_rename (old_scripts_directory_path, scripts_directory_path); if (res == -1) { fd = g_creat (updated, 0600); if (fd != -1) { res = write (fd, message, strlen (message)); close (fd); } } } g_free (parent_dir); } g_free (updated); } if (g_mkdir_with_parents (scripts_directory_path, 0700) == 0) { scripts_directory_uri = g_filename_to_uri (scripts_directory_path, NULL, NULL); scripts_directory_uri_length = strlen (scripts_directory_uri); } g_free (scripts_directory_path); g_free (old_scripts_directory_path); return (scripts_directory_uri != NULL) ? TRUE : FALSE; } static void scripts_added_or_changed_callback (NautilusDirectory *directory, GList *files, gpointer callback_data) { NautilusView *view; view = NAUTILUS_VIEW (callback_data); if (view->details->active) { schedule_update_context_menus (view); } } static void templates_added_or_changed_callback (NautilusDirectory *directory, GList *files, gpointer callback_data) { NautilusView *view; view = NAUTILUS_VIEW (callback_data); if (view->details->active) { schedule_update_context_menus (view); } } static void add_directory_to_directory_list (NautilusView *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 (NautilusView *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 (NautilusView *view, NautilusDirectory *directory) { add_directory_to_directory_list (view, directory, &view->details->scripts_directory_list, G_CALLBACK (scripts_added_or_changed_callback)); } static void remove_directory_from_scripts_directory_list (NautilusView *view, NautilusDirectory *directory) { remove_directory_from_directory_list (view, directory, &view->details->scripts_directory_list, G_CALLBACK (scripts_added_or_changed_callback)); } static void add_directory_to_templates_directory_list (NautilusView *view, NautilusDirectory *directory) { add_directory_to_directory_list (view, directory, &view->details->templates_directory_list, G_CALLBACK (templates_added_or_changed_callback)); } static void remove_directory_from_templates_directory_list (NautilusView *view, NautilusDirectory *directory) { remove_directory_from_directory_list (view, directory, &view->details->templates_directory_list, G_CALLBACK (templates_added_or_changed_callback)); } static void slot_active (NautilusWindowSlot *slot, NautilusView *view) { if (view->details->active) { return; } view->details->active = TRUE; /* Avoid updating the toolbar withouth making sure the toolbar * zoom slider has the correct adjustment that changes when the * view mode changes */ nautilus_window_slot_sync_view_mode (slot); nautilus_view_update_context_menus(view); nautilus_view_update_toolbar_menus (view); schedule_update_context_menus (view); gtk_widget_insert_action_group (GTK_WIDGET (nautilus_view_get_window (view)), "view", G_ACTION_GROUP (view->details->view_action_group)); } static void slot_inactive (NautilusWindowSlot *slot, NautilusView *view) { if (!view->details->active) { return; } view->details->active = FALSE; remove_update_context_menus_timeout_callback (view); gtk_widget_insert_action_group (GTK_WIDGET (nautilus_view_get_window (view)), "view", NULL); } static void nautilus_view_grab_focus (GtkWidget *widget) { /* focus the child of the scrolled window if it exists */ NautilusView *view; GtkWidget *child; view = NAUTILUS_VIEW (widget); child = gtk_bin_get_child (GTK_BIN (view->details->scrolled_window)); GTK_WIDGET_CLASS (nautilus_view_parent_class)->grab_focus (widget); if (child) { gtk_widget_grab_focus (GTK_WIDGET (child)); } } int nautilus_view_get_selection_count (NautilusView *view) { /* FIXME: This could be faster if we special cased it in subclasses */ GList *files; int len; files = nautilus_view_get_selection (NAUTILUS_VIEW (view)); len = g_list_length (files); nautilus_file_list_free (files); return len; } void nautilus_view_set_selection (NautilusView *nautilus_view, GList *selection) { NautilusView *view; view = NAUTILUS_VIEW (nautilus_view); if (!view->details->loading) { /* If we aren't still loading, set the selection right now, * and reveal the new selection. */ nautilus_view_call_set_selection (view, selection); nautilus_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. */ g_list_free_full (view->details->pending_selection, g_object_unref); view->details->pending_selection = g_list_copy_deep (selection, (GCopyFunc) g_object_ref, NULL); } } static char * get_bulk_rename_tool () { char *bulk_rename_tool; g_settings_get (nautilus_preferences, NAUTILUS_PREFERENCES_BULK_RENAME_TOOL, "^ay", &bulk_rename_tool); return g_strstrip (bulk_rename_tool); } static gboolean have_bulk_rename_tool () { char *bulk_rename_tool; gboolean have_tool; bulk_rename_tool = get_bulk_rename_tool (); have_tool = ((bulk_rename_tool != NULL) && (*bulk_rename_tool != '\0')); g_free (bulk_rename_tool); return have_tool; } static void nautilus_view_destroy (GtkWidget *object) { NautilusView *view; GList *node, *next; view = NAUTILUS_VIEW (object); nautilus_view_stop_loading (view); for (node = view->details->scripts_directory_list; node != NULL; node = next) { next = node->next; remove_directory_from_scripts_directory_list (view, node->data); } for (node = view->details->templates_directory_list; node != NULL; node = next) { next = node->next; remove_directory_from_templates_directory_list (view, node->data); } while (view->details->subdirectory_list != NULL) { nautilus_view_remove_subdirectory (view, view->details->subdirectory_list->data); } remove_update_context_menus_timeout_callback (view); remove_update_status_idle_callback (view); if (view->details->display_selection_idle_id != 0) { g_source_remove (view->details->display_selection_idle_id); view->details->display_selection_idle_id = 0; } if (view->details->reveal_selection_idle_id != 0) { g_source_remove (view->details->reveal_selection_idle_id); view->details->reveal_selection_idle_id = 0; } if (view->details->floating_bar_set_status_timeout_id != 0) { g_source_remove (view->details->floating_bar_set_status_timeout_id); view->details->floating_bar_set_status_timeout_id = 0; } if (view->details->floating_bar_loading_timeout_id != 0) { g_source_remove (view->details->floating_bar_loading_timeout_id); view->details->floating_bar_loading_timeout_id = 0; } if (view->details->directory_as_file) { nautilus_file_unref (view->details->directory_as_file); view->details->directory_as_file = NULL; } /* We don't own the slot, so no unref */ view->details->slot = NULL; GTK_WIDGET_CLASS (nautilus_view_parent_class)->destroy (object); } static void nautilus_view_finalize (GObject *object) { NautilusView *view; view = NAUTILUS_VIEW (object); 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 (nautilus_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_view_display_selection_info, view); g_signal_handlers_disconnect_by_func (gnome_lockdown_preferences, schedule_update_context_menus, view); g_hash_table_destroy (view->details->non_ready_files); G_OBJECT_CLASS (nautilus_view_parent_class)->finalize (object); } /** * nautilus_view_display_selection_info: * * Display information about the current selection, and notify the view frame of the changed selection. * @view: NautilusView for which to display selection info. * **/ void nautilus_view_display_selection_info (NautilusView *view) { GList *selection; 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_VIEW (view)); selection = nautilus_view_get_selection (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); } } nautilus_file_list_free (selection); /* 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_view_send_selection_change (NautilusView *view) { g_signal_emit (view, signals[SELECTION_CHANGED], 0); } void nautilus_view_load_location (NautilusView *nautilus_view, GFile *location) { NautilusDirectory *directory; nautilus_profile_start (NULL); directory = nautilus_directory_get (location); load_directory (nautilus_view, directory); nautilus_directory_unref (directory); nautilus_profile_end (NULL); } static gboolean reveal_selection_idle_callback (gpointer data) { NautilusView *view; view = NAUTILUS_VIEW (data); view->details->reveal_selection_idle_id = 0; nautilus_view_reveal_selection (view); return FALSE; } static void done_loading (NautilusView *view, gboolean all_files_seen) { GList *selection; gboolean do_reveal = FALSE; if (!view->details->loading) { return; } nautilus_profile_start (NULL); /* This can be called during destruction, in which case we set the model * as NULL. */ if (view->details->model != NULL) { nautilus_view_update_toolbar_menus (view); remove_loading_floating_bar (view); schedule_update_context_menus (view); schedule_update_status (view); reset_update_interval (view); selection = view->details->pending_selection; if (nautilus_view_is_search (view) && all_files_seen) { nautilus_view_select_first (view); do_reveal = TRUE; } else if (selection != NULL && all_files_seen) { view->details->pending_selection = NULL; nautilus_view_call_set_selection (view, selection); g_list_free_full (selection, g_object_unref); do_reveal = TRUE; } if (do_reveal) { if (NAUTILUS_IS_LIST_VIEW (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 (view->details->reveal_selection_idle_id != 0) { g_source_remove (view->details->reveal_selection_idle_id); } view->details->reveal_selection_idle_id = g_idle_add (reveal_selection_idle_callback, view); } else { nautilus_view_reveal_selection (view); } } nautilus_view_display_selection_info (view); } view->details->loading = FALSE; g_signal_emit (view, signals[END_LOADING], 0, all_files_seen); 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_file_callback (NautilusView *view, NautilusFile *new_file, NautilusDirectory *directory, DebutingFilesData *data) { GFile *location; nautilus_profile_start (NULL); location = nautilus_file_get_location (new_file); if (g_hash_table_remove (data->debuting_files, location)) { nautilus_file_ref (new_file); data->added_files = g_list_prepend (data->added_files, new_file); if (g_hash_table_size (data->debuting_files) == 0) { nautilus_view_call_set_selection (view, data->added_files); nautilus_view_reveal_selection (view); g_signal_handlers_disconnect_by_func (view, G_CALLBACK (debuting_files_add_file_callback), data); } } nautilus_profile_end (NULL); g_object_unref (location); } typedef struct { GList *added_files; NautilusView *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_file_callback (NautilusView *view, NautilusFile *new_file, NautilusDirectory *directory, CopyMoveDoneData *data) { nautilus_file_ref (new_file); data->added_files = g_list_prepend (data->added_files, new_file); } /* 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 (NautilusView *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_FILE signal is registered as G_SIGNAL_RUN_LAST, so we * must use connect_after. */ g_signal_connect (directory_view, "add-file", G_CALLBACK (pre_copy_move_add_file_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 (gpointer data, gpointer callback_data) { GFile *location; gboolean result; location = nautilus_file_get_location (NAUTILUS_FILE (data)); 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) { NautilusView *directory_view; CopyMoveDoneData *copy_move_done_data; DebutingFilesData *debuting_files_data; copy_move_done_data = (CopyMoveDoneData *) data; directory_view = copy_move_done_data->directory_view; if (directory_view != NULL) { g_assert (NAUTILUS_IS_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 = eel_g_list_partition (copy_move_done_data->added_files, copy_move_done_partition_func, debuting_files, ©_move_done_data->added_files); /* We're passed the same data used by pre_copy_move_add_file_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_file_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_file 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_view_call_set_selection (directory_view, debuting_files_data->added_files); nautilus_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_FILE signal is registered as G_SIGNAL_RUN_LAST, so we * must use connect_after. */ g_signal_connect_data (directory_view, "add-file", G_CALLBACK (debuting_files_add_file_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 (NautilusView *view, NautilusFile *file, NautilusDirectory *directory) { if (view->details->model != directory && g_list_find (view->details->subdirectory_list, directory) == NULL) { return FALSE; } return nautilus_directory_contains_file (directory, file); } static gboolean still_should_show_file (NautilusView *view, NautilusFile *file, NautilusDirectory *directory) { return nautilus_view_should_show_file (view, file) && view_file_still_belongs (view, file, directory); } 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; NautilusView *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_VIEW_CLASS (G_OBJECT_GET_CLASS (view))->compare_files (view, fad1->file, fad2->file); } } static void sort_files (NautilusView *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 (NautilusView *view) { GList *new_added_files, *new_changed_files, *old_added_files, *old_changed_files; GHashTable *non_ready_files; GList *node, *next; FileAndDirectory *pending; gboolean in_non_ready; new_added_files = view->details->new_added_files; view->details->new_added_files = NULL; new_changed_files = view->details->new_changed_files; view->details->new_changed_files = NULL; non_ready_files = view->details->non_ready_files; old_added_files = view->details->old_added_files; old_changed_files = view->details->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_lookup (non_ready_files, pending) != NULL; if (nautilus_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_insert (non_ready_files, pending, pending); } } } } file_and_directory_list_free (new_added_files); /* 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->file, pending->directory) || ready_to_load (pending->file)) { if (g_hash_table_lookup (non_ready_files, pending) != NULL) { g_hash_table_remove (non_ready_files, pending); if (still_should_show_file (view, pending->file, pending->directory)) { new_changed_files = g_list_delete_link (new_changed_files, node); old_added_files = g_list_prepend (old_added_files, pending); } } else if (nautilus_view_should_show_file (view, pending->file)) { new_changed_files = g_list_delete_link (new_changed_files, node); old_changed_files = g_list_prepend (old_changed_files, pending); } } } file_and_directory_list_free (new_changed_files); /* If any files were added to old_added_files, then resort it. */ if (old_added_files != view->details->old_added_files) { view->details->old_added_files = old_added_files; sort_files (view, &view->details->old_added_files); } /* Resort old_changed_files too, since file attributes * relevant to sorting could have changed. */ if (old_changed_files != view->details->old_changed_files) { view->details->old_changed_files = old_changed_files; sort_files (view, &view->details->old_changed_files); } } static void process_old_files (NautilusView *view) { GList *files_added, *files_changed, *node; FileAndDirectory *pending; GList *selection, *files; gboolean send_selection_change; files_added = view->details->old_added_files; files_changed = view->details->old_changed_files; send_selection_change = FALSE; if (files_added != NULL || files_changed != NULL) { g_signal_emit (view, signals[BEGIN_FILE_CHANGES], 0); for (node = files_added; node != NULL; node = node->next) { pending = node->data; g_signal_emit (view, signals[ADD_FILE], 0, pending->file, pending->directory); } for (node = files_changed; node != NULL; node = node->next) { pending = node->data; g_signal_emit (view, signals[still_should_show_file (view, pending->file, pending->directory) ? FILE_CHANGED : REMOVE_FILE], 0, pending->file, pending->directory); } g_signal_emit (view, signals[END_FILE_CHANGES], 0); check_empty_states (view); if (files_changed != NULL) { selection = nautilus_view_get_selection (view); files = file_and_directory_list_to_files (files_changed); send_selection_change = eel_g_lists_sort_and_check_for_intersection (&files, &selection); nautilus_file_list_free (files); nautilus_file_list_free (selection); } file_and_directory_list_free (view->details->old_added_files); view->details->old_added_files = NULL; file_and_directory_list_free (view->details->old_changed_files); view->details->old_changed_files = NULL; } if (send_selection_change) { /* Send a selection change since some file names could * have changed. */ nautilus_view_send_selection_change (view); } } static void display_pending_files (NautilusView *view) { process_new_files (view); process_old_files (view); if (view->details->model != NULL && nautilus_directory_are_all_files_seen (view->details->model) && g_hash_table_size (view->details->non_ready_files) == 0) { done_loading (view, TRUE); } } static gboolean display_selection_info_idle_callback (gpointer data) { NautilusView *view; view = NAUTILUS_VIEW (data); g_object_ref (G_OBJECT (view)); view->details->display_selection_idle_id = 0; nautilus_view_display_selection_info (view); nautilus_view_send_selection_change (view); g_object_unref (G_OBJECT (view)); return FALSE; } static void remove_update_context_menus_timeout_callback (NautilusView *view) { if (view->details->update_context_menus_timeout_id != 0) { g_source_remove (view->details->update_context_menus_timeout_id); view->details->update_context_menus_timeout_id = 0; } } static void update_context_menus_if_pending (NautilusView *view) { remove_update_context_menus_timeout_callback (view); nautilus_view_update_context_menus(view); } static gboolean update_context_menus_timeout_callback (gpointer data) { NautilusView *view; view = NAUTILUS_VIEW (data); g_object_ref (G_OBJECT (view)); view->details->update_context_menus_timeout_id = 0; nautilus_view_update_context_menus(view); g_object_unref (G_OBJECT (view)); return FALSE; } static gboolean display_pending_callback (gpointer data) { NautilusView *view; view = NAUTILUS_VIEW (data); g_object_ref (G_OBJECT (view)); view->details->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 (NautilusView *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. */ view->details->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 (NautilusView *view, guint interval) { /* No need to schedule an update if there's already one pending. */ if (view->details->display_pending_source_id != 0) { return; } view->details->display_pending_source_id = g_timeout_add (interval, display_pending_callback, view); } static void unschedule_display_of_pending_files (NautilusView *view) { /* Get rid of source if it's active. */ if (view->details->display_pending_source_id != 0) { g_source_remove (view->details->display_pending_source_id); view->details->display_pending_source_id = 0; } } static void queue_pending_files (NautilusView *view, NautilusDirectory *directory, GList *files, GList **pending_list) { if (files == NULL) { return; } *pending_list = g_list_concat (file_and_directory_list_from_files (directory, files), *pending_list); if (! view->details->loading || nautilus_directory_are_all_files_seen (directory)) { schedule_timeout_display_of_pending_files (view, view->details->update_interval); } } static void remove_changes_timeout_callback (NautilusView *view) { if (view->details->changes_timeout_id != 0) { g_source_remove (view->details->changes_timeout_id); view->details->changes_timeout_id = 0; } } static void reset_update_interval (NautilusView *view) { view->details->update_interval = UPDATE_INTERVAL_MIN; remove_changes_timeout_callback (view); /* Reschedule a pending timeout to idle */ if (view->details->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; NautilusView *view; view = NAUTILUS_VIEW (data); g_object_ref (G_OBJECT (view)); now = g_get_monotonic_time (); time_delta = now - view->details->last_queued; if (time_delta < UPDATE_INTERVAL_RESET*1000) { if (view->details->update_interval < UPDATE_INTERVAL_MAX && view->details->loading) { /* Increase */ view->details->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 (NautilusView *view) { /* Remember when the change was queued */ view->details->last_queued = g_get_monotonic_time (); /* No need to schedule if there are already changes pending or during loading */ if (view->details->changes_timeout_id != 0 || view->details->loading) { return; } view->details->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) { NautilusView *view; GtkWindow *window; char *uri; view = NAUTILUS_VIEW (callback_data); nautilus_profile_start (NULL); window = nautilus_view_get_containing_window (view); uri = nautilus_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, &view->details->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) { NautilusView *view; GtkWindow *window; char *uri; view = NAUTILUS_VIEW (callback_data); window = nautilus_view_get_containing_window (view); uri = nautilus_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, &view->details->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) { NautilusView *view; view = NAUTILUS_VIEW (callback_data); nautilus_profile_start (NULL); process_new_files (view); if (g_hash_table_size (view->details->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) { NautilusView *view; view = NAUTILUS_VIEW (callback_data); /* FIXME: By doing a stop, we discard some pending files. Is * that OK? */ nautilus_view_stop_loading (view); nautilus_report_error_loading_directory (nautilus_view_get_directory_as_file (view), error, nautilus_view_get_containing_window (view)); } void nautilus_view_add_subdirectory (NautilusView *view, NautilusDirectory*directory) { NautilusFileAttributes attributes; g_assert (!g_list_find (view->details->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_LINK_INFO | NAUTILUS_FILE_ATTRIBUTE_MOUNT | NAUTILUS_FILE_ATTRIBUTE_EXTENSION_INFO; nautilus_directory_file_monitor_add (directory, &view->details->model, view->details->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); view->details->subdirectory_list = g_list_prepend ( view->details->subdirectory_list, directory); } void nautilus_view_remove_subdirectory (NautilusView *view, NautilusDirectory*directory) { g_assert (g_list_find (view->details->subdirectory_list, directory)); view->details->subdirectory_list = g_list_remove ( view->details->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, &view->details->model); nautilus_directory_unref (directory); } /** * nautilus_view_get_loading: * @view: an #NautilusView. * * Return value: #gboolean inicating whether @view is currently loaded. * **/ gboolean nautilus_view_get_loading (NautilusView *view) { g_return_val_if_fail (NAUTILUS_IS_VIEW (view), FALSE); return view->details->loading; } /** * nautilus_view_get_model: * * Get the model for this NautilusView. * @view: NautilusView of interest. * * Return value: NautilusDirectory for this view. * **/ NautilusDirectory * nautilus_view_get_model (NautilusView *view) { g_return_val_if_fail (NAUTILUS_IS_VIEW (view), NULL); return view->details->model; } GtkWidget* nautilus_view_get_content_widget (NautilusView *view) { g_return_val_if_fail (NAUTILUS_IS_VIEW (view), NULL); return view->details->scrolled_window; } GdkAtom nautilus_view_get_copied_files_atom (NautilusView *view) { g_return_val_if_fail (NAUTILUS_IS_VIEW (view), GDK_NONE); return copied_files_atom; } static void offset_drop_points (GArray *relative_item_points, int x_offset, int y_offset) { guint index; if (relative_item_points == NULL) { return; } for (index = 0; index < relative_item_points->len; index++) { g_array_index (relative_item_points, GdkPoint, index).x += x_offset; g_array_index (relative_item_points, GdkPoint, index).y += y_offset; } } /* special_link_in_selection * * Return TRUE if one of our special links is in the selection. * Special links include the following: * NAUTILUS_DESKTOP_LINK_TRASH, NAUTILUS_DESKTOP_LINK_HOME, NAUTILUS_DESKTOP_LINK_MOUNT */ static gboolean special_link_in_selection (GList *selection) { gboolean saw_link; GList *node; NautilusFile *file; saw_link = FALSE; for (node = selection; node != NULL; node = node->next) { file = NAUTILUS_FILE (node->data); saw_link = NAUTILUS_IS_DESKTOP_ICON_FILE (file); if (saw_link) { break; } } return saw_link; } /* desktop_or_home_dir_in_selection * * Return TRUE if either the desktop or the home directory is in the selection. */ static gboolean desktop_or_home_dir_in_selection (GList *selection) { gboolean saw_desktop_or_home_dir; GList *node; NautilusFile *file; saw_desktop_or_home_dir = FALSE; for (node = selection; node != NULL; node = node->next) { file = NAUTILUS_FILE (node->data); saw_desktop_or_home_dir = nautilus_file_is_home (file) || nautilus_file_is_desktop_directory (file); if (saw_desktop_or_home_dir) { break; } } return saw_desktop_or_home_dir; } static void trash_or_delete_done_cb (GHashTable *debuting_uris, gboolean user_cancel, NautilusView *view) { if (user_cancel) { view->details->selection_was_removed = FALSE; } } static void trash_or_delete_files (GtkWindow *parent_window, const GList *files, NautilusView *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 (locations, parent_window, (NautilusDeleteCallback) trash_or_delete_done_cb, view); g_list_free_full (locations, g_object_unref); } static gboolean can_rename_file (NautilusView *view, NautilusFile *file) { return nautilus_file_can_rename (file); } static void open_one_in_new_window (gpointer data, gpointer callback_data) { g_assert (NAUTILUS_IS_FILE (data)); g_assert (NAUTILUS_IS_VIEW (callback_data)); nautilus_view_activate_file (NAUTILUS_VIEW (callback_data), NAUTILUS_FILE (data), NAUTILUS_WINDOW_OPEN_FLAG_NEW_WINDOW); } static void update_context_menu_position_from_event (NautilusView *view, GdkEventButton *event) { g_return_if_fail (NAUTILUS_IS_VIEW (view)); if (event != NULL) { view->details->context_menu_position.x = event->x; view->details->context_menu_position.y = event->y; } else { view->details->context_menu_position.x = -1; view->details->context_menu_position.y = -1; } } NautilusFile * nautilus_view_get_directory_as_file (NautilusView *view) { g_assert (NAUTILUS_IS_VIEW (view)); return view->details->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 (NautilusView *view) { NautilusWindow *window; GList *items; GList *providers; GList *l; GList *selection; window = nautilus_view_get_window (view); selection = nautilus_view_get_selection (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 (NautilusView *view) { NautilusWindow *window; NautilusFile *file; GList *items; GList *providers; GList *l; window = nautilus_view_get_window (view); providers = nautilus_module_get_extensions_for_type (NAUTILUS_TYPE_MENU_PROVIDER); file = nautilus_window_slot_get_file (view->details->slot); 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_background_items (provider, GTK_WIDGET (window), file); 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 (NautilusView *view, NautilusMenuItem *item, const char *action_name) { gboolean sensitive; GSimpleAction *action; 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 (view->details->view_action_group), G_ACTION (action)); g_simple_action_set_enabled (action, sensitive); g_object_unref (action); } static GMenu * build_menu_for_extension_menu_items (NautilusView *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; char *extension_id, *prefix, *parsed_name, *detailed_action_name; item = NAUTILUS_MENU_ITEM (l->data); g_object_get (item, "label", &label, "menu", &menu, "name", &name, NULL); extension_id = g_strdup_printf ("%s_%d", extension_prefix, idx); prefix = g_strdup_printf ("extension_%s_", extension_id); parsed_name = nautilus_escape_action_name (name, prefix); add_extension_action (view, item, parsed_name); detailed_action_name = g_strconcat ("view.", parsed_name, NULL); menu_item = g_menu_item_new (label, detailed_action_name); if (menu != NULL) { GList *children; GMenu *children_menu; 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, G_MENU_MODEL (children_menu)); nautilus_menu_item_list_free (children); g_object_unref (children_menu); } g_menu_append_item (gmenu, menu_item); idx++; g_free (extension_id); g_free (parsed_name); g_free (prefix); g_free (detailed_action_name); g_free (name); g_free (label); g_object_unref (menu_item); } return gmenu; } static void add_extension_menu_items (NautilusView *view, const gchar *extension_prefix, GList *menu_items, GMenu *insertion_menu) { GMenu *menu; menu = build_menu_for_extension_menu_items (view, extension_prefix, menu_items); nautilus_gmenu_merge (insertion_menu, menu, "extensions", FALSE); g_object_unref (menu); } static void update_extensions_menus (NautilusView *view) { GList *selection_items, *background_items; selection_items = get_extension_selection_menu_items (view); if (selection_items != NULL) { add_extension_menu_items (view, "selection", selection_items, view->details->selection_menu); nautilus_menu_item_list_free (selection_items); } background_items = get_extension_background_menu_items (view); if (background_items != NULL) { add_extension_menu_items (view, "background", background_items, view->details->background_menu); nautilus_menu_item_list_free (background_items); } } static char * change_to_view_directory (NautilusView *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 (GList *selection, gboolean get_paths) { char *path; char *uri; char *result; NautilusDesktopLink *link; GString *expanding_string; GList *node; GFile *location; expanding_string = g_string_new (""); for (node = selection; node != NULL; node = node->next) { uri = NULL; if (NAUTILUS_IS_DESKTOP_ICON_FILE (node->data)) { link = nautilus_desktop_icon_file_get_link (NAUTILUS_DESKTOP_ICON_FILE (node->data)); if (link != NULL) { location = nautilus_desktop_link_get_activation_location (link); uri = g_file_get_uri (location); g_object_unref (location); g_object_unref (G_OBJECT (link)); } } else { 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 (GList *selection) { return get_file_paths_or_uris_as_newline_delimited_string (selection, TRUE); } static char * get_file_uris_as_newline_delimited_string (GList *selection) { return get_file_paths_or_uris_as_newline_delimited_string (selection, FALSE); } /* returns newly allocated strings for setting the environment variables */ static void get_strings_for_environment_variables (NautilusView *view, GList *selected_files, char **file_paths, char **uris, char **uri) { char *directory_uri; /* 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 (view->details->model); if (g_str_has_prefix (directory_uri, "file:") || eel_uri_is_desktop (directory_uri) || eel_uri_is_trash (directory_uri)) { *file_paths = get_file_paths_as_newline_delimited_string (selected_files); } else { *file_paths = g_strdup (""); } g_free (directory_uri); *uris = get_file_uris_as_newline_delimited_string (selected_files); *uri = nautilus_directory_get_uri (view->details->model); if (eel_uri_is_desktop (*uri)) { g_free (*uri); *uri = nautilus_get_desktop_directory_uri (); } } /* * Set up some environment variables that scripts can use * to take advantage of the current Nautilus state. */ static void set_script_environment_variables (NautilusView *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_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) { ScriptLaunchParameters *launch_parameters; GdkScreen *screen; GList *selected_files; char *file_uri; char *local_file_path; char *quoted_path; char *old_working_dir; char **parameters; launch_parameters = (ScriptLaunchParameters *) user_data; 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); g_free (local_file_path); old_working_dir = change_to_view_directory (launch_parameters->directory_view); selected_files = nautilus_view_get_selection (launch_parameters->directory_view); set_script_environment_variables (launch_parameters->directory_view, selected_files); parameters = get_file_names_as_parameter_array (selected_files, launch_parameters->directory_view->details->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); nautilus_file_list_free (selected_files); 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 (NautilusView *view, NautilusFile *file, GMenu *menu) { gchar *name; GdkPixbuf *mimetype_icon; gchar *action_name, *detailed_action_name; ScriptLaunchParameters *launch_parameters; GAction *action; GMenuItem *menu_item; launch_parameters = script_launch_parameters_new (file, view); name = nautilus_file_get_display_name (file); action_name = nautilus_escape_action_name (name, "script_"); 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 (view->details->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); 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; } static GMenu * update_directory_in_scripts_menu (NautilusView *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_VIEW (view), NULL); g_return_val_if_fail (NAUTILUS_IS_DIRECTORY (directory), NULL); 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 (); file_list = nautilus_file_list_sort_by_display_name (filtered); num = 0; any_scripts = FALSE; for (node = file_list; 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 (file_list); if (!any_scripts) { g_object_unref (menu); menu = NULL; } return menu; } static void update_scripts_menu (NautilusView *view) { GList *sorted_copy, *node; NautilusDirectory *directory; GMenu *submenu; char *uri; sorted_copy = nautilus_directory_list_sort_by_uri (nautilus_directory_list_copy (view->details->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) { nautilus_gmenu_merge (view->details->selection_menu, submenu, "scripts-submenu", TRUE); g_object_unref (submenu); } view->details->scripts_present = submenu != NULL; } static void create_template (GSimpleAction *action, GVariant *state, gpointer user_data) { CreateTemplateParameters *parameters; parameters = user_data; nautilus_view_new_file (parameters->directory_view, NULL, parameters->file); } static void add_template_to_templates_menus (NautilusView *view, NautilusFile *file, GMenu *menu) { char *tmp, *uri, *name; GdkPixbuf *mimetype_icon; char *action_name, *detailed_action_name; CreateTemplateParameters *parameters; GAction *action; GMenuItem *menu_item; tmp = nautilus_file_get_display_name (file); name = eel_filename_strip_extension (tmp); g_free (tmp); uri = nautilus_file_get_uri (file); action_name = nautilus_escape_action_name (uri, "template_"); 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 (view->details->view_action_group), 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); 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 (NautilusView *view) { NautilusDirectory *templates_directory; GList *node, *next; char *templates_uri; for (node = view->details->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 GMenu * update_directory_in_templates_menu (NautilusView *view, NautilusDirectory *directory) { GList *file_list, *filtered, *node; GMenu *menu, *children_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_VIEW (view), NULL); g_return_val_if_fail (NAUTILUS_IS_DIRECTORY (directory), NULL); file_list = nautilus_directory_get_file_list (directory); filtered = nautilus_file_list_filter_hidden (file_list, FALSE); nautilus_file_list_free (file_list); templates_directory_uri = nautilus_get_templates_directory_uri (); menu = g_menu_new (); file_list = nautilus_file_list_sort_by_display_name (filtered); num = 0; any_templates = FALSE; for (node = file_list; 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)) { 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) { menu_item = g_menu_item_new_submenu (nautilus_file_get_display_name (file), G_MENU_MODEL (children_menu)); g_menu_append_item (menu, menu_item); any_templates = TRUE; g_object_unref (menu_item); g_object_unref (children_menu); } 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 (file_list); g_free (templates_directory_uri); if (!any_templates) { g_object_unref (menu); menu = NULL; } return menu; } static void update_templates_menu (NautilusView *view) { GList *sorted_copy, *node; NautilusDirectory *directory; GMenu *submenu; char *uri; char *templates_directory_uri; if (nautilus_should_use_templates_directory ()) { templates_directory_uri = nautilus_get_templates_directory_uri (); } else { view->details->templates_present = FALSE; return; } sorted_copy = nautilus_directory_list_sort_by_uri (nautilus_directory_list_copy (view->details->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) { nautilus_gmenu_merge (view->details->background_menu, submenu, "templates-submenu", FALSE); g_object_unref (submenu); } view->details->templates_present = submenu != NULL; g_free (templates_directory_uri); } static void action_open_scripts_folder (GSimpleAction *action, GVariant *state, gpointer user_data) { NautilusView *view; static GFile *location = NULL; if (location == NULL) { location = g_file_new_for_uri (scripts_directory_uri); } view = NAUTILUS_VIEW (user_data); nautilus_window_slot_open_location (view->details->slot, location, 0); } typedef struct _CopyCallbackData { NautilusView *view; GtkFileChooser *chooser; GHashTable *locations; GList *selection; gboolean is_move; } CopyCallbackData; static void add_bookmark_for_uri (CopyCallbackData *data, const char *uri) { GError *error = NULL; int count; count = GPOINTER_TO_INT (g_hash_table_lookup (data->locations, uri)); if (count == 0) { gtk_file_chooser_add_shortcut_folder_uri (data->chooser, uri, &error); if (error != NULL) { DEBUG ("Unable to add location '%s' to file selector: %s", uri, error->message); g_clear_error (&error); } } g_hash_table_replace (data->locations, g_strdup (uri), GINT_TO_POINTER (count + 1)); } static void remove_bookmark_for_uri (CopyCallbackData *data, const char *uri) { GError *error = NULL; int count; count = GPOINTER_TO_INT (g_hash_table_lookup (data->locations, uri)); if (count == 1) { gtk_file_chooser_remove_shortcut_folder_uri (data->chooser, uri, &error); if (error != NULL) { DEBUG ("Unable to remove location '%s' to file selector: %s", uri, error->message); g_clear_error (&error); } g_hash_table_remove (data->locations, uri); } else { g_hash_table_replace (data->locations, g_strdup (uri), GINT_TO_POINTER (count - 1)); } } static void add_bookmarks_for_window_slot (CopyCallbackData *data, NautilusWindowSlot *slot) { char *uri; uri = nautilus_window_slot_get_location_uri (slot); if (uri != NULL) { add_bookmark_for_uri (data, uri); } g_free (uri); } static void remove_bookmarks_for_window_slot (CopyCallbackData *data, NautilusWindowSlot *slot) { char *uri; uri = nautilus_window_slot_get_location_uri (slot); if (uri != NULL) { remove_bookmark_for_uri (data, uri); } g_free (uri); } static void on_slot_location_changed (NautilusWindowSlot *slot, const char *from, const char *to, CopyCallbackData *data) { if (from != NULL) { remove_bookmark_for_uri (data, from); } if (to != NULL) { add_bookmark_for_uri (data, to); } } static void on_slot_added (NautilusWindow *window, NautilusWindowSlot *slot, CopyCallbackData *data) { add_bookmarks_for_window_slot (data, slot); g_signal_connect (slot, "location-changed", G_CALLBACK (on_slot_location_changed), data); } static void on_slot_removed (NautilusWindow *window, NautilusWindowSlot *slot, CopyCallbackData *data) { remove_bookmarks_for_window_slot (data, slot); g_signal_handlers_disconnect_by_func (slot, G_CALLBACK (on_slot_location_changed), data); } static void add_bookmarks_for_window (CopyCallbackData *data, NautilusWindow *window) { GList *s; GList *slots; slots = nautilus_window_get_slots (window); for (s = slots; s != NULL; s = s->next) { NautilusWindowSlot *slot = s->data; add_bookmarks_for_window_slot (data, slot); g_signal_connect (slot, "location-changed", G_CALLBACK (on_slot_location_changed), data); } g_signal_connect (window, "slot-added", G_CALLBACK (on_slot_added), data); g_signal_connect (window, "slot-removed", G_CALLBACK (on_slot_removed), data); } static void remove_bookmarks_for_window (CopyCallbackData *data, NautilusWindow *window) { GList *s; GList *slots; slots = nautilus_window_get_slots (window); for (s = slots; s != NULL; s = s->next) { NautilusWindowSlot *slot = s->data; remove_bookmarks_for_window_slot (data, slot); g_signal_handlers_disconnect_by_func (slot, G_CALLBACK (on_slot_location_changed), data); } g_signal_handlers_disconnect_by_func (window, G_CALLBACK (on_slot_added), data); g_signal_handlers_disconnect_by_func (window, G_CALLBACK (on_slot_removed), data); } static void on_app_window_added (GtkApplication *application, GtkWindow *window, CopyCallbackData *data) { add_bookmarks_for_window (data, NAUTILUS_WINDOW (window)); } static void on_app_window_removed (GtkApplication *application, GtkWindow *window, CopyCallbackData *data) { remove_bookmarks_for_window (data, NAUTILUS_WINDOW (window)); } static void copy_data_free (CopyCallbackData *data) { NautilusApplication *application; GList *windows; GList *w; application = NAUTILUS_APPLICATION (g_application_get_default ()); g_signal_handlers_disconnect_by_func (application, G_CALLBACK (on_app_window_added), data); g_signal_handlers_disconnect_by_func (application, G_CALLBACK (on_app_window_removed), data); windows = nautilus_application_get_windows (application); for (w = windows; w != NULL; w = w->next) { NautilusWindow *window = w->data; GList *slots; GList *s; slots = nautilus_window_get_slots (window); for (s = slots; s != NULL; s = s->next) { NautilusWindowSlot *slot = s->data; g_signal_handlers_disconnect_by_func (slot, G_CALLBACK (on_slot_location_changed), data); } g_signal_handlers_disconnect_by_func (window, G_CALLBACK (on_slot_added), data); g_signal_handlers_disconnect_by_func (window, G_CALLBACK (on_slot_removed), data); } nautilus_file_list_free (data->selection); g_hash_table_destroy (data->locations); 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_view_move_copy_items (copy_data->view, uris, NULL, target_uri, copy_data->is_move ? GDK_ACTION_MOVE : GDK_ACTION_COPY, 0, 0); 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 add_window_location_bookmarks (CopyCallbackData *data) { NautilusApplication *application; GList *windows; GList *w; application = NAUTILUS_APPLICATION (g_application_get_default ()); windows = nautilus_application_get_windows (application); g_signal_connect (application, "window-added", G_CALLBACK (on_app_window_added), data); g_signal_connect (application, "window-removed", G_CALLBACK (on_app_window_removed), data); for (w = windows; w != NULL; w = w->next) { NautilusWindow *window = w->data; add_bookmarks_for_window (data, window); } } static void copy_or_move_selection (NautilusView *view, gboolean is_move) { GtkWidget *dialog; char *uri; CopyCallbackData *copy_data; GList *selection; const gchar *title; if (is_move) { title = _("Select Move Destination"); } else { title = _("Select Copy Destination"); } selection = nautilus_view_get_selection_for_file_transfer (view); dialog = gtk_file_chooser_dialog_new (title, GTK_WINDOW (nautilus_view_get_window (view)), GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER, _("_Cancel"), GTK_RESPONSE_CANCEL, _("_Select"), GTK_RESPONSE_OK, NULL); 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; copy_data->chooser = GTK_FILE_CHOOSER (dialog); copy_data->locations = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL); add_window_location_bookmarks (copy_data); 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); } uri = nautilus_directory_get_uri (view->details->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 copy_or_cut_files (NautilusView *view, GList *clipboard_contents, gboolean cut) { NautilusClipboardInfo info; GtkTargetList *target_list; GtkTargetEntry *targets; int n_targets; info.files = clipboard_contents; info.cut = cut; target_list = gtk_target_list_new (NULL, 0); gtk_target_list_add (target_list, copied_files_atom, 0, 0); gtk_target_list_add_uri_targets (target_list, 0); gtk_target_list_add_text_targets (target_list, 0); targets = gtk_target_table_new_from_list (target_list, &n_targets); gtk_target_list_unref (target_list); gtk_clipboard_set_with_data (nautilus_clipboard_get (GTK_WIDGET (view)), targets, n_targets, nautilus_get_clipboard_callback, nautilus_clear_clipboard_callback, NULL); gtk_target_table_free (targets, n_targets); nautilus_clipboard_monitor_set_clipboard_info (nautilus_clipboard_monitor_get (), &info); } static void action_copy (GSimpleAction *action, GVariant *state, gpointer user_data) { NautilusView *view; GList *selection; view = NAUTILUS_VIEW (user_data); selection = nautilus_view_get_selection_for_file_transfer (view); copy_or_cut_files (view, selection, FALSE); nautilus_file_list_free (selection); } static void action_cut (GSimpleAction *action, GVariant *state, gpointer user_data) { NautilusView *view; GList *selection; view = NAUTILUS_VIEW (user_data); selection = nautilus_view_get_selection_for_file_transfer (view); copy_or_cut_files (view, selection, TRUE); nautilus_file_list_free (selection); } static void action_copy_to (GSimpleAction *action, GVariant *state, gpointer user_data) { NautilusView *view; view = NAUTILUS_VIEW (user_data); copy_or_move_selection (view, FALSE); } static void action_move_to (GSimpleAction *action, GVariant *state, gpointer user_data) { NautilusView *view; view = NAUTILUS_VIEW (user_data); copy_or_move_selection (view, TRUE); } typedef struct { NautilusView *view; NautilusFile *target; } PasteIntoData; static void paste_into_clipboard_received_callback (GtkClipboard *clipboard, GtkSelectionData *selection_data, gpointer callback_data) { PasteIntoData *data; NautilusView *view; char *directory_uri; data = (PasteIntoData *) callback_data; view = NAUTILUS_VIEW (data->view); if (view->details->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 (NautilusView *view, NautilusFile *target) { PasteIntoData *data; g_assert (NAUTILUS_IS_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_contents (nautilus_clipboard_get (GTK_WIDGET (view)), copied_files_atom, paste_into_clipboard_received_callback, data); } static void action_paste_files_into (GSimpleAction *action, GVariant *state, gpointer user_data) { NautilusView *view; GList *selection; view = NAUTILUS_VIEW (user_data); selection = nautilus_view_get_selection (view); if (selection != NULL) { paste_into (view, NAUTILUS_FILE (selection->data)); nautilus_file_list_free (selection); } } static void invoke_external_bulk_rename_utility (NautilusView *view, GList *selection) { GString *cmd; char *parameter; char *quoted_parameter; char *bulk_rename_tool; GList *walk; NautilusFile *file; /* assemble command line */ bulk_rename_tool = get_bulk_rename_tool (); cmd = g_string_new (bulk_rename_tool); g_free (bulk_rename_tool); for (walk = selection; walk; walk = walk->next) { file = walk->data; parameter = nautilus_file_get_uri (file); quoted_parameter = g_shell_quote (parameter); g_free (parameter); cmd = g_string_append (cmd, " "); cmd = g_string_append (cmd, quoted_parameter); g_free (quoted_parameter); } /* spawning and error handling */ nautilus_launch_application_from_command (gtk_widget_get_screen (GTK_WIDGET (view)), cmd->str, FALSE, NULL); g_string_free (cmd, TRUE); } static void real_action_rename (NautilusView *view, gboolean select_all) { NautilusFile *file; GList *selection; g_assert (NAUTILUS_IS_VIEW (view)); selection = nautilus_view_get_selection (view); if (selection_not_empty_in_menu_callback (view, selection)) { /* If there is more than one file selected, invoke a batch renamer */ if (selection->next != NULL) { if (have_bulk_rename_tool ()) { invoke_external_bulk_rename_utility (view, selection); } } else { file = NAUTILUS_FILE (selection->data); if (!select_all) { /* directories don't have a file extension, so * they are always pre-selected as a whole */ select_all = nautilus_file_is_directory (file); } nautilus_view_rename_dialog_new (view, file); } } nautilus_file_list_free (selection); } static void action_rename (GSimpleAction *action, GVariant *state, gpointer user_data) { real_action_rename (NAUTILUS_VIEW (user_data), FALSE); } #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) { GList *selection; /* Copy the item to Pictures/Wallpaper since it may be remote. Then set it as the current wallpaper. */ g_assert (NAUTILUS_IS_VIEW (user_data)); selection = nautilus_view_get_selection (user_data); if (can_set_wallpaper (selection) && selection_not_empty_in_menu_callback (user_data, 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, NULL, 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); } nautilus_file_list_free (selection); } static void file_mount_callback (NautilusFile *file, GFile *result_location, GError *error, gpointer callback_data) { NautilusView *view; view = NAUTILUS_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); eel_show_error_dialog (text, error->message, GTK_WINDOW (nautilus_view_get_window (view))); g_free (text); g_free (name); } } static void file_unmount_callback (NautilusFile *file, GFile *result_location, GError *error, gpointer callback_data) { NautilusView *view; view = NAUTILUS_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); eel_show_error_dialog (text, error->message, GTK_WINDOW (nautilus_view_get_window (view))); g_free (text); g_free (name); } } static void file_eject_callback (NautilusFile *file, GFile *result_location, GError *error, gpointer callback_data) { NautilusView *view; view = NAUTILUS_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); eel_show_error_dialog (text, error->message, GTK_WINDOW (nautilus_view_get_window (view))); g_free (text); g_free (name); } } static void file_stop_callback (NautilusFile *file, GFile *result_location, GError *error, gpointer callback_data) { NautilusView *view; view = NAUTILUS_VIEW (callback_data); if (error != NULL && (error->domain != G_IO_ERROR || (error->code != G_IO_ERROR_CANCELLED && error->code != G_IO_ERROR_FAILED_HANDLED))) { eel_show_error_dialog (_("Unable to stop drive"), error->message, GTK_WINDOW (nautilus_view_get_window (view))); } } static void action_mount_volume (GSimpleAction *action, GVariant *state, gpointer user_data) { NautilusFile *file; GList *selection, *l; NautilusView *view; GMountOperation *mount_op; 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_mount (file)) { mount_op = gtk_mount_operation_new (nautilus_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; GList *selection, *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_unmount (file)) { GMountOperation *mount_op; mount_op = gtk_mount_operation_new (nautilus_view_get_containing_window (view)); nautilus_file_unmount (file, mount_op, NULL, file_unmount_callback, g_object_ref (view)); g_object_unref (mount_op); } } nautilus_file_list_free (selection); } static void action_eject_volume (GSimpleAction *action, GVariant *state, gpointer user_data) { NautilusFile *file; GList *selection, *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_eject (file)) { GMountOperation *mount_op; mount_op = gtk_mount_operation_new (nautilus_view_get_containing_window (view)); nautilus_file_eject (file, mount_op, NULL, file_eject_callback, g_object_ref (view)); g_object_unref (mount_op); } } nautilus_file_list_free (selection); } static void file_start_callback (NautilusFile *file, GFile *result_location, GError *error, gpointer callback_data) { NautilusView *view; view = NAUTILUS_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); eel_show_error_dialog (text, error->message, GTK_WINDOW (nautilus_view_get_window (view))); g_free (text); g_free (name); } } static void action_start_volume (GSimpleAction *action, GVariant *state, gpointer user_data) { NautilusFile *file; GList *selection, *l; NautilusView *view; GMountOperation *mount_op; 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_start (file) || nautilus_file_can_start_degraded (file)) { mount_op = gtk_mount_operation_new (nautilus_view_get_containing_window (view)); nautilus_file_start (file, mount_op, NULL, file_start_callback, view); g_object_unref (mount_op); } } nautilus_file_list_free (selection); } static void action_stop_volume (GSimpleAction *action, GVariant *state, gpointer user_data) { NautilusFile *file; GList *selection, *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_stop (file)) { GMountOperation *mount_op; mount_op = gtk_mount_operation_new (nautilus_view_get_containing_window (view)); nautilus_file_stop (file, mount_op, NULL, file_stop_callback, view); g_object_unref (mount_op); } } nautilus_file_list_free (selection); } static void action_detect_media (GSimpleAction *action, GVariant *state, gpointer user_data) { NautilusFile *file; GList *selection, *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); } } nautilus_file_list_free (selection); } const GActionEntry view_entries[] = { /* Toolbar menu */ { "zoom-in", action_zoom_in }, { "zoom-out", action_zoom_out }, { "zoom-default", action_zoom_default }, { "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 }, { "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}, { "move-to", action_move_to}, { "copy-to", action_copy_to}, { "move-to-trash", action_move_to_trash}, { "delete-from-trash", action_delete }, /* 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 }, { "remove-from-recent", action_remove_from_recent }, { "restore-from-trash", action_restore_from_trash}, { "paste-into", action_paste_files_into }, { "rename", action_rename}, { "properties", action_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 }, { "open-file-and-close-window", action_open_file_and_close_window }, /* Warning dialog for the change of the shorcut to move to trash */ { "show-move-to-trash-shortcut-changed-dialog", action_show_move_to_trash_shortcut_changed_dialog } }; 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 clipboard_targets_received (GtkClipboard *clipboard, GdkAtom *targets, int n_targets, gpointer user_data) { NautilusView *view; gboolean can_paste; int i; GAction *action; view = NAUTILUS_VIEW (user_data); can_paste = FALSE; if (view->details->slot == NULL || !view->details->active) { /* We've been destroyed or became inactive since call */ g_object_unref (view); return; } if (targets) { for (i = 0; i < n_targets; i++) { if (targets[i] == copied_files_atom) { can_paste = TRUE; } } } action = g_action_map_lookup_action (G_ACTION_MAP (view->details->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), can_paste && g_action_get_enabled (action)); action = g_action_map_lookup_action (G_ACTION_MAP (view->details->view_action_group), "paste-into"); g_simple_action_set_enabled (G_SIMPLE_ACTION (action), can_paste && 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 clipboard_changed_callback (NautilusClipboardMonitor *monitor, NautilusView *view) { /* Update paste menu item */ nautilus_view_update_context_menus (view); } 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; } GActionGroup * nautilus_view_get_action_group (NautilusView *view) { g_assert (NAUTILUS_IS_VIEW (view)); return view->details->view_action_group; } static void real_update_actions_state (NautilusView *view) { GList *selection, *l; NautilusFile *file; gint selection_count; gboolean selection_contains_special_link; gboolean selection_contains_desktop_or_home_dir; gboolean selection_contains_recent; gboolean selection_contains_search; 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 show_app, show_run; gboolean item_opens_in_view; gboolean is_read_only; GAction *action; GAppInfo *app; gboolean show_properties; GActionGroup *view_action_group; gboolean show_mount; gboolean show_unmount; gboolean show_eject; gboolean show_start; gboolean show_stop; gboolean show_detect_media; GDriveStartStopType start_stop_type; view_action_group = view->details->view_action_group; selection = nautilus_view_get_selection (view); selection_count = g_list_length (selection); selection_contains_special_link = special_link_in_selection (selection); selection_contains_desktop_or_home_dir = desktop_or_home_dir_in_selection (selection); selection_contains_recent = showing_recent_directory (view); selection_contains_search = nautilus_view_is_search (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); is_read_only = nautilus_view_is_read_only (view); can_create_files = nautilus_view_supports_creating_files (view); can_delete_files = can_delete_all (selection) && selection_count != 0 && !selection_contains_special_link && !selection_contains_desktop_or_home_dir; can_trash_files = can_trash_all (selection) && selection_count != 0 && !selection_contains_special_link && !selection_contains_desktop_or_home_dir; can_copy_files = selection_count != 0 && !selection_contains_special_link; can_move_files = can_delete_files && !selection_contains_recent; can_paste_files_into = (!selection_contains_recent && selection_count == 1 && can_paste_into_file (NAUTILUS_FILE (selection->data))); show_properties = !showing_network_directory (view) && (!NAUTILUS_IS_DESKTOP_CANVAS_VIEW (view) || selection_count > 0); /* 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); 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), have_bulk_rename_tool ()); } else { g_simple_action_set_enabled (G_SIMPLE_ACTION (action), selection_count == 1 && nautilus_view_can_rename_file (view, selection->data)); } 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)); 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); selection = nautilus_view_get_selection (view); selection_count = g_list_length (selection); show_app = show_run = item_opens_in_view = selection_count != 0; for (l = selection; l != NULL; l = l->next) { NautilusFile *file; file = NAUTILUS_FILE (selection->data); if (!nautilus_mime_file_opens_in_external_app (file)) { show_app = FALSE; } if (!nautilus_mime_file_launches (file)) { show_run = FALSE; } if (!nautilus_mime_file_opens_in_view (file)) { item_opens_in_view = FALSE; } if (!show_app && !show_run && !item_opens_in_view) { break; } } /* Open With menu item */ app = NULL; if (show_app) { app = nautilus_mime_get_default_application_for_files (selection); } 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), app != NULL || (selection_count > 0 && nautilus_file_is_directory (NAUTILUS_FILE (selection->data)))); 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); 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); 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), "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); /* Drive menu */ show_mount = show_unmount = show_eject = show_start = show_stop = show_detect_media = FALSE; for (l = selection; l != NULL && (show_mount || show_unmount || show_eject || show_start || show_stop || show_detect_media); l = l->next) { 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), view->details->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); 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); action = g_action_map_lookup_action (G_ACTION_MAP (view_action_group), "properties"); g_simple_action_set_enabled (G_SIMPLE_ACTION (action), show_properties); 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 && view->details->templates_present); /* Ask the clipboard */ g_object_ref (view); /* Need to keep the object alive until we get the reply */ gtk_clipboard_request_targets (nautilus_clipboard_get (GTK_WIDGET (view)), clipboard_targets_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_view_is_empty (view)); /* Toolbar menu actions */ g_action_group_change_action_state (view_action_group, "show-hidden-files", g_variant_new_boolean (view->details->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_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_view_can_zoom_out (view)); action = g_action_map_lookup_action (G_ACTION_MAP (view_action_group), "zoom-default"); g_simple_action_set_enabled (G_SIMPLE_ACTION (action), nautilus_view_supports_zooming (view)); } /* 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_view_update_actions_state (NautilusView *view) { g_assert(NAUTILUS_IS_VIEW (view)); NAUTILUS_VIEW_CLASS (G_OBJECT_GET_CLASS (view))->update_actions_state (view); } static void update_selection_menu (NautilusView *view) { GList *selection, *l; NautilusFile *file; gint selection_count; gboolean show_app, show_run; gboolean item_opens_in_view; gchar *item_label; GAppInfo *app; GIcon *app_icon; GMenuItem *menu_item; 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 (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"); nautilus_gmenu_add_item_in_submodel (view->details->selection_menu, menu_item, "new-folder-with-selection-section", FALSE); g_object_unref (menu_item); g_free (item_label); /* Open With menu item */ show_app = show_run = item_opens_in_view = selection_count != 0; for (l = selection; l != NULL; l = l->next) { NautilusFile *file; file = NAUTILUS_FILE (selection->data); if (!nautilus_mime_file_opens_in_external_app (file)) { show_app = FALSE; } if (!nautilus_mime_file_launches (file)) { show_run = FALSE; } if (!nautilus_mime_file_opens_in_view (file)) { item_opens_in_view = FALSE; } if (!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); } char *escaped_app; if (app != NULL) { 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 { 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); nautilus_gmenu_add_item_in_submodel (view->details->selection_menu, menu_item, "open-with-default-application-section", FALSE); 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) { 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"); nautilus_gmenu_add_item_in_submodel (view->details->selection_menu, menu_item, "drive-section", FALSE); 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"); nautilus_gmenu_add_item_in_submodel (view->details->selection_menu, menu_item, "drive-section", FALSE); g_object_unref (menu_item); } update_scripts_menu (view); } static void update_background_menu (NautilusView *view) { if (nautilus_view_supports_creating_files (view) && !showing_recent_directory (view)) update_templates_menu (view); } static void real_update_context_menus (NautilusView *view) { g_clear_object (&view->details->background_menu); g_clear_object (&view->details->selection_menu); GtkBuilder *builder; builder = gtk_builder_new_from_resource ("/org/gnome/nautilus/nautilus-view-context-menus.xml"); view->details->background_menu = g_object_ref (G_MENU (gtk_builder_get_object (builder, "background-menu"))); view->details->selection_menu = g_object_ref (G_MENU (gtk_builder_get_object (builder, "selection-menu"))); g_object_unref (builder); update_selection_menu (view); update_background_menu (view); update_extensions_menus (view); nautilus_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_view_update_actions_state */ void nautilus_view_update_context_menus (NautilusView *view) { g_assert(NAUTILUS_IS_VIEW (view)); NAUTILUS_VIEW_CLASS (G_OBJECT_GET_CLASS (view))->update_context_menus (view); } static void nautilus_view_reset_view_menu (NautilusView *view) { GActionGroup *view_action_group; GVariant *variant; GVariantIter iter; gboolean show_sort_trash, show_sort_search, show_sort_access, show_sort_modification, enable_sort; const gchar *hint; /* Allow actions from the current view to be activated through * the view menu and action menu of the toolbar */ view_action_group = nautilus_view_get_action_group (view); gtk_widget_insert_action_group (GTK_WIDGET (view), "view", G_ACTION_GROUP (view_action_group)); gtk_widget_set_visible (view->details->visible_columns, g_action_group_has_action (view_action_group, "visible-columns")); enable_sort = g_action_group_get_action_enabled (view_action_group, "sort"); show_sort_trash = show_sort_search = show_sort_modification = show_sort_access = FALSE; gtk_widget_set_visible (view->details->sort_menu, enable_sort); if (enable_sort) { variant = g_action_group_get_action_state_hint (view_action_group, "sort"); g_variant_iter_init (&iter, variant); while (g_variant_iter_next (&iter, "&s", &hint)) { if (g_strcmp0 (hint, "trash-time") == 0) show_sort_trash = TRUE; if (g_strcmp0 (hint, "search-relevance") == 0) show_sort_search = TRUE; } g_variant_unref (variant); } gtk_widget_set_visible (view->details->sort_trash_time, show_sort_trash); gtk_widget_set_visible (view->details->sort_search_relevance, show_sort_search); variant = g_action_group_get_action_state (view_action_group, "zoom-to-level"); gtk_adjustment_set_value (view->details->zoom_adjustment, g_variant_get_int32 (variant)); g_variant_unref (variant); } /* 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_view_update_actions_state */ void nautilus_view_update_toolbar_menus (NautilusView *view) { g_assert (NAUTILUS_IS_VIEW (view)); /* Don't update after destroy (#349551), * or if we are not active. */ if (view->details->slot == NULL || !view->details->active) { return; } nautilus_view_reset_view_menu (view); nautilus_view_update_actions_state (view); } /** * nautilus_view_pop_up_selection_context_menu * * Pop up a context menu appropriate to the selected items. * @view: NautilusView of interest. * @event: The event that triggered this context menu. * **/ void nautilus_view_pop_up_selection_context_menu (NautilusView *view, GdkEventButton *event) { g_assert (NAUTILUS_IS_VIEW (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); update_context_menu_position_from_event (view, event); nautilus_pop_up_context_menu (GTK_WIDGET (view), view->details->selection_menu, event); } /** * nautilus_view_pop_up_background_context_menu * * Pop up a context menu appropriate to the view globally at the last right click location. * @view: NautilusView of interest. * **/ void nautilus_view_pop_up_background_context_menu (NautilusView *view, GdkEventButton *event) { g_assert (NAUTILUS_IS_VIEW (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); update_context_menu_position_from_event (view, event); nautilus_pop_up_context_menu (GTK_WIDGET (view), view->details->background_menu, event); } static void schedule_update_context_menus (NautilusView *view) { g_assert (NAUTILUS_IS_VIEW (view)); /* Don't schedule updates after destroy (#349551), * or if we are not active. */ if (view->details->slot == NULL || !view->details->active) { return; } /* Schedule a menu update with the current update interval */ if (view->details->update_context_menus_timeout_id == 0) { view->details->update_context_menus_timeout_id = g_timeout_add (view->details->update_interval, update_context_menus_timeout_callback, view); } } static void remove_update_status_idle_callback (NautilusView *view) { if (view->details->update_status_idle_id != 0) { g_source_remove (view->details->update_status_idle_id); view->details->update_status_idle_id = 0; } } static gboolean update_status_idle_callback (gpointer data) { NautilusView *view; view = NAUTILUS_VIEW (data); nautilus_view_display_selection_info (view); view->details->update_status_idle_id = 0; return FALSE; } static void schedule_update_status (NautilusView *view) { g_assert (NAUTILUS_IS_VIEW (view)); /* Make sure we haven't already destroyed it */ if (view->details->slot == NULL) { return; } if (view->details->loading) { /* Don't update status bar while loading the dir */ return; } if (view->details->update_status_idle_id == 0) { view->details->update_status_idle_id = g_idle_add_full (G_PRIORITY_DEFAULT_IDLE - 20, update_status_idle_callback, view, NULL); } } /** * nautilus_view_notify_selection_changed: * * Notify this view that the selection has changed. This is normally * called only by subclasses. * @view: NautilusView whose selection has changed. * **/ void nautilus_view_notify_selection_changed (NautilusView *view) { GtkWindow *window; GList *selection; g_return_if_fail (NAUTILUS_IS_VIEW (view)); selection = nautilus_view_get_selection (view); window = nautilus_view_get_containing_window (view); DEBUG_FILES (selection, "Selection changed in window %p", window); nautilus_file_list_free (selection); view->details->selection_was_removed = FALSE; /* Schedule a display of the new selection. */ if (view->details->display_selection_idle_id == 0) { view->details->display_selection_idle_id = g_idle_add (display_selection_info_idle_callback, view); } if (view->details->batching_selection_level != 0) { view->details->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) { NautilusView *view = NAUTILUS_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: NautilusView whose location will be changed. * @uri: A string representing the uri to switch to. * **/ static void load_directory (NautilusView *view, NautilusDirectory *directory) { NautilusDirectory *old_directory; NautilusFile *old_file; NautilusFileAttributes attributes; g_assert (NAUTILUS_IS_VIEW (view)); g_assert (NAUTILUS_IS_DIRECTORY (directory)); nautilus_profile_start (NULL); nautilus_view_stop_loading (view); g_signal_emit (view, signals[CLEAR], 0); view->details->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 (view->details->subdirectory_list != NULL) { nautilus_view_remove_subdirectory (view, view->details->subdirectory_list->data); } old_directory = view->details->model; nautilus_directory_ref (directory); view->details->model = directory; nautilus_directory_unref (old_directory); old_file = view->details->directory_as_file; view->details->directory_as_file = nautilus_directory_get_corresponding_file (directory); nautilus_file_unref (old_file); /* 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; view->details->metadata_for_directory_as_file_pending = TRUE; view->details->metadata_for_files_in_directory_pending = TRUE; nautilus_file_call_when_ready (view->details->directory_as_file, attributes, metadata_for_directory_as_file_ready_callback, view); nautilus_directory_call_when_ready (view->details->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 (view->details->directory_as_file, &view->details->directory_as_file, attributes); view->details->file_changed_handler_id = g_signal_connect (view->details->directory_as_file, "changed", G_CALLBACK (file_changed_callback), view); nautilus_profile_end (NULL); } static void finish_loading (NautilusView *view) { NautilusFileAttributes attributes; 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"); check_empty_states (view); if (nautilus_directory_are_all_files_seen (view->details->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. */ view->details->done_loading_handler_id = g_signal_connect (view->details->model, "done-loading", G_CALLBACK (done_loading_callback), view); view->details->load_error_handler_id = g_signal_connect (view->details->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_LINK_INFO | NAUTILUS_FILE_ATTRIBUTE_MOUNT | NAUTILUS_FILE_ATTRIBUTE_EXTENSION_INFO; nautilus_directory_file_monitor_add (view->details->model, &view->details->model, view->details->show_hidden_files, attributes, files_added_callback, view); view->details->files_added_handler_id = g_signal_connect (view->details->model, "files-added", G_CALLBACK (files_added_callback), view); view->details->files_changed_handler_id = g_signal_connect (view->details->model, "files-changed", G_CALLBACK (files_changed_callback), view); nautilus_profile_end (NULL); } static void finish_loading_if_all_metadata_loaded (NautilusView *view) { if (!view->details->metadata_for_directory_as_file_pending && !view->details->metadata_for_files_in_directory_pending) { finish_loading (view); } } static void metadata_for_directory_as_file_ready_callback (NautilusFile *file, gpointer callback_data) { NautilusView *view; view = callback_data; g_assert (NAUTILUS_IS_VIEW (view)); g_assert (view->details->directory_as_file == file); g_assert (view->details->metadata_for_directory_as_file_pending); nautilus_profile_start (NULL); view->details->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) { NautilusView *view; view = callback_data; g_assert (NAUTILUS_IS_VIEW (view)); g_assert (view->details->model == directory); g_assert (view->details->metadata_for_files_in_directory_pending); nautilus_profile_start (NULL); view->details->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 (NautilusView *view, guint *id) { disconnect_handler (G_OBJECT (view->details->model), id); } static void disconnect_directory_as_file_handler (NautilusView *view, guint *id) { disconnect_handler (G_OBJECT (view->details->directory_as_file), id); } static void disconnect_model_handlers (NautilusView *view) { if (view->details->model == NULL) { return; } disconnect_directory_handler (view, &view->details->files_added_handler_id); disconnect_directory_handler (view, &view->details->files_changed_handler_id); disconnect_directory_handler (view, &view->details->done_loading_handler_id); disconnect_directory_handler (view, &view->details->load_error_handler_id); disconnect_directory_as_file_handler (view, &view->details->file_changed_handler_id); nautilus_file_cancel_call_when_ready (view->details->directory_as_file, metadata_for_directory_as_file_ready_callback, view); nautilus_directory_cancel_callback (view->details->model, metadata_for_files_in_directory_ready_callback, view); nautilus_directory_file_monitor_remove (view->details->model, &view->details->model); nautilus_file_monitor_remove (view->details->directory_as_file, &view->details->directory_as_file); } static void nautilus_view_select_file (NautilusView *view, NautilusFile *file) { GList file_list; file_list.data = file; file_list.next = NULL; file_list.prev = NULL; nautilus_view_call_set_selection (view, &file_list); } static gboolean remove_all (gpointer key, gpointer value, gpointer callback_data) { return TRUE; } /** * nautilus_view_stop_loading: * * Stop the current ongoing process, such as switching to a new uri. * @view: NautilusView in question. * **/ void nautilus_view_stop_loading (NautilusView *view) { g_return_if_fail (NAUTILUS_IS_VIEW (view)); disconnect_model_handlers (view); if (view->details->model) { nautilus_directory_unref (view->details->model); view->details->model = NULL; } unschedule_display_of_pending_files (view); reset_update_interval (view); /* Free extra undisplayed files */ file_and_directory_list_free (view->details->new_added_files); view->details->new_added_files = NULL; file_and_directory_list_free (view->details->new_changed_files); view->details->new_changed_files = NULL; g_hash_table_foreach_remove (view->details->non_ready_files, remove_all, NULL); file_and_directory_list_free (view->details->old_added_files); view->details->old_added_files = NULL; file_and_directory_list_free (view->details->old_changed_files); view->details->old_changed_files = NULL; g_list_free_full (view->details->pending_selection, g_object_unref); view->details->pending_selection = NULL; done_loading (view, FALSE); } gboolean nautilus_view_is_editable (NautilusView *view) { NautilusDirectory *directory; directory = nautilus_view_get_model (view); if (directory != NULL) { return nautilus_directory_is_editable (directory); } return TRUE; } static gboolean real_is_read_only (NautilusView *view) { NautilusFile *file; if (!nautilus_view_is_editable (view)) { return TRUE; } file = nautilus_view_get_directory_as_file (view); if (file != NULL) { return !nautilus_file_can_write (file); } return FALSE; } /** * nautilus_view_should_show_file * * Returns whether or not this file should be displayed based on * current filtering options. */ gboolean nautilus_view_should_show_file (NautilusView *view, NautilusFile *file) { return nautilus_file_should_show (file, view->details->show_hidden_files, view->details->show_foreign_files); } static gboolean real_using_manual_layout (NautilusView *view) { g_return_val_if_fail (NAUTILUS_IS_VIEW (view), FALSE); return FALSE; } void nautilus_view_ignore_hidden_file_preferences (NautilusView *view) { g_return_if_fail (view->details->model == NULL); if (view->details->ignore_hidden_file_preferences) { return; } view->details->show_hidden_files = FALSE; view->details->ignore_hidden_file_preferences = TRUE; } void nautilus_view_set_show_foreign (NautilusView *view, gboolean show_foreign) { view->details->show_foreign_files = show_foreign; } char * nautilus_view_get_uri (NautilusView *view) { g_return_val_if_fail (NAUTILUS_IS_VIEW (view), NULL); if (view->details->model == NULL) { return NULL; } return nautilus_directory_get_uri (view->details->model); } void nautilus_view_move_copy_items (NautilusView *view, const GList *item_uris, GArray *relative_item_points, const char *target_uri, int copy_action, int x, int y) { NautilusFile *target_file; g_assert (relative_item_points == NULL || relative_item_points->len == 0 || g_list_length ((GList *)item_uris) == relative_item_points->len); /* add the drop location to the icon offsets */ offset_drop_points (relative_item_points, x, y); target_file = nautilus_file_get_existing_by_uri (target_uri); /* special-case "command:" here instead of starting a move/copy */ if (target_file != NULL && nautilus_file_is_launcher (target_file)) { nautilus_file_unref (target_file); nautilus_launch_desktop_file ( gtk_widget_get_screen (GTK_WIDGET (view)), target_uri, item_uris, nautilus_view_get_containing_window (view)); return; } else 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, relative_item_points, target_uri, copy_action, GTK_WIDGET (view), copy_move_done_callback, pre_copy_move (view)); } static void nautilus_view_trash_state_changed_callback (NautilusTrashMonitor *trash_monitor, gboolean state, gpointer callback_data) { NautilusView *view; view = (NautilusView *) callback_data; g_assert (NAUTILUS_IS_VIEW (view)); schedule_update_context_menus (view); } void nautilus_view_start_batching_selection_changes (NautilusView *view) { g_return_if_fail (NAUTILUS_IS_VIEW (view)); ++view->details->batching_selection_level; view->details->selection_changed_while_batched = FALSE; } void nautilus_view_stop_batching_selection_changes (NautilusView *view) { g_return_if_fail (NAUTILUS_IS_VIEW (view)); g_return_if_fail (view->details->batching_selection_level > 0); if (--view->details->batching_selection_level == 0) { if (view->details->selection_changed_while_batched) { nautilus_view_notify_selection_changed (view); } } } static GArray * real_get_selected_icon_locations (NautilusView *view) { /* By default, just return an empty list. */ return g_array_new (FALSE, TRUE, sizeof (GdkPoint)); } static void nautilus_view_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) { NautilusView *view = NAUTILUS_VIEW (object); switch (prop_id) { case PROP_ICON: g_value_set_object (value, nautilus_view_get_icon (view)); break; case PROP_VIEW_WIDGET: g_value_set_object (value, nautilus_view_get_view_widget (view)); break; default: g_assert_not_reached (); } } static void nautilus_view_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) { NautilusView *directory_view; NautilusWindowSlot *slot; directory_view = NAUTILUS_VIEW (object); switch (prop_id) { case PROP_WINDOW_SLOT: g_assert (directory_view->details->slot == NULL); slot = NAUTILUS_WINDOW_SLOT (g_value_get_object (value)); directory_view->details->slot = slot; g_signal_connect_object (directory_view->details->slot, "active", G_CALLBACK (slot_active), directory_view, 0); g_signal_connect_object (directory_view->details->slot, "inactive", G_CALLBACK (slot_inactive), directory_view, 0); break; case PROP_SUPPORTS_ZOOMING: directory_view->details->supports_zooming = g_value_get_boolean (value); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } gboolean nautilus_view_handle_scroll_event (NautilusView *directory_view, GdkEventScroll *event) { static gdouble total_delta_y = 0; gdouble delta_x, delta_y; if (event->state & GDK_CONTROL_MASK) { switch (event->direction) { case GDK_SCROLL_UP: /* Zoom In */ nautilus_view_bump_zoom_level (directory_view, 1); return TRUE; case GDK_SCROLL_DOWN: /* Zoom Out */ nautilus_view_bump_zoom_level (directory_view, -1); return TRUE; case GDK_SCROLL_SMOOTH: gdk_event_get_scroll_deltas ((const GdkEvent *) 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_view_bump_zoom_level (directory_view, -1); return TRUE; } else if (total_delta_y <= - 1) { total_delta_y = 0; /* emulate scroll up */ nautilus_view_bump_zoom_level (directory_view, 1); return TRUE; } else { /* eat event */ return TRUE; } case GDK_SCROLL_LEFT: case GDK_SCROLL_RIGHT: break; default: g_assert_not_reached (); } } return FALSE; } /* handle Shift+Scroll, which will cause a zoom-in/out */ static gboolean nautilus_view_scroll_event (GtkWidget *widget, GdkEventScroll *event) { NautilusView *directory_view; directory_view = NAUTILUS_VIEW (widget); if (nautilus_view_handle_scroll_event (directory_view, event)) { return TRUE; } return FALSE; } static void action_reload_enabled_changed (GActionGroup *action_group, gchar *action_name, gboolean enabled, NautilusView *view) { gtk_widget_set_visible (view->details->reload, enabled); } static void action_stop_enabled_changed (GActionGroup *action_group, gchar *action_name, gboolean enabled, NautilusView *view) { gtk_widget_set_visible (view->details->stop, enabled); } static void nautilus_view_parent_set (GtkWidget *widget, GtkWidget *old_parent) { NautilusWindow *window; NautilusView *view; GtkWidget *parent; view = NAUTILUS_VIEW (widget); parent = gtk_widget_get_parent (widget); window = nautilus_view_get_window (view); g_assert (parent == NULL || old_parent == NULL); if (GTK_WIDGET_CLASS (nautilus_view_parent_class)->parent_set != NULL) { GTK_WIDGET_CLASS (nautilus_view_parent_class)->parent_set (widget, old_parent); } if (view->details->stop_signal_handler > 0) { g_signal_handler_disconnect (window, view->details->stop_signal_handler); view->details->stop_signal_handler = 0; } if (view->details->reload_signal_handler > 0) { g_signal_handler_disconnect (window, view->details->reload_signal_handler); view->details->reload_signal_handler = 0; } if (parent != NULL) { g_assert (old_parent == NULL); if (view->details->slot == nautilus_window_get_active_slot (window)) { view->details->active = TRUE; gtk_widget_insert_action_group (GTK_WIDGET (nautilus_view_get_window (view)), "view", G_ACTION_GROUP (view->details->view_action_group)); } view->details->stop_signal_handler = g_signal_connect (window, "action-enabled-changed::stop", G_CALLBACK (action_stop_enabled_changed), view); view->details->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); gtk_widget_insert_action_group (GTK_WIDGET (nautilus_view_get_window (view)), "view", NULL); } } static void nautilus_view_class_init (NautilusViewClass *klass) { GObjectClass *oclass; GtkWidgetClass *widget_class; widget_class = GTK_WIDGET_CLASS (klass); oclass = G_OBJECT_CLASS (klass); oclass->finalize = nautilus_view_finalize; oclass->get_property = nautilus_view_get_property; oclass->set_property = nautilus_view_set_property; widget_class->destroy = nautilus_view_destroy; widget_class->scroll_event = nautilus_view_scroll_event; widget_class->parent_set = nautilus_view_parent_set; widget_class->grab_focus = nautilus_view_grab_focus; g_type_class_add_private (klass, sizeof (NautilusViewDetails)); signals[ADD_FILE] = g_signal_new ("add-file", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (NautilusViewClass, add_file), NULL, NULL, g_cclosure_marshal_generic, G_TYPE_NONE, 2, NAUTILUS_TYPE_FILE, NAUTILUS_TYPE_DIRECTORY); signals[BEGIN_FILE_CHANGES] = g_signal_new ("begin-file-changes", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (NautilusViewClass, 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 (NautilusViewClass, 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 (NautilusViewClass, 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 (NautilusViewClass, 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 (NautilusViewClass, 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 (NautilusViewClass, 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 (NautilusViewClass, 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_selected_icon_locations = real_get_selected_icon_locations; klass->is_read_only = real_is_read_only; klass->can_rename_file = can_rename_file; klass->get_backing_uri = real_get_backing_uri; klass->using_manual_layout = real_using_manual_layout; klass->get_window = nautilus_view_get_window; klass->update_context_menus = real_update_context_menus; klass->update_actions_state = real_update_actions_state; copied_files_atom = gdk_atom_intern ("x-special/gnome-copied-files", FALSE); properties[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); properties[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); properties[PROP_ICON] = g_param_spec_object ("icon", "Icon", "The icon that represents the view", G_TYPE_ICON, G_PARAM_READABLE); properties[PROP_VIEW_WIDGET] = g_param_spec_object ("view-widget", "View widget", "The view's widget that will appear under the view menu button", GTK_TYPE_WIDGET, G_PARAM_READABLE); g_object_class_install_properties (oclass, NUM_PROPERTIES, properties); } static void nautilus_view_init (NautilusView *view) { GtkBuilder *builder; AtkObject *atk_object; NautilusDirectory *scripts_directory; NautilusDirectory *templates_directory; gchar *templates_uri; GApplication *app; const gchar *open_accels[] = { "o", "Down", NULL }; nautilus_profile_start (NULL); view->details = G_TYPE_INSTANCE_GET_PRIVATE (view, NAUTILUS_TYPE_VIEW, NautilusViewDetails); /* NautilusFloatingBar listen to its parent's 'enter-notify-event' signal * and GtkOverlay doesn't have it enabled by default, so we have to add them * here. */ gtk_widget_add_events (GTK_WIDGET (view), GDK_ENTER_NOTIFY_MASK | GDK_LEAVE_NOTIFY_MASK); /* View menu */ builder = gtk_builder_new_from_resource ("/org/gnome/nautilus/nautilus-toolbar-view-menu.xml"); view->details->view_menu_widget = g_object_ref (gtk_builder_get_object (builder, "view_menu_widget")); view->details->zoom_level_scale = GTK_WIDGET (gtk_builder_get_object (builder, "zoom_level_scale")); view->details->zoom_adjustment = GTK_ADJUSTMENT (gtk_builder_get_object (builder, "zoom_adjustment")); view->details->sort_menu = GTK_WIDGET (gtk_builder_get_object (builder, "sort_menu")); view->details->sort_trash_time = GTK_WIDGET (gtk_builder_get_object (builder, "sort_trash_time")); view->details->sort_search_relevance = GTK_WIDGET (gtk_builder_get_object (builder, "sort_search_relevance")); view->details->visible_columns = GTK_WIDGET (gtk_builder_get_object (builder, "visible_columns")); view->details->reload = GTK_WIDGET (gtk_builder_get_object (builder, "reload")); view->details->stop = GTK_WIDGET (gtk_builder_get_object (builder, "stop")); g_signal_connect (view->details->zoom_level_scale, "value-changed", G_CALLBACK (zoom_level_changed), view); g_object_unref (builder); /* Scrolled Window */ view->details->scrolled_window = gtk_scrolled_window_new (NULL, NULL); gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (view->details->scrolled_window), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC); gtk_widget_show (view->details->scrolled_window); g_signal_connect_swapped (view->details->scrolled_window, "scroll-event", G_CALLBACK (nautilus_view_scroll_event), view); gtk_container_add (GTK_CONTAINER (view), view->details->scrolled_window); /* Empty states */ builder = gtk_builder_new_from_resource ("/org/gnome/nautilus/nautilus-no-search-results.ui"); view->details->no_search_results_widget = GTK_WIDGET (gtk_builder_get_object (builder, "no_search_results")); gtk_overlay_add_overlay (GTK_OVERLAY (view), view->details->no_search_results_widget); g_object_unref (builder); builder = gtk_builder_new_from_resource ("/org/gnome/nautilus/nautilus-folder-is-empty.ui"); view->details->folder_is_empty_widget = GTK_WIDGET (gtk_builder_get_object (builder, "folder_is_empty")); gtk_overlay_add_overlay (GTK_OVERLAY (view), view->details->folder_is_empty_widget); g_object_unref (builder); /* Floating bar */ view->details->floating_bar = nautilus_floating_bar_new (NULL, NULL, FALSE); gtk_widget_set_halign (view->details->floating_bar, GTK_ALIGN_END); gtk_widget_set_valign (view->details->floating_bar, GTK_ALIGN_END); gtk_overlay_add_overlay (GTK_OVERLAY (view), view->details->floating_bar); g_signal_connect (view->details->floating_bar, "action", G_CALLBACK (floating_bar_action_cb), view); /* Default to true; desktop-icon-view sets to false */ view->details->show_foreign_files = TRUE; view->details->non_ready_files = g_hash_table_new_full (file_and_directory_hash, file_and_directory_equal, (GDestroyNotify)file_and_directory_free, 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); view->details->sort_directories_first = g_settings_get_boolean (nautilus_preferences, NAUTILUS_PREFERENCES_SORT_DIRECTORIES_FIRST); view->details->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_view_trash_state_changed_callback), view, 0); /* React to clipboard changes */ g_signal_connect_object (nautilus_clipboard_monitor_get (), "clipboard-changed", G_CALLBACK (clipboard_changed_callback), view, 0); /* 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 (nautilus_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); /* 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")); view->details->view_action_group = G_ACTION_GROUP (g_simple_action_group_new ()); g_action_map_add_action_entries (G_ACTION_MAP (view->details->view_action_group), view_entries, G_N_ELEMENTS (view_entries), view); gtk_widget_insert_action_group (GTK_WIDGET (view), "view", G_ACTION_GROUP (view->details->view_action_group)); app = g_application_get_default (); /* Toolbar menu */ nautilus_application_add_accelerator (app, "view.zoom-in", "plus"); nautilus_application_add_accelerator (app, "view.zoom-out", "minus"); nautilus_application_add_accelerator (app, "view.show-hidden-files", "h"); /* Background menu */ nautilus_application_add_accelerator (app, "view.select-all", "a"); nautilus_application_add_accelerator (app, "view.paste", "v"); /* Selection menu */ gtk_application_set_accels_for_action (GTK_APPLICATION (app), "view.open-with-default-application", open_accels); nautilus_application_add_accelerator (app, "view.open-item-new-tab", "t"); nautilus_application_add_accelerator (app, "view.open-item-new-window", "w"); nautilus_application_add_accelerator (app, "view.move-to-trash", "Delete"); nautilus_application_add_accelerator (app, "view.delete-from-trash", "Delete"); nautilus_application_add_accelerator (app, "view.delete-permanently-shortcut", "Delete"); nautilus_application_add_accelerator (app, "view.properties", "i"); nautilus_application_add_accelerator (app, "view.open-item-location", "o"); nautilus_application_add_accelerator (app, "view.rename", "F2"); nautilus_application_add_accelerator (app, "view.cut", "x"); nautilus_application_add_accelerator (app, "view.copy", "c"); nautilus_application_add_accelerator (app, "view.new-folder", "n"); /* Only accesible by shorcuts */ nautilus_application_add_accelerator (app, "view.select-pattern", "s"); nautilus_application_add_accelerator (app, "view.zoom-default", "0"); nautilus_application_add_accelerator (app, "view.invert-selection", "i"); nautilus_application_add_accelerator (app, "view.open-file-and-close-window", "Down"); /* Show a warning dialog to inform the user that the shorcut for move to trash * changed */ nautilus_application_add_accelerator (app, "view.show-move-to-trash-shortcut-changed-dialog", "Delete"); nautilus_profile_end (NULL); } NautilusView * nautilus_view_new (const gchar *id, NautilusWindowSlot *slot) { NautilusView *view = NULL; if (g_strcmp0 (id, NAUTILUS_CANVAS_VIEW_ID) == 0) { view = nautilus_canvas_view_new (slot); } else if (g_strcmp0 (id, NAUTILUS_LIST_VIEW_ID) == 0) { view = nautilus_list_view_new (slot); } else if (g_strcmp0 (id, NAUTILUS_DESKTOP_CANVAS_VIEW_ID) == 0) { view = nautilus_desktop_canvas_view_new (slot); } #if ENABLE_EMPTY_VIEW else if (g_strcmp0 (id, NAUTILUS_EMPTY_VIEW_ID) == 0) { view = nautilus_empty_view_new (slot); } #endif if (view == NULL) { g_critical ("Unknown view type ID: %s", id); } else if (g_object_is_floating (view)) { g_object_ref_sink (view); } return view; }