diff options
Diffstat (limited to '')
-rw-r--r-- | src/nautilus-window-slot.c | 3417 |
1 files changed, 3417 insertions, 0 deletions
diff --git a/src/nautilus-window-slot.c b/src/nautilus-window-slot.c new file mode 100644 index 0000000..64dc5fc --- /dev/null +++ b/src/nautilus-window-slot.c @@ -0,0 +1,3417 @@ +/* + * nautilus-window-slot.c: Nautilus window slot + * + * Copyright (C) 2008 Free Software Foundation, 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 <http://www.gnu.org/licenses/>. + * + * Author: Christian Neumair <cneumair@gnome.org> + */ + +#include "config.h" + +#include "nautilus-window-slot.h" + +#include "nautilus-application.h" +#include "nautilus-bookmark.h" +#include "nautilus-bookmark-list.h" +#include "nautilus-files-view.h" +#include "nautilus-mime-actions.h" +#include "nautilus-places-view.h" +#include "nautilus-query-editor.h" +#include "nautilus-special-location-bar.h" +#include "nautilus-toolbar.h" +#include "nautilus-view.h" +#include "nautilus-window.h" +#include "nautilus-x-content-bar.h" + +#include <glib/gi18n.h> + +#include "nautilus-file.h" +#include "nautilus-file-utilities.h" +#include "nautilus-global-preferences.h" +#include "nautilus-module.h" +#include "nautilus-monitor.h" +#include "nautilus-profile.h" +#include "nautilus-ui-utilities.h" +#include <eel/eel-vfs-extensions.h> + +enum +{ + PROP_ACTIVE = 1, + PROP_WINDOW, + PROP_ICON_NAME, + PROP_TOOLBAR_MENU_SECTIONS, + PROP_EXTENSIONS_BACKGROUND_MENU, + PROP_TEMPLATES_MENU, + PROP_LOADING, + PROP_SEARCHING, + PROP_SELECTION, + PROP_LOCATION, + PROP_TOOLTIP, + PROP_ALLOW_STOP, + PROP_TITLE, + NUM_PROPERTIES +}; + +#define FILE_SHARING_SCHEMA_ID "org.gnome.desktop.file-sharing" + +struct _NautilusWindowSlot +{ + GtkBox parent_instance; + + NautilusWindow *window; + + gboolean active : 1; + guint loading : 1; + + /* slot contains + * 1) an vbox containing extra_location_widgets + * 2) the view + */ + GtkWidget *extra_location_widgets; + + /* Slot actions */ + GActionGroup *slot_action_group; + + /* Current location. */ + GFile *location; + gchar *title; + + /* Viewed file */ + NautilusView *content_view; + NautilusView *new_content_view; + NautilusFile *viewed_file; + gboolean viewed_file_seen; + gboolean viewed_file_in_trash; + + /* Information about bookmarks and history list */ + NautilusBookmark *current_location_bookmark; + NautilusBookmark *last_location_bookmark; + GList *back_list; + GList *forward_list; + + /* Query editor */ + NautilusQueryEditor *query_editor; + NautilusQuery *pending_search_query; + gulong qe_changed_id; + gulong qe_cancel_id; + gulong qe_activated_id; + gulong qe_focus_view_id; + + GtkLabel *search_info_label; + GtkRevealer *search_info_label_revealer; + + /* Load state */ + GCancellable *find_mount_cancellable; + /* It could be either the view is loading the files or the search didn't + * finish. Used for showing a spinner to provide feedback to the user. */ + gboolean allow_stop; + gboolean needs_reload; + + /* New location. */ + GFile *pending_location; + NautilusLocationChangeType location_change_type; + guint location_change_distance; + char *pending_scroll_to; + GList *pending_selection; + NautilusFile *pending_file_to_activate; + NautilusFile *determine_view_file; + GCancellable *mount_cancellable; + GError *mount_error; + gboolean tried_mount; + gint view_mode_before_search; + gint view_mode_before_places; + + /* Menus */ + GMenuModel *extensions_background_menu; + GMenuModel *templates_menu; + + /* View bindings */ + GBinding *searching_binding; + GBinding *selection_binding; + GBinding *extensions_background_menu_binding; + GBinding *templates_menu_binding; + gboolean searching; + GList *selection; +}; + +G_DEFINE_TYPE (NautilusWindowSlot, nautilus_window_slot, GTK_TYPE_BOX); + +static GParamSpec *properties[NUM_PROPERTIES] = { NULL, }; + +static void nautilus_window_slot_force_reload (NautilusWindowSlot *self); +static void change_view (NautilusWindowSlot *self); +static void hide_query_editor (NautilusWindowSlot *self); +static void nautilus_window_slot_sync_actions (NautilusWindowSlot *self); +static void nautilus_window_slot_connect_new_content_view (NautilusWindowSlot *self); +static void nautilus_window_slot_disconnect_content_view (NautilusWindowSlot *self); +static gboolean nautilus_window_slot_content_view_matches (NautilusWindowSlot *self, + guint id); +static NautilusView *nautilus_window_slot_get_view_for_location (NautilusWindowSlot *self, + GFile *location); +static void nautilus_window_slot_set_content_view (NautilusWindowSlot *self, + guint id); +static void nautilus_window_slot_set_loading (NautilusWindowSlot *self, + gboolean loading); +char *nautilus_window_slot_get_location_uri (NautilusWindowSlot *self); +static void nautilus_window_slot_set_search_visible (NautilusWindowSlot *self, + gboolean visible); +static gboolean nautilus_window_slot_get_search_visible (NautilusWindowSlot *self); +static void nautilus_window_slot_set_location (NautilusWindowSlot *self, + GFile *location); +static void update_search_information (NautilusWindowSlot *self); +static void real_set_extensions_background_menu (NautilusWindowSlot *self, + GMenuModel *menu); +static GMenuModel *real_get_extensions_background_menu (NautilusWindowSlot *self); +static void real_set_templates_menu (NautilusWindowSlot *self, + GMenuModel *menu); +static GMenuModel *real_get_templates_menu (NautilusWindowSlot *self); +static void nautilus_window_slot_setup_extra_location_widgets (NautilusWindowSlot *self); + +void +free_navigation_state (gpointer data) +{ + NautilusNavigationState *navigation_state = data; + + g_list_free_full (navigation_state->back_list, g_object_unref); + g_list_free_full (navigation_state->forward_list, g_object_unref); + nautilus_file_unref (navigation_state->file); + g_clear_object (&navigation_state->current_location_bookmark); + + g_free (navigation_state); +} + +void +nautilus_window_slot_restore_navigation_state (NautilusWindowSlot *self, + NautilusNavigationState *data) +{ + self->back_list = g_steal_pointer (&data->back_list); + + self->forward_list = g_steal_pointer (&data->forward_list); + + self->view_mode_before_search = data->view_before_search; + + g_set_object (&self->current_location_bookmark, data->current_location_bookmark); + + self->location_change_type = NAUTILUS_LOCATION_CHANGE_RELOAD; +} + +NautilusNavigationState * +nautilus_window_slot_get_navigation_state (NautilusWindowSlot *self) +{ + NautilusNavigationState *data; + GList *back_list; + GList *forward_list; + + if (self->location == NULL) + { + return NULL; + } + + back_list = g_list_copy_deep (self->back_list, + (GCopyFunc) g_object_ref, + NULL); + forward_list = g_list_copy_deep (self->forward_list, + (GCopyFunc) g_object_ref, + NULL); + + /* This data is used to restore a tab after it was closed. + * In order to do that we need to keep the history, what was + * the view mode before search and a reference to the file. + * A GFile isn't enough, as the NautilusFile also keeps a + * reference to the search directory */ + data = g_new0 (NautilusNavigationState, 1); + data->back_list = back_list; + data->forward_list = forward_list; + data->file = nautilus_file_get (self->location); + data->view_before_search = self->view_mode_before_search; + g_set_object (&data->current_location_bookmark, self->current_location_bookmark); + + return data; +} + +static NautilusView * +nautilus_window_slot_get_view_for_location (NautilusWindowSlot *self, + GFile *location) +{ + g_autoptr (NautilusFile) file = NULL; + NautilusView *view; + guint view_id; + + file = nautilus_file_get (location); + view = NULL; + view_id = NAUTILUS_VIEW_INVALID_ID; + + if (nautilus_file_is_other_locations (file)) + { + view = NAUTILUS_VIEW (nautilus_places_view_new ()); + + /* Save the current view, so we can go back after places view */ + if (NAUTILUS_IS_FILES_VIEW (self->content_view)) + { + self->view_mode_before_places = nautilus_view_get_view_id (self->content_view); + } + + return view; + } + + /* If we are in search, try to use by default list view. */ + if (nautilus_file_is_in_search (file)) + { + /* If it's already set, is because we already made the change to search mode, + * so the view mode of the current view will be the one search is using, + * which is not the one we are interested in */ + if (self->view_mode_before_search == NAUTILUS_VIEW_INVALID_ID && + NAUTILUS_IS_FILES_VIEW (self->content_view)) + { + self->view_mode_before_search = nautilus_view_get_view_id (self->content_view); + } + view_id = g_settings_get_enum (nautilus_preferences, NAUTILUS_PREFERENCES_SEARCH_VIEW); + } + else if (self->content_view != NULL) + { + /* If there is already a view, just use the view mode that it's currently using, or + * if we were on search before, use what we were using before entering + * search mode */ + if (self->view_mode_before_search != NAUTILUS_VIEW_INVALID_ID) + { + view_id = self->view_mode_before_search; + self->view_mode_before_search = NAUTILUS_VIEW_INVALID_ID; + } + else if (NAUTILUS_IS_PLACES_VIEW (self->content_view)) + { + view_id = self->view_mode_before_places; + self->view_mode_before_places = NAUTILUS_VIEW_INVALID_ID; + } + else + { + view_id = nautilus_view_get_view_id (self->content_view); + } + } + + /* If there is not previous view in this slot, use the default view mode + * from preferences */ + if (view_id == NAUTILUS_VIEW_INVALID_ID) + { + view_id = g_settings_get_enum (nautilus_preferences, NAUTILUS_PREFERENCES_DEFAULT_FOLDER_VIEWER); + } + if (view_id == NAUTILUS_VIEW_INVALID_ID) + { + g_warning ("Invalid value stored for 'default-folder-viewer' key for " + "the 'org.gnome.nautilus.preferences' schemas. Installed " + "schemas may be outdated. Falling back to 'list-view'."); + view_id = NAUTILUS_VIEW_LIST_ID; + } + + /* Try to reuse the current view */ + if (nautilus_window_slot_content_view_matches (self, view_id)) + { + view = self->content_view; + } + else + { + view = NAUTILUS_VIEW (nautilus_files_view_new (view_id, self)); + } + + return view; +} + +static gboolean +nautilus_window_slot_content_view_matches (NautilusWindowSlot *self, + guint id) +{ + if (self->content_view == NULL) + { + return FALSE; + } + + if (id != NAUTILUS_VIEW_INVALID_ID) + { + return nautilus_view_get_view_id (self->content_view) == id; + } + else + { + return FALSE; + } +} + +static void +update_search_visible (NautilusWindowSlot *self) +{ + NautilusQuery *query; + NautilusView *view; + + view = nautilus_window_slot_get_current_view (self); + /* If we changed location just to another search location, for example, + * when changing the query, just keep the search visible. + * Make sure the search is visible though, since we could be returning + * from a previous search location when using the history */ + if (nautilus_view_is_searching (view)) + { + nautilus_window_slot_set_search_visible (self, TRUE); + return; + } + + query = nautilus_query_editor_get_query (self->query_editor); + if (query) + { + /* If the view is not searching, but search is visible, and the + * query is empty, we don't hide it. Some users enable the search + * and then change locations, then they search. */ + if (!nautilus_query_is_empty (query)) + { + nautilus_window_slot_set_search_visible (self, FALSE); + } + } + + if (self->pending_search_query) + { + nautilus_window_slot_search (self, g_object_ref (self->pending_search_query)); + } +} + +static void +nautilus_window_slot_sync_actions (NautilusWindowSlot *self) +{ + NautilusView *view; + GAction *action; + GVariant *variant; + + if (!nautilus_window_slot_get_active (self)) + { + return; + } + + if (self->content_view == NULL || self->new_content_view != NULL) + { + return; + } + + /* Check if we need to close the search or show search after changing the location. + * Needs to be done after the change has been done, if not, a loop happens, + * because setting the search enabled or not actually opens a location */ + update_search_visible (self); + + /* Files view mode */ + view = nautilus_window_slot_get_current_view (self); + action = g_action_map_lookup_action (G_ACTION_MAP (self->slot_action_group), "files-view-mode"); + g_simple_action_set_enabled (G_SIMPLE_ACTION (action), NAUTILUS_IS_FILES_VIEW (view)); + if (g_action_get_enabled (action)) + { + variant = g_variant_new_uint32 (nautilus_view_get_view_id (view)); + g_action_change_state (action, variant); + } + action = g_action_map_lookup_action (G_ACTION_MAP (self->slot_action_group), "files-view-mode-toggle"); + g_simple_action_set_enabled (G_SIMPLE_ACTION (action), NAUTILUS_IS_FILES_VIEW (view)); +} + +static void +query_editor_cancel_callback (NautilusQueryEditor *editor, + NautilusWindowSlot *self) +{ + nautilus_window_slot_set_search_visible (self, FALSE); +} + +static void +query_editor_activated_callback (NautilusQueryEditor *editor, + NautilusWindowSlot *self) +{ + if (self->content_view != NULL) + { + if (NAUTILUS_IS_FILES_VIEW (self->content_view)) + { + nautilus_files_view_activate_selection (NAUTILUS_FILES_VIEW (self->content_view)); + } + } +} + +static void +query_editor_focus_view_callback (NautilusQueryEditor *editor, + NautilusWindowSlot *self) +{ + if (self->content_view != NULL) + { + gtk_widget_grab_focus (GTK_WIDGET (self->content_view)); + } +} + +static void +query_editor_changed_callback (NautilusQueryEditor *editor, + NautilusQuery *query, + gboolean reload, + NautilusWindowSlot *self) +{ + NautilusView *view; + + view = nautilus_window_slot_get_current_view (self); + + nautilus_view_set_search_query (view, query); + nautilus_window_slot_open_location_full (self, nautilus_view_get_location (view), 0, NULL); +} + +static void +hide_query_editor (NautilusWindowSlot *self) +{ + NautilusView *view; + + view = nautilus_window_slot_get_current_view (self); + + g_clear_signal_handler (&self->qe_changed_id, self->query_editor); + g_clear_signal_handler (&self->qe_cancel_id, self->query_editor); + g_clear_signal_handler (&self->qe_activated_id, self->query_editor); + g_clear_signal_handler (&self->qe_focus_view_id, self->query_editor); + + nautilus_query_editor_set_query (self->query_editor, NULL); + + if (nautilus_view_is_searching (view)) + { + g_autolist (NautilusFile) selection = NULL; + + selection = nautilus_view_get_selection (view); + + nautilus_view_set_search_query (view, NULL); + nautilus_window_slot_open_location_full (self, + nautilus_view_get_location (view), + 0, + selection); + } + + if (nautilus_window_slot_get_active (self)) + { + gtk_widget_grab_focus (GTK_WIDGET (self->window)); + } +} + +static GFile * +nautilus_window_slot_get_current_location (NautilusWindowSlot *self) +{ + if (self->pending_location != NULL) + { + return self->pending_location; + } + + if (self->location != NULL) + { + return self->location; + } + + return NULL; +} + +static void +show_query_editor (NautilusWindowSlot *self) +{ + NautilusView *view; + + view = nautilus_window_slot_get_current_view (self); + if (view == NULL) + { + return; + } + + if (nautilus_view_is_searching (view)) + { + NautilusQuery *query; + + query = nautilus_view_get_search_query (view); + + if (query != NULL) + { + nautilus_query_editor_set_query (self->query_editor, query); + } + } + + gtk_widget_grab_focus (GTK_WIDGET (self->query_editor)); + + if (self->qe_changed_id == 0) + { + self->qe_changed_id = + g_signal_connect (self->query_editor, "changed", + G_CALLBACK (query_editor_changed_callback), self); + } + if (self->qe_cancel_id == 0) + { + self->qe_cancel_id = + g_signal_connect (self->query_editor, "cancel", + G_CALLBACK (query_editor_cancel_callback), self); + } + if (self->qe_activated_id == 0) + { + self->qe_activated_id = + g_signal_connect (self->query_editor, "activated", + G_CALLBACK (query_editor_activated_callback), self); + } + if (self->qe_focus_view_id == 0) + { + self->qe_focus_view_id = + g_signal_connect (self->query_editor, "focus-view", + G_CALLBACK (query_editor_focus_view_callback), self); + } +} + +static void +nautilus_window_slot_set_search_visible (NautilusWindowSlot *self, + gboolean visible) +{ + GAction *action; + + action = g_action_map_lookup_action (G_ACTION_MAP (self->slot_action_group), + "search-visible"); + g_action_change_state (action, g_variant_new_boolean (visible)); +} + +static gboolean +nautilus_window_slot_get_search_visible (NautilusWindowSlot *self) +{ + GAction *action; + GVariant *state; + gboolean searching; + + action = g_action_map_lookup_action (G_ACTION_MAP (self->slot_action_group), + "search-visible"); + state = g_action_get_state (action); + searching = g_variant_get_boolean (state); + + g_variant_unref (state); + + return searching; +} + +void +nautilus_window_slot_search (NautilusWindowSlot *self, + NautilusQuery *query) +{ + NautilusView *view; + + g_clear_object (&self->pending_search_query); + + view = nautilus_window_slot_get_current_view (self); + /* We could call this when the location is still being checked in the + * window slot. For that, save the search we want to do for once we have + * a view set up */ + if (view) + { + nautilus_window_slot_set_search_visible (self, TRUE); + nautilus_query_editor_set_query (self->query_editor, query); + nautilus_view_set_search_query (view, query); + } + else + { + self->pending_search_query = g_object_ref (query); + } +} + +gboolean +nautilus_window_slot_handle_event (NautilusWindowSlot *self, + GtkEventControllerKey *controller, + guint keyval, + GdkModifierType state) +{ + gboolean retval; + GAction *action; + + retval = FALSE; + action = g_action_map_lookup_action (G_ACTION_MAP (self->slot_action_group), + "search-visible"); + + if (keyval == GDK_KEY_Escape || + keyval == GDK_KEY_BackSpace) + { + g_autoptr (GVariant) action_state = NULL; + + action_state = g_action_get_state (action); + + if (!g_variant_get_boolean (action_state)) + { + return GDK_EVENT_PROPAGATE; + } + else if (keyval == GDK_KEY_Escape) + { + nautilus_window_slot_set_search_visible (self, FALSE); + return GDK_EVENT_STOP; + } + } + + /* If the action is not enabled, don't try to handle search */ + if (g_action_get_enabled (action)) + { + retval = nautilus_query_editor_handle_event (self->query_editor, + controller, + keyval, + state); + } + + if (retval) + { + nautilus_window_slot_set_search_visible (self, TRUE); + } + + return retval; +} + +static void +nautilus_window_slot_remove_extra_location_widgets (NautilusWindowSlot *self) +{ + GtkWidget *widget; + while ((widget = gtk_widget_get_first_child (self->extra_location_widgets)) != NULL) + { + gtk_box_remove (GTK_BOX (self->extra_location_widgets), widget); + } +} + +static void +nautilus_window_slot_add_extra_location_widget (NautilusWindowSlot *self, + GtkWidget *widget) +{ + gtk_box_append (GTK_BOX (self->extra_location_widgets), widget); + gtk_widget_show (self->extra_location_widgets); +} + +static void +nautilus_window_slot_set_searching (NautilusWindowSlot *self, + gboolean searching) +{ + self->searching = searching; + g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_SEARCHING]); +} + +static void +nautilus_window_slot_set_selection (NautilusWindowSlot *self, + GList *selection) +{ + self->selection = selection; + g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_SELECTION]); +} + +static void +real_set_extensions_background_menu (NautilusWindowSlot *self, + GMenuModel *menu) +{ + g_set_object (&self->extensions_background_menu, menu); +} + +static void +real_set_templates_menu (NautilusWindowSlot *self, + GMenuModel *menu) +{ + g_set_object (&self->templates_menu, menu); +} + +static void +nautilus_window_slot_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec) +{ + NautilusWindowSlot *self = NAUTILUS_WINDOW_SLOT (object); + + switch (property_id) + { + case PROP_ACTIVE: + { + nautilus_window_slot_set_active (self, g_value_get_boolean (value)); + } + break; + + case PROP_WINDOW: + { + nautilus_window_slot_set_window (self, g_value_get_object (value)); + } + break; + + case PROP_LOCATION: + { + nautilus_window_slot_set_location (self, g_value_get_object (value)); + } + break; + + case PROP_SEARCHING: + { + nautilus_window_slot_set_searching (self, g_value_get_boolean (value)); + } + break; + + case PROP_EXTENSIONS_BACKGROUND_MENU: + { + real_set_extensions_background_menu (self, g_value_get_object (value)); + } + break; + + case PROP_TEMPLATES_MENU: + { + real_set_templates_menu (self, g_value_get_object (value)); + } + break; + + case PROP_SELECTION: + { + nautilus_window_slot_set_selection (self, g_value_get_pointer (value)); + } + break; + + default: + { + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + } + break; + } +} + +static GMenuModel * +real_get_extensions_background_menu (NautilusWindowSlot *self) +{ + return self->extensions_background_menu; +} + +GMenuModel * +nautilus_window_slot_get_extensions_background_menu (NautilusWindowSlot *self) +{ + GMenuModel *menu = NULL; + + g_object_get (self, "extensions-background-menu", &menu, NULL); + + return menu; +} + +static GMenuModel * +real_get_templates_menu (NautilusWindowSlot *self) +{ + return self->templates_menu; +} + +GMenuModel * +nautilus_window_slot_get_templates_menu (NautilusWindowSlot *self) +{ + GMenuModel *menu = NULL; + + g_object_get (self, "templates-menu", &menu, NULL); + + return menu; +} + +static void +nautilus_window_slot_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec) +{ + NautilusWindowSlot *self = NAUTILUS_WINDOW_SLOT (object); + switch (property_id) + { + case PROP_ACTIVE: + { + g_value_set_boolean (value, nautilus_window_slot_get_active (self)); + } + break; + + case PROP_WINDOW: + { + g_value_set_object (value, self->window); + } + break; + + case PROP_ICON_NAME: + { + g_value_set_static_string (value, nautilus_window_slot_get_icon_name (self)); + } + break; + + case PROP_TOOLBAR_MENU_SECTIONS: + { + g_value_set_object (value, nautilus_window_slot_get_toolbar_menu_sections (self)); + } + break; + + case PROP_EXTENSIONS_BACKGROUND_MENU: + { + g_value_set_object (value, real_get_extensions_background_menu (self)); + } + break; + + case PROP_TEMPLATES_MENU: + { + g_value_set_object (value, real_get_templates_menu (self)); + } + break; + + case PROP_LOADING: + { + g_value_set_boolean (value, nautilus_window_slot_get_loading (self)); + } + break; + + case PROP_LOCATION: + { + g_value_set_object (value, nautilus_window_slot_get_current_location (self)); + } + break; + + case PROP_SEARCHING: + { + g_value_set_boolean (value, nautilus_window_slot_get_searching (self)); + } + break; + + case PROP_TOOLTIP: + { + g_value_set_static_string (value, nautilus_window_slot_get_tooltip (self)); + } + break; + + case PROP_ALLOW_STOP: + { + g_value_set_boolean (value, nautilus_window_slot_get_allow_stop (self)); + } + break; + + case PROP_TITLE: + { + g_value_set_string (value, nautilus_window_slot_get_title (self)); + } + break; + + default: + { + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + } + break; + } +} + +gboolean +nautilus_window_slot_get_searching (NautilusWindowSlot *self) +{ + return self->searching; +} + +GList * +nautilus_window_slot_get_selection (NautilusWindowSlot *self) +{ + return self->selection; +} + +static void +nautilus_window_slot_constructed (GObject *object) +{ + NautilusWindowSlot *self = NAUTILUS_WINDOW_SLOT (object); + GtkWidget *extras_vbox; + GtkStyleContext *style_context; + + G_OBJECT_CLASS (nautilus_window_slot_parent_class)->constructed (object); + + gtk_orientable_set_orientation (GTK_ORIENTABLE (self), + GTK_ORIENTATION_VERTICAL); + gtk_widget_show (GTK_WIDGET (self)); + + extras_vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0); + self->extra_location_widgets = extras_vbox; + gtk_box_append (GTK_BOX (self), extras_vbox); + gtk_widget_show (extras_vbox); + + self->query_editor = NAUTILUS_QUERY_EDITOR (nautilus_query_editor_new ()); + /* We want to keep alive the query editor betwen additions and removals on the + * UI, specifically when the toolbar adds or removes it */ + g_object_ref_sink (self->query_editor); + gtk_widget_show (GTK_WIDGET (self->query_editor)); + + self->search_info_label = GTK_LABEL (gtk_label_new (NULL)); + self->search_info_label_revealer = GTK_REVEALER (gtk_revealer_new ()); + + gtk_revealer_set_child (GTK_REVEALER (self->search_info_label_revealer), + GTK_WIDGET (self->search_info_label)); + gtk_box_append (GTK_BOX (self), + GTK_WIDGET (self->search_info_label_revealer)); + + gtk_widget_show (GTK_WIDGET (self->search_info_label)); + gtk_widget_show (GTK_WIDGET (self->search_info_label_revealer)); + + style_context = gtk_widget_get_style_context (GTK_WIDGET (self->search_info_label)); + gtk_style_context_add_class (style_context, "search-information"); + + g_object_bind_property (self, "location", + self->query_editor, "location", + G_BINDING_DEFAULT); + + self->title = g_strdup (_("Loading…")); + g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_TITLE]); +} + +static void +action_search_visible (GSimpleAction *action, + GVariant *state, + gpointer user_data) +{ + NautilusWindowSlot *self; + GVariant *current_state; + + self = NAUTILUS_WINDOW_SLOT (user_data); + current_state = g_action_get_state (G_ACTION (action)); + if (g_variant_get_boolean (current_state) != g_variant_get_boolean (state)) + { + g_simple_action_set_state (action, state); + + if (g_variant_get_boolean (state)) + { + show_query_editor (self); + nautilus_window_slot_set_searching (self, TRUE); + } + else + { + hide_query_editor (self); + nautilus_window_slot_set_searching (self, FALSE); + } + + update_search_information (self); + } + + g_variant_unref (current_state); +} + +static void +change_files_view_mode (NautilusWindowSlot *self, + guint view_id) +{ + const gchar *preferences_key; + + if (!nautilus_window_slot_content_view_matches (self, view_id)) + { + nautilus_window_slot_set_content_view (self, view_id); + } + preferences_key = nautilus_view_is_searching (nautilus_window_slot_get_current_view (self)) ? + NAUTILUS_PREFERENCES_SEARCH_VIEW : + NAUTILUS_PREFERENCES_DEFAULT_FOLDER_VIEWER; + + g_settings_set_enum (nautilus_preferences, preferences_key, view_id); +} + +static void +action_files_view_mode_toggle (GSimpleAction *action, + GVariant *value, + gpointer user_data) +{ + NautilusWindowSlot *self; + guint current_view_id; + + self = NAUTILUS_WINDOW_SLOT (user_data); + if (!NAUTILUS_IS_FILES_VIEW (self->content_view)) + { + return; + } + + current_view_id = nautilus_view_get_view_id (self->content_view); + if (current_view_id == NAUTILUS_VIEW_LIST_ID) + { + change_files_view_mode (self, NAUTILUS_VIEW_GRID_ID); + } + else + { + change_files_view_mode (self, NAUTILUS_VIEW_LIST_ID); + } +} + +static void +action_files_view_mode (GSimpleAction *action, + GVariant *value, + gpointer user_data) +{ + NautilusWindowSlot *self; + guint view_id; + + view_id = g_variant_get_uint32 (value); + self = NAUTILUS_WINDOW_SLOT (user_data); + + if (!NAUTILUS_IS_FILES_VIEW (nautilus_window_slot_get_current_view (self))) + { + return; + } + + change_files_view_mode (self, view_id); + + g_simple_action_set_state (action, value); +} + +const GActionEntry slot_entries[] = +{ + { "files-view-mode", NULL, "u", "uint32 " G_STRINGIFY (NAUTILUS_VIEW_INVALID_ID), action_files_view_mode }, + { "files-view-mode-toggle", action_files_view_mode_toggle }, + { "search-visible", NULL, NULL, "false", action_search_visible }, +}; + +static void +update_search_information (NautilusWindowSlot *self) +{ + GFile *location; + + if (!nautilus_window_slot_get_searching (self)) + { + gtk_revealer_set_reveal_child (self->search_info_label_revealer, FALSE); + + return; + } + + location = nautilus_window_slot_get_current_location (self); + + if (location) + { + g_autoptr (NautilusFile) file = NULL; + gchar *label; + g_autofree gchar *uri = NULL; + + file = nautilus_file_get (location); + label = NULL; + uri = g_file_get_uri (location); + + if (nautilus_file_is_other_locations (file)) + { + label = _("Searching locations only"); + } + else if (g_str_has_prefix (uri, "network://")) + { + label = _("Searching network locations only"); + } + else if (nautilus_file_is_remote (file) && + location_settings_search_get_recursive_for_location (location) == NAUTILUS_QUERY_RECURSIVE_NEVER) + { + label = _("Remote location — only searching the current folder"); + } + else if (location_settings_search_get_recursive_for_location (location) == NAUTILUS_QUERY_RECURSIVE_NEVER) + { + label = _("Only searching the current folder"); + } + + gtk_label_set_label (self->search_info_label, label); + gtk_revealer_set_reveal_child (self->search_info_label_revealer, + label != NULL); + } +} + +static void +recursive_search_preferences_changed (GSettings *settings, + gchar *key, + gpointer callback_data) +{ + NautilusWindowSlot *self; + + self = callback_data; + + update_search_information (self); +} + +static void +remove_old_trash_files_preferences_changed (GSettings *settings, + gchar *key, + gpointer callback_data) +{ + NautilusWindowSlot *self; + GFile *location; + g_autoptr (NautilusDirectory) directory = NULL; + + g_assert (NAUTILUS_IS_WINDOW_SLOT (callback_data)); + + self = NAUTILUS_WINDOW_SLOT (callback_data); + location = nautilus_window_slot_get_current_location (self); + directory = nautilus_directory_get (location); + + if (nautilus_directory_is_in_trash (directory)) + { + if (g_settings_get_boolean (gnome_privacy_preferences, "remove-old-trash-files")) + { + nautilus_window_slot_setup_extra_location_widgets (self); + } + else + { + nautilus_window_slot_remove_extra_location_widgets (self); + } + } +} + +static void +nautilus_window_slot_init (NautilusWindowSlot *self) +{ + GApplication *app; + const gchar *search_visible_accels[] = + { + "<control>f", + "Search", + NULL + }; + + app = g_application_get_default (); + + g_signal_connect_object (nautilus_preferences, + "changed::recursive-search", + G_CALLBACK (recursive_search_preferences_changed), + self, 0); + + g_signal_connect_object (gnome_privacy_preferences, + "changed::remove-old-trash-files", + G_CALLBACK (remove_old_trash_files_preferences_changed), + self, 0); + + self->slot_action_group = G_ACTION_GROUP (g_simple_action_group_new ()); + g_action_map_add_action_entries (G_ACTION_MAP (self->slot_action_group), + slot_entries, + G_N_ELEMENTS (slot_entries), + self); + gtk_widget_insert_action_group (GTK_WIDGET (self), + "slot", + G_ACTION_GROUP (self->slot_action_group)); + + nautilus_application_set_accelerator (app, + "slot.files-view-mode(uint32 " G_STRINGIFY (NAUTILUS_VIEW_LIST_ID) ")", + "<control>1"); + nautilus_application_set_accelerator (app, + "slot.files-view-mode(uint32 " G_STRINGIFY (NAUTILUS_VIEW_GRID_ID) ")", + "<control>2"); + nautilus_application_set_accelerators (app, "slot.search-visible", search_visible_accels); + + self->view_mode_before_search = NAUTILUS_VIEW_INVALID_ID; + self->view_mode_before_places = NAUTILUS_VIEW_INVALID_ID; +} + +#define DEBUG_FLAG NAUTILUS_DEBUG_WINDOW +#include "nautilus-debug.h" + +static void begin_location_change (NautilusWindowSlot *slot, + GFile *location, + GFile *previous_location, + GList *new_selection, + NautilusLocationChangeType type, + guint distance, + const char *scroll_pos); +static void free_location_change (NautilusWindowSlot *self); +static void end_location_change (NautilusWindowSlot *self); +static void got_file_info_for_view_selection_callback (NautilusFile *file, + gpointer callback_data); +static gboolean setup_view (NautilusWindowSlot *self, + NautilusView *view); +static void load_new_location (NautilusWindowSlot *slot, + GFile *location, + GList *selection, + NautilusFile *file_to_activate, + gboolean tell_current_content_view, + gboolean tell_new_content_view); + +void +nautilus_window_slot_open_location_full (NautilusWindowSlot *self, + GFile *location, + NautilusOpenFlags flags, + GList *new_selection) +{ + GFile *old_location; + g_autolist (NautilusFile) old_selection = NULL; + + old_selection = NULL; + old_location = nautilus_window_slot_get_location (self); + + if (self->content_view) + { + old_selection = nautilus_view_get_selection (self->content_view); + } + if (old_location && g_file_equal (old_location, location) && + nautilus_file_selection_equal (old_selection, new_selection)) + { + goto done; + } + + begin_location_change (self, location, old_location, new_selection, + NAUTILUS_LOCATION_CHANGE_STANDARD, 0, NULL); + +done: + nautilus_profile_end (NULL); +} + +static GList * +check_select_old_location_containing_folder (GList *new_selection, + GFile *location, + GFile *previous_location) +{ + GFile *from_folder, *parent; + + /* If there is no new selection and the new location is + * a (grand)parent of the old location then we automatically + * select the folder the previous location was in */ + if (new_selection == NULL && previous_location != NULL && + g_file_has_prefix (previous_location, location)) + { + from_folder = g_object_ref (previous_location); + parent = g_file_get_parent (from_folder); + while (parent != NULL && !g_file_equal (parent, location)) + { + g_object_unref (from_folder); + from_folder = parent; + parent = g_file_get_parent (from_folder); + } + + if (parent != NULL) + { + new_selection = g_list_prepend (NULL, nautilus_file_get (from_folder)); + g_object_unref (parent); + } + + g_object_unref (from_folder); + } + + return new_selection; +} + +static void +check_force_reload (GFile *location, + NautilusLocationChangeType type) +{ + NautilusDirectory *directory; + NautilusFile *file; + gboolean force_reload; + + /* The code to force a reload is here because if we do it + * after determining an initial view (in the components), then + * we end up fetching things twice. + */ + directory = nautilus_directory_get (location); + file = nautilus_file_get (location); + + if (type == NAUTILUS_LOCATION_CHANGE_RELOAD) + { + force_reload = TRUE; + } + else + { + force_reload = !g_file_is_native (location); + } + + /* We need to invalidate file attributes as well due to how mounting works + * in the window slot and to avoid other caching issues. + * Read handle_mount_if_needed for one example */ + if (force_reload) + { + nautilus_file_invalidate_all_attributes (file); + nautilus_directory_force_reload (directory); + } + + nautilus_directory_unref (directory); + nautilus_file_unref (file); +} + +static void +save_scroll_position_for_history (NautilusWindowSlot *self) +{ + /* Set current_bookmark scroll pos */ + if (self->current_location_bookmark != NULL && + self->content_view != NULL && + NAUTILUS_IS_FILES_VIEW (self->content_view)) + { + char *current_pos; + + current_pos = nautilus_files_view_get_first_visible_file (NAUTILUS_FILES_VIEW (self->content_view)); + nautilus_bookmark_set_scroll_pos (self->current_location_bookmark, current_pos); + g_free (current_pos); + } +} + +/* + * begin_location_change + * + * Change a window slot's location. + * @window: The NautilusWindow whose location should be changed. + * @location: A url specifying the location to load + * @previous_location: The url that was previously shown in the window that initialized the change, if any + * @new_selection: The initial selection to present after loading the location + * @type: Which type of location change is this? Standard, back, forward, or reload? + * @distance: If type is back or forward, the index into the back or forward chain. If + * type is standard or reload, this is ignored, and must be 0. + * @scroll_pos: The file to scroll to when the location is loaded. + * + * This is the core function for changing the location of a window. Every change to the + * location begins here. + */ +static void +begin_location_change (NautilusWindowSlot *self, + GFile *location, + GFile *previous_location, + GList *new_selection, + NautilusLocationChangeType type, + guint distance, + const char *scroll_pos) +{ + g_assert (self != NULL); + g_assert (location != NULL); + g_assert (type == NAUTILUS_LOCATION_CHANGE_BACK + || type == NAUTILUS_LOCATION_CHANGE_FORWARD + || distance == 0); + + nautilus_profile_start (NULL); + + /* Avoid to update status from the current view in our async calls */ + nautilus_window_slot_disconnect_content_view (self); + /* We are going to change the location, so make sure we stop any loading + * or searching of the previous view, so we avoid to be slow */ + nautilus_window_slot_stop_loading (self); + + nautilus_window_slot_set_allow_stop (self, TRUE); + + new_selection = check_select_old_location_containing_folder (new_selection, location, previous_location); + + g_assert (self->pending_location == NULL); + + self->pending_location = g_object_ref (location); + self->location_change_type = type; + self->location_change_distance = distance; + self->tried_mount = FALSE; + self->pending_selection = nautilus_file_list_copy (new_selection); + + self->pending_scroll_to = g_strdup (scroll_pos); + + check_force_reload (location, type); + + save_scroll_position_for_history (self); + + /* Get the info needed to make decisions about how to open the new location */ + self->determine_view_file = nautilus_file_get (location); + g_assert (self->determine_view_file != NULL); + + nautilus_file_call_when_ready (self->determine_view_file, + NAUTILUS_FILE_ATTRIBUTE_INFO | + NAUTILUS_FILE_ATTRIBUTE_MOUNT, + got_file_info_for_view_selection_callback, + self); + + nautilus_profile_end (NULL); +} + +static void +nautilus_window_slot_set_location (NautilusWindowSlot *self, + GFile *location) +{ + GFile *old_location; + + if (self->location && + g_file_equal (location, self->location)) + { + /* The location name could be updated even if the location + * wasn't changed. This is the case for a search. + */ + nautilus_window_slot_update_title (self); + return; + } + + old_location = self->location; + self->location = g_object_ref (location); + + if (nautilus_window_slot_get_active (self)) + { + nautilus_window_sync_location_widgets (self->window); + } + + nautilus_window_slot_update_title (self); + + if (old_location) + { + g_object_unref (old_location); + } + + g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_LOCATION]); +} + +static void +viewed_file_changed_callback (NautilusFile *file, + NautilusWindowSlot *self) +{ + GFile *new_location; + gboolean is_in_trash, was_in_trash; + + g_assert (NAUTILUS_IS_FILE (file)); + g_assert (NAUTILUS_IS_WINDOW_SLOT (self)); + + g_assert (file == self->viewed_file); + + if (!nautilus_file_is_not_yet_confirmed (file)) + { + self->viewed_file_seen = TRUE; + } + + was_in_trash = self->viewed_file_in_trash; + + self->viewed_file_in_trash = is_in_trash = nautilus_file_is_in_trash (file); + + if (nautilus_file_is_gone (file) || (is_in_trash && !was_in_trash)) + { + if (self->viewed_file_seen) + { + GFile *go_to_file; + GFile *parent; + GFile *location; + GMount *mount; + + parent = NULL; + location = nautilus_file_get_location (file); + + if (g_file_is_native (location)) + { + mount = nautilus_get_mounted_mount_for_root (location); + + if (mount == NULL) + { + parent = g_file_get_parent (location); + } + + g_clear_object (&mount); + } + + if (parent != NULL) + { + /* auto-show existing parent */ + go_to_file = nautilus_find_existing_uri_in_hierarchy (parent); + } + else + { + go_to_file = g_file_new_for_path (g_get_home_dir ()); + } + + nautilus_window_slot_open_location_full (self, go_to_file, 0, NULL); + + g_clear_object (&parent); + g_object_unref (go_to_file); + g_object_unref (location); + } + } + else + { + new_location = nautilus_file_get_location (file); + nautilus_window_slot_set_location (self, new_location); + g_object_unref (new_location); + } +} + +static void +nautilus_window_slot_go_home (NautilusWindowSlot *self, + NautilusOpenFlags flags) +{ + GFile *home; + + g_return_if_fail (NAUTILUS_IS_WINDOW_SLOT (self)); + + home = g_file_new_for_path (g_get_home_dir ()); + nautilus_window_slot_open_location_full (self, home, flags, NULL); + g_object_unref (home); +} + +static void +nautilus_window_slot_set_viewed_file (NautilusWindowSlot *self, + NautilusFile *file) +{ + NautilusFileAttributes attributes; + + if (self->viewed_file == file) + { + return; + } + + nautilus_file_ref (file); + + if (self->viewed_file != NULL) + { + g_signal_handlers_disconnect_by_func (self->viewed_file, + G_CALLBACK (viewed_file_changed_callback), + self); + nautilus_file_monitor_remove (self->viewed_file, + self); + } + + if (file != NULL) + { + attributes = NAUTILUS_FILE_ATTRIBUTE_INFO; + nautilus_file_monitor_add (file, self, attributes); + + g_signal_connect_object (file, "changed", + G_CALLBACK (viewed_file_changed_callback), self, 0); + } + + nautilus_file_unref (self->viewed_file); + self->viewed_file = file; +} + +typedef struct +{ + GCancellable *cancellable; + NautilusWindowSlot *slot; +} MountNotMountedData; + +static void +mount_not_mounted_callback (GObject *source_object, + GAsyncResult *res, + gpointer user_data) +{ + MountNotMountedData *data; + NautilusWindowSlot *self; + GError *error; + GCancellable *cancellable; + + data = user_data; + self = data->slot; + cancellable = data->cancellable; + g_free (data); + + if (g_cancellable_is_cancelled (cancellable)) + { + /* Cancelled, don't call back */ + g_object_unref (cancellable); + return; + } + + self->mount_cancellable = NULL; + + self->determine_view_file = nautilus_file_get (self->pending_location); + + error = NULL; + if (!g_file_mount_enclosing_volume_finish (G_FILE (source_object), res, &error)) + { + self->mount_error = error; + got_file_info_for_view_selection_callback (self->determine_view_file, self); + self->mount_error = NULL; + g_error_free (error); + } + else + { + nautilus_file_invalidate_all_attributes (self->determine_view_file); + nautilus_file_call_when_ready (self->determine_view_file, + NAUTILUS_FILE_ATTRIBUTE_INFO | + NAUTILUS_FILE_ATTRIBUTE_MOUNT, + got_file_info_for_view_selection_callback, + self); + } + + g_object_unref (cancellable); +} + +static void +nautilus_window_slot_display_view_selection_failure (NautilusWindow *window, + NautilusFile *file, + GFile *location, + GError *error) +{ + char *error_message; + char *detail_message; + char *scheme_string; + char *file_path; + + /* Some sort of failure occurred. How 'bout we tell the user? */ + + error_message = g_strdup (_("Oops! Something went wrong.")); + detail_message = NULL; + if (error == NULL) + { + if (nautilus_file_is_directory (file)) + { + detail_message = g_strdup (_("Unable to display the contents of this folder.")); + } + else + { + detail_message = g_strdup (_("This location doesn’t appear to be a folder.")); + } + } + else if (error->domain == G_IO_ERROR) + { + switch (error->code) + { + case G_IO_ERROR_NOT_FOUND: + { + file_path = g_file_get_path (location); + if (file_path != NULL) + { + detail_message = g_strdup_printf (_("Unable to find “%s”. Please check the spelling and try again."), + file_path); + } + else + { + detail_message = g_strdup (_("Unable to find the requested file. Please check the spelling and try again.")); + } + g_free (file_path); + } + break; + + case G_IO_ERROR_NOT_SUPPORTED: + { + scheme_string = g_file_get_uri_scheme (location); + if (scheme_string != NULL) + { + detail_message = g_strdup_printf (_("“%s” locations are not supported."), + scheme_string); + } + else + { + detail_message = g_strdup (_("Unable to handle this kind of location.")); + } + g_free (scheme_string); + } + break; + + case G_IO_ERROR_NOT_MOUNTED: + { + detail_message = g_strdup (_("Unable to access the requested location.")); + } + break; + + case G_IO_ERROR_PERMISSION_DENIED: + { + detail_message = g_strdup (_("Don’t have permission to access the requested location.")); + } + break; + + case G_IO_ERROR_HOST_NOT_FOUND: + { + /* This case can be hit for user-typed strings like "foo" due to + * the code that guesses web addresses when there's no initial "/". + * But this case is also hit for legitimate web addresses when + * the proxy is set up wrong. + */ + detail_message = g_strdup (_("Unable to find the requested location. Please check the spelling or the network settings.")); + } + break; + + case G_IO_ERROR_CONNECTION_REFUSED: + { + /* This case can be hit when server application is not installed + * or is inactive in the system user is trying to connect to. + */ + detail_message = g_strdup (_("The server has refused the connection. Typically this means that the firewall is blocking access or that the remote service is not running.")); + } + break; + + case G_IO_ERROR_CANCELLED: + case G_IO_ERROR_FAILED_HANDLED: + { + goto done; + } + + default: + { + } + break; + } + } + + if (detail_message == NULL) + { + detail_message = g_strdup_printf (_("Unhandled error message: %s"), error->message); + } + + show_dialog (error_message, detail_message, GTK_WINDOW (window), GTK_MESSAGE_ERROR); + +done: + g_free (error_message); + g_free (detail_message); +} + +/* FIXME: This works in the folowwing way. begin_location_change tries to get the + * information of the file directly. + * If the nautilus file finds that there is an error trying to get its + * information and the error match that the file is not mounted, it sets an + * internal attribute with the error then we try to mount it here. + * + * However, files are cached, and if the file doesn't get finalized in a location + * change, because needs to be in the navigation history or is a bookmark, and the + * file is not the root of the mount point, which is tracked by a volume monitor, + * and it gets unmounted aftwerwards, the file doesn't realize it's unmounted, and + * therefore this trick to open an unmounted file will fail the next time the user + * tries to open. + * For that, we need to always invalidate the file attributes when a location is + * changed, which is done in check_force_reload. + * A better way would be to make sure any children of the mounted root gets + * akwnoledge by it either by adding a reference to its parent volume monitor + * or with another solution. */ +static gboolean +handle_mount_if_needed (NautilusWindowSlot *self, + NautilusFile *file) +{ + NautilusWindow *window; + GMountOperation *mount_op; + MountNotMountedData *data; + GFile *location; + GError *error = NULL; + gboolean needs_mount_handling = FALSE; + + window = nautilus_window_slot_get_window (self); + if (self->mount_error) + { + error = g_error_copy (self->mount_error); + } + else if (nautilus_file_get_file_info_error (file) != NULL) + { + error = g_error_copy (nautilus_file_get_file_info_error (file)); + } + + if (error && error->domain == G_IO_ERROR && error->code == G_IO_ERROR_NOT_MOUNTED && + !self->tried_mount) + { + self->tried_mount = TRUE; + + mount_op = gtk_mount_operation_new (GTK_WINDOW (window)); + g_mount_operation_set_password_save (mount_op, G_PASSWORD_SAVE_FOR_SESSION); + location = nautilus_file_get_location (file); + data = g_new0 (MountNotMountedData, 1); + data->cancellable = g_cancellable_new (); + data->slot = self; + self->mount_cancellable = data->cancellable; + g_file_mount_enclosing_volume (location, 0, mount_op, self->mount_cancellable, + mount_not_mounted_callback, data); + g_object_unref (location); + g_object_unref (mount_op); + + needs_mount_handling = TRUE; + } + + g_clear_error (&error); + + return needs_mount_handling; +} + +static gboolean +handle_regular_file_if_needed (NautilusWindowSlot *self, + NautilusFile *file) +{ + NautilusFile *parent_file; + gboolean needs_regular_file_handling = FALSE; + parent_file = nautilus_file_get_parent (file); + if ((parent_file != NULL) && + nautilus_file_get_file_type (file) == G_FILE_TYPE_REGULAR) + { + g_clear_pointer (&self->pending_selection, nautilus_file_list_free); + g_clear_object (&self->pending_location); + g_clear_object (&self->pending_file_to_activate); + g_free (self->pending_scroll_to); + + self->pending_location = nautilus_file_get_parent_location (file); + if (nautilus_mime_file_extracts (file)) + { + self->pending_file_to_activate = nautilus_file_ref (file); + } + else + { + self->pending_selection = g_list_prepend (NULL, nautilus_file_ref (file)); + } + self->determine_view_file = nautilus_file_ref (parent_file); + self->pending_scroll_to = nautilus_file_get_uri (file); + + nautilus_file_invalidate_all_attributes (self->determine_view_file); + nautilus_file_call_when_ready (self->determine_view_file, + NAUTILUS_FILE_ATTRIBUTE_INFO | + NAUTILUS_FILE_ATTRIBUTE_MOUNT, + got_file_info_for_view_selection_callback, + self); + + needs_regular_file_handling = TRUE; + } + + nautilus_file_unref (parent_file); + + return needs_regular_file_handling; +} + +static void +got_file_info_for_view_selection_callback (NautilusFile *file, + gpointer callback_data) +{ + GError *error = NULL; + NautilusWindow *window; + NautilusWindowSlot *self; + NautilusFile *viewed_file; + NautilusView *view; + GFile *location; + NautilusApplication *app; + + self = callback_data; + window = nautilus_window_slot_get_window (self); + + g_assert (self->determine_view_file == file); + self->determine_view_file = NULL; + + nautilus_profile_start (NULL); + + if (handle_mount_if_needed (self, file)) + { + goto done; + } + + if (handle_regular_file_if_needed (self, file)) + { + goto done; + } + + if (self->mount_error) + { + error = g_error_copy (self->mount_error); + } + else if (nautilus_file_get_file_info_error (file) != NULL) + { + error = g_error_copy (nautilus_file_get_file_info_error (file)); + } + + location = self->pending_location; + + /* desktop and other-locations GFile operations report G_IO_ERROR_NOT_SUPPORTED, + * but it's not an actual error for Nautilus */ + if (!error || g_error_matches (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED)) + { + view = nautilus_window_slot_get_view_for_location (self, location); + setup_view (self, view); + } + else + { + if (error == NULL) + { + error = g_error_new (G_IO_ERROR, G_IO_ERROR_NOT_FOUND, + _("Unable to load location")); + } + nautilus_window_slot_display_view_selection_failure (window, + file, + location, + error); + + if (!gtk_widget_get_visible (GTK_WIDGET (window))) + { + /* Destroy never-had-a-chance-to-be-seen window. This case + * happens when a new window cannot display its initial URI. + */ + /* if this is the only window, we don't want to quit, so we redirect it to home */ + + app = NAUTILUS_APPLICATION (g_application_get_default ()); + + if (g_list_length (nautilus_application_get_windows (app)) == 1) + { + /* the user could have typed in a home directory that doesn't exist, + * in which case going home would cause an infinite loop, so we + * better test for that */ + + if (!nautilus_is_root_directory (location)) + { + if (!nautilus_is_home_directory (location)) + { + nautilus_window_slot_go_home (self, FALSE); + } + else + { + GFile *root; + + root = g_file_new_for_path ("/"); + /* the last fallback is to go to a known place that can't be deleted! */ + nautilus_window_slot_open_location_full (self, location, 0, NULL); + g_object_unref (root); + } + } + else + { + gtk_window_destroy (GTK_WINDOW (window)); + } + } + else + { + /* Since this is a window, destroying it will also unref it. */ + gtk_window_destroy (GTK_WINDOW (window)); + } + } + else + { + GFile *slot_location; + + /* Clean up state of already-showing window */ + end_location_change (self); + slot_location = nautilus_window_slot_get_location (self); + + /* XXX FIXME VOODOO TODO: + * Context: https://gitlab.gnome.org/GNOME/nautilus/issues/562 + * (and the associated MR) + * + * This used to just close the slot, which, in combination with + * the transient error dialog, caused Mutter to have a heart attack + * and die when the slot happened to be the only one remaining. + * The following condition can hold true in (at least) two cases: + * + * 1. We are inside the “Other Locations” view and are opening + * a broken bookmark, which causes the window slot to get replaced + * with one that handles the location, and is, understandably, + * empty. + * 2. We open a broken bookmark in a new window, which works almost + * the same, in that it has no open location. + * + * Ernestas: I’m leaning towards having an in-view message about the + * failure, which avoids dialogs and magically disappearing + * slots/tabs/windows (also allowing to go back to the + * previous location), but a dialog is quicker to inform + * about the failure. + * XXX + */ + if (slot_location == NULL) + { + nautilus_window_slot_go_home (self, 0); + } + else + { + /* We disconnected this, so we need to re-connect it */ + viewed_file = nautilus_file_get (slot_location); + nautilus_window_slot_set_viewed_file (self, viewed_file); + nautilus_file_unref (viewed_file); + + /* Leave the location bar showing the bad location that the user + * typed (or maybe achieved by dragging or something). Many times + * the mistake will just be an easily-correctable typo. The user + * can choose "Refresh" to get the original URI back in the location bar. + */ + } + } + } + +done: + g_clear_error (&error); + + nautilus_file_unref (file); + nautilus_profile_end (NULL); +} + +/* Load a view into the window, either reusing the old one or creating + * a new one. This happens when you want to load a new location, or just + * switch to a different view. + * If pending_location is set we're loading a new location and + * pending_location/selection will be used. If not, we're just switching + * view, and the current location will be used. + */ +static gboolean +setup_view (NautilusWindowSlot *self, + NautilusView *view) +{ + gboolean ret = TRUE; + GFile *old_location; + nautilus_profile_start (NULL); + + nautilus_window_slot_disconnect_content_view (self); + + self->new_content_view = view; + + nautilus_window_slot_connect_new_content_view (self); + + /* Forward search selection and state before loading the new model */ + old_location = self->content_view ? nautilus_view_get_location (self->content_view) : NULL; + + /* Actually load the pending location and selection: */ + if (self->pending_location != NULL) + { + load_new_location (self, + self->pending_location, + self->pending_selection, + self->pending_file_to_activate, + FALSE, + TRUE); + + nautilus_file_list_free (self->pending_selection); + self->pending_selection = NULL; + } + else if (old_location != NULL) + { + g_autolist (NautilusFile) selection = NULL; + + selection = nautilus_view_get_selection (self->content_view); + + load_new_location (self, + old_location, + selection, + NULL, + FALSE, + TRUE); + } + else + { + ret = FALSE; + goto out; + } + + change_view (self); + gtk_widget_show (GTK_WIDGET (self->window)); + +out: + nautilus_profile_end (NULL); + + return ret; +} + +static void +load_new_location (NautilusWindowSlot *self, + GFile *location, + GList *selection, + NautilusFile *file_to_activate, + gboolean tell_current_content_view, + gboolean tell_new_content_view) +{ + NautilusView *view; + g_assert (self != NULL); + g_assert (location != NULL); + + view = NULL; + nautilus_profile_start (NULL); + /* Note, these may recurse into report_load_underway */ + if (self->content_view != NULL && tell_current_content_view) + { + view = self->content_view; + nautilus_view_set_location (self->content_view, location); + } + + if (self->new_content_view != NULL && tell_new_content_view && + (!tell_current_content_view || + self->new_content_view != self->content_view)) + { + view = self->new_content_view; + nautilus_view_set_location (self->new_content_view, location); + } + if (view) + { + nautilus_view_set_selection (view, selection); + if (file_to_activate != NULL) + { + g_autoptr (GAppInfo) app_info = NULL; + const gchar *app_id; + + g_return_if_fail (NAUTILUS_IS_FILES_VIEW (view)); + app_info = nautilus_mime_get_default_application_for_file (file_to_activate); + app_id = g_app_info_get_id (app_info); + if (g_strcmp0 (app_id, NAUTILUS_DESKTOP_ID) == 0) + { + nautilus_files_view_activate_file (NAUTILUS_FILES_VIEW (view), + file_to_activate, 0); + } + } + } + + nautilus_profile_end (NULL); +} + +static void +end_location_change (NautilusWindowSlot *self) +{ + char *uri; + uri = nautilus_window_slot_get_location_uri (self); + if (uri) + { + DEBUG ("Finished loading window for uri %s", uri); + g_free (uri); + } + + nautilus_window_slot_set_allow_stop (self, FALSE); + + /* Now we can free details->pending_scroll_to, since the load_complete + * callback already has been emitted. + */ + g_free (self->pending_scroll_to); + self->pending_scroll_to = NULL; + + free_location_change (self); +} + +static void +free_location_change (NautilusWindowSlot *self) +{ + g_clear_object (&self->pending_location); + g_clear_object (&self->pending_file_to_activate); + nautilus_file_list_free (self->pending_selection); + self->pending_selection = NULL; + + /* Don't free details->pending_scroll_to, since thats needed until + * the load_complete callback. + */ + + if (self->mount_cancellable != NULL) + { + g_cancellable_cancel (self->mount_cancellable); + self->mount_cancellable = NULL; + } + + if (self->determine_view_file != NULL) + { + nautilus_file_cancel_call_when_ready + (self->determine_view_file, + got_file_info_for_view_selection_callback, self); + self->determine_view_file = NULL; + } +} + +/* This sets up a new view, for the current location, with the provided id. Used + * whenever the user changes the type of view to use. + * + * Note that the current view will be thrown away, even if it has the same id. + * Callers may first check if !nautilus_window_slot_content_view_matches(). + */ +static void +nautilus_window_slot_set_content_view (NautilusWindowSlot *self, + guint id) +{ + NautilusFilesView *view; + g_autolist (NautilusFile) selection = NULL; + char *uri; + g_assert (self != NULL); + + uri = nautilus_window_slot_get_location_uri (self); + DEBUG ("Change view of window %s to %d", uri, id); + g_free (uri); + + selection = nautilus_view_get_selection (self->content_view); + view = nautilus_files_view_new (id, self); + + nautilus_window_slot_stop_loading (self); + + nautilus_window_slot_set_allow_stop (self, TRUE); + + if (g_list_length (selection) == 0 && NAUTILUS_IS_FILES_VIEW (self->content_view)) + { + /* If there is no selection, queue a scroll to the same icon that + * is currently visible */ + self->pending_scroll_to = nautilus_files_view_get_first_visible_file (NAUTILUS_FILES_VIEW (self->content_view)); + } + + self->location_change_type = NAUTILUS_LOCATION_CHANGE_RELOAD; + + if (!setup_view (self, NAUTILUS_VIEW (view))) + { + /* Just load the homedir. */ + nautilus_window_slot_go_home (self, FALSE); + } +} + +void +nautilus_window_slot_back_or_forward (NautilusWindowSlot *self, + gboolean back, + guint distance) +{ + GList *list; + guint len; + NautilusBookmark *bookmark; + g_autoptr (GFile) location = NULL; + GFile *old_location; + g_autofree char *scroll_pos = NULL; + + list = back ? self->back_list : self->forward_list; + len = g_list_length (list); + + /* If we can't move in the direction at all, just return. */ + if (list == NULL) + { + return; + } + + /* If the distance to move is off the end of the list, go to the end + * of the list. */ + if (distance >= len) + { + distance = len - 1; + } + + bookmark = g_list_nth_data (list, distance); + location = nautilus_bookmark_get_location (bookmark); + old_location = nautilus_window_slot_get_location (self); + scroll_pos = nautilus_bookmark_get_scroll_pos (bookmark); + + begin_location_change (self, + location, old_location, + NULL, + back ? NAUTILUS_LOCATION_CHANGE_BACK : NAUTILUS_LOCATION_CHANGE_FORWARD, + distance, + scroll_pos); +} + +/* reload the contents of the window */ +static void +nautilus_window_slot_force_reload (NautilusWindowSlot *self) +{ + GFile *location; + char *current_pos; + g_autolist (NautilusFile) selection = NULL; + + g_assert (NAUTILUS_IS_WINDOW_SLOT (self)); + + location = nautilus_window_slot_get_location (self); + if (location == NULL) + { + return; + } + + /* peek_slot_field (window, location) can be free'd during the processing + * of begin_location_change, so make a copy + */ + g_object_ref (location); + current_pos = NULL; + + if (self->new_content_view) + { + selection = nautilus_view_get_selection (self->content_view); + + if (NAUTILUS_IS_FILES_VIEW (self->new_content_view)) + { + current_pos = nautilus_files_view_get_first_visible_file (NAUTILUS_FILES_VIEW (self->content_view)); + } + } + begin_location_change + (self, location, location, selection, + NAUTILUS_LOCATION_CHANGE_RELOAD, 0, current_pos); + g_free (current_pos); + g_object_unref (location); +} + +void +nautilus_window_slot_queue_reload (NautilusWindowSlot *self) +{ + g_assert (NAUTILUS_IS_WINDOW_SLOT (self)); + + if (nautilus_window_slot_get_location (self) == NULL) + { + return; + } + + if (self->pending_location != NULL + || self->content_view == NULL + || nautilus_view_is_loading (self->content_view)) + { + /* there is a reload in flight */ + self->needs_reload = TRUE; + return; + } + + nautilus_window_slot_force_reload (self); +} + +static void +nautilus_window_slot_clear_forward_list (NautilusWindowSlot *self) +{ + g_assert (NAUTILUS_IS_WINDOW_SLOT (self)); + + g_list_free_full (self->forward_list, g_object_unref); + self->forward_list = NULL; +} + +static void +nautilus_window_slot_clear_back_list (NautilusWindowSlot *self) +{ + g_assert (NAUTILUS_IS_WINDOW_SLOT (self)); + + g_list_free_full (self->back_list, g_object_unref); + self->back_list = NULL; +} + +static void +nautilus_window_slot_update_bookmark (NautilusWindowSlot *self, + NautilusFile *file) +{ + gboolean recreate; + GFile *new_location; + new_location = nautilus_file_get_location (file); + + if (self->current_location_bookmark == NULL) + { + recreate = TRUE; + } + else + { + GFile *bookmark_location; + bookmark_location = nautilus_bookmark_get_location (self->current_location_bookmark); + recreate = !g_file_equal (bookmark_location, new_location); + g_object_unref (bookmark_location); + } + + if (recreate) + { + char *display_name = NULL; + + /* We've changed locations, must recreate bookmark for current location. */ + g_clear_object (&self->last_location_bookmark); + self->last_location_bookmark = self->current_location_bookmark; + + display_name = nautilus_file_get_display_name (file); + self->current_location_bookmark = nautilus_bookmark_new (new_location, display_name); + g_free (display_name); + } + + g_object_unref (new_location); +} + +static void +check_bookmark_location_matches (NautilusBookmark *bookmark, + GFile *location) +{ + GFile *bookmark_location; + char *bookmark_uri, *uri; + + bookmark_location = nautilus_bookmark_get_location (bookmark); + if (!g_file_equal (location, bookmark_location)) + { + bookmark_uri = g_file_get_uri (bookmark_location); + uri = g_file_get_uri (location); + g_warning ("bookmark uri is %s, but expected %s", bookmark_uri, uri); + g_free (uri); + g_free (bookmark_uri); + } + g_object_unref (bookmark_location); +} + +/* Debugging function used to verify that the last_location_bookmark + * is in the state we expect when we're about to use it to update the + * Back or Forward list. + */ +static void +check_last_bookmark_location_matches_slot (NautilusWindowSlot *self) +{ + check_bookmark_location_matches (self->last_location_bookmark, + nautilus_window_slot_get_location (self)); +} + +static void +handle_go_direction (NautilusWindowSlot *self, + GFile *location, + gboolean forward) +{ + GList **list_ptr, **other_list_ptr; + GList *list, *other_list, *link; + NautilusBookmark *bookmark; + gint i; + list_ptr = (forward) ? (&self->forward_list) : (&self->back_list); + other_list_ptr = (forward) ? (&self->back_list) : (&self->forward_list); + list = *list_ptr; + other_list = *other_list_ptr; + + /* Move items from the list to the other list. */ + g_assert (g_list_length (list) > self->location_change_distance); + check_bookmark_location_matches (g_list_nth_data (list, self->location_change_distance), + location); + g_assert (nautilus_window_slot_get_location (self) != NULL); + + /* Move current location to list */ + check_last_bookmark_location_matches_slot (self); + + /* Use the first bookmark in the history list rather than creating a new one. */ + other_list = g_list_prepend (other_list, self->last_location_bookmark); + g_object_ref (other_list->data); + + /* Move extra links from the list to the other list */ + for (i = 0; i < self->location_change_distance; ++i) + { + bookmark = NAUTILUS_BOOKMARK (list->data); + list = g_list_remove (list, bookmark); + other_list = g_list_prepend (other_list, bookmark); + } + + /* One bookmark falls out of back/forward lists and becomes viewed location */ + link = list; + list = g_list_remove_link (list, link); + g_object_unref (link->data); + g_list_free_1 (link); + + *list_ptr = list; + *other_list_ptr = other_list; +} + +static void +handle_go_elsewhere (NautilusWindowSlot *self, + GFile *location) +{ + GFile *slot_location; + /* Clobber the entire forward list, and move displayed location to back list */ + nautilus_window_slot_clear_forward_list (self); + + slot_location = nautilus_window_slot_get_location (self); + + if (slot_location != NULL) + { + /* If we're returning to the same uri somehow, don't put this uri on back list. + * This also avoids a problem where set_displayed_location + * didn't update last_location_bookmark since the uri didn't change. + */ + if (!g_file_equal (slot_location, location)) + { + /* Store bookmark for current location in back list, unless there is no current location */ + check_last_bookmark_location_matches_slot (self); + /* Use the first bookmark in the history list rather than creating a new one. */ + self->back_list = g_list_prepend (self->back_list, + self->last_location_bookmark); + g_object_ref (self->back_list->data); + } + } +} + +static void +update_history (NautilusWindowSlot *self, + NautilusLocationChangeType type, + GFile *new_location) +{ + switch (type) + { + case NAUTILUS_LOCATION_CHANGE_STANDARD: + { + handle_go_elsewhere (self, new_location); + return; + } + + case NAUTILUS_LOCATION_CHANGE_RELOAD: + { + /* for reload there is no work to do */ + return; + } + + case NAUTILUS_LOCATION_CHANGE_BACK: + { + handle_go_direction (self, new_location, FALSE); + return; + } + + case NAUTILUS_LOCATION_CHANGE_FORWARD: + { + handle_go_direction (self, new_location, TRUE); + return; + } + } + g_return_if_fail (FALSE); +} + +typedef struct +{ + NautilusWindowSlot *slot; + GCancellable *cancellable; + GMount *mount; +} FindMountData; + +static void +nautilus_window_slot_show_x_content_bar (NautilusWindowSlot *self, + GMount *mount, + const char * const *x_content_types) +{ + GtkWidget *bar; + + g_assert (NAUTILUS_IS_WINDOW_SLOT (self)); + + if (!should_handle_content_types (x_content_types)) + { + return; + } + + bar = nautilus_x_content_bar_new (mount, x_content_types); + gtk_widget_show (bar); + nautilus_window_slot_add_extra_location_widget (self, bar); +} + +static void +found_content_type_cb (const char **x_content_types, + gpointer user_data) +{ + NautilusWindowSlot *self; + FindMountData *data = user_data; + self = data->slot; + if (g_cancellable_is_cancelled (data->cancellable)) + { + goto out; + } + + + if (x_content_types != NULL && x_content_types[0] != NULL) + { + nautilus_window_slot_show_x_content_bar (self, data->mount, (const char * const *) x_content_types); + } + + self->find_mount_cancellable = NULL; + +out: + g_object_unref (data->mount); + g_object_unref (data->cancellable); + g_free (data); +} + +static void +found_mount_cb (GObject *source_object, + GAsyncResult *res, + gpointer user_data) +{ + FindMountData *data = user_data; + NautilusWindowSlot *self; + GMount *mount; + self = NAUTILUS_WINDOW_SLOT (data->slot); + if (g_cancellable_is_cancelled (data->cancellable)) + { + goto out; + } + + mount = g_file_find_enclosing_mount_finish (G_FILE (source_object), + res, + NULL); + if (mount != NULL) + { + data->mount = mount; + nautilus_get_x_content_types_for_mount_async (mount, + found_content_type_cb, + data->cancellable, + data); + return; + } + + self->find_mount_cancellable = NULL; + +out: + g_object_unref (data->cancellable); + g_free (data); +} + +static void +nautilus_window_slot_show_special_location_bar (NautilusWindowSlot *self, + NautilusSpecialLocation special_location) +{ + GtkWidget *bar; + + bar = nautilus_special_location_bar_new (special_location); + gtk_widget_show (bar); + + nautilus_window_slot_add_extra_location_widget (self, bar); +} + +static void +nautilus_window_slot_update_for_new_location (NautilusWindowSlot *self) +{ + GFile *new_location; + NautilusFile *file; + new_location = self->pending_location; + self->pending_location = NULL; + + file = nautilus_file_get (new_location); + nautilus_window_slot_update_bookmark (self, file); + + update_history (self, self->location_change_type, new_location); + + /* Create a NautilusFile for this location, so we can catch it + * if it goes away. + */ + nautilus_window_slot_set_viewed_file (self, file); + self->viewed_file_seen = !nautilus_file_is_not_yet_confirmed (file); + self->viewed_file_in_trash = nautilus_file_is_in_trash (file); + nautilus_file_unref (file); + + nautilus_window_slot_set_location (self, new_location); + + /* Sync the actions for this new location. */ + nautilus_window_slot_sync_actions (self); +} + +static void +view_started_loading (NautilusWindowSlot *self, + NautilusView *view) +{ + if (view == self->content_view) + { + nautilus_window_slot_set_allow_stop (self, TRUE); + } + + /* Only grab focus if the menu isn't showing. Otherwise the menu disappears + * e.g. when the user toggles Show Hidden Files + */ + if (!nautilus_toolbar_is_menu_visible (NAUTILUS_TOOLBAR (nautilus_window_get_toolbar (self->window)))) + { + gtk_widget_grab_focus (GTK_WIDGET (self->window)); + } + + gtk_widget_show (GTK_WIDGET (self->window)); + + nautilus_window_slot_set_loading (self, TRUE); +} + +static void +view_ended_loading (NautilusWindowSlot *self, + NautilusView *view) +{ + if (view == self->content_view) + { + if (NAUTILUS_IS_FILES_VIEW (view) && self->pending_scroll_to != NULL) + { + nautilus_files_view_scroll_to_file (NAUTILUS_FILES_VIEW (self->content_view), self->pending_scroll_to); + } + + end_location_change (self); + } + + if (self->needs_reload) + { + nautilus_window_slot_queue_reload (self); + self->needs_reload = FALSE; + } + + nautilus_window_slot_set_allow_stop (self, FALSE); + + nautilus_window_slot_set_loading (self, FALSE); +} + +static void +view_is_loading_changed_cb (GObject *object, + GParamSpec *pspec, + NautilusWindowSlot *self) +{ + NautilusView *view; + + view = NAUTILUS_VIEW (object); + + nautilus_profile_start (NULL); + + if (nautilus_view_is_loading (view)) + { + view_started_loading (self, view); + } + else + { + view_ended_loading (self, view); + } + + nautilus_profile_end (NULL); +} + +static gboolean +nautilus_file_is_public_share_folder (NautilusFile *file) +{ + if (nautilus_file_is_user_special_directory (file, G_USER_DIRECTORY_PUBLIC_SHARE)) + { + return TRUE; + } + if (g_strcmp0 (g_get_home_dir (), g_get_user_special_dir (G_USER_DIRECTORY_PUBLIC_SHARE))) + { + /* In order to match the behavior of gnome-user-share the ~/Public folder + * is considered to be the public sharing folder when XDG_PUBLICSHARE_DIR + * is set to the home folder. */ + g_autoptr (GFile) public_folder = g_file_new_build_filename (g_get_home_dir (), "Public", NULL); + g_autoptr (GFile) location = nautilus_file_get_location (file); + + return g_file_equal (public_folder, location); + } + return FALSE; +} + +static void +nautilus_window_slot_setup_extra_location_widgets (NautilusWindowSlot *self) +{ + GFile *location; + FindMountData *data; + NautilusDirectory *directory; + NautilusFile *file; + GFile *scripts_file; + char *scripts_path; + + scripts_path = nautilus_get_scripts_directory_path (); + location = nautilus_window_slot_get_current_location (self); + + if (location == NULL) + { + return; + } + + directory = nautilus_directory_get (location); + + scripts_file = g_file_new_for_path (scripts_path); + g_free (scripts_path); + + file = nautilus_file_get (location); + + if (nautilus_should_use_templates_directory () && + nautilus_file_is_user_special_directory (file, G_USER_DIRECTORY_TEMPLATES)) + { + nautilus_window_slot_show_special_location_bar (self, NAUTILUS_SPECIAL_LOCATION_TEMPLATES); + } + else if (g_file_equal (location, scripts_file)) + { + nautilus_window_slot_show_special_location_bar (self, NAUTILUS_SPECIAL_LOCATION_SCRIPTS); + } + else if (check_schema_available (FILE_SHARING_SCHEMA_ID) && nautilus_file_is_public_share_folder (file)) + { + nautilus_window_slot_show_special_location_bar (self, NAUTILUS_SPECIAL_LOCATION_SHARING); + } + else if (nautilus_directory_is_in_trash (directory) && + g_settings_get_boolean (gnome_privacy_preferences, "remove-old-trash-files")) + { + nautilus_window_slot_show_special_location_bar (self, NAUTILUS_SPECIAL_LOCATION_TRASH); + } + + g_object_unref (scripts_file); + nautilus_file_unref (file); + + /* need the mount to determine if we should put up the x-content cluebar */ + if (self->find_mount_cancellable != NULL) + { + g_cancellable_cancel (self->find_mount_cancellable); + self->find_mount_cancellable = NULL; + } + + data = g_new (FindMountData, 1); + data->slot = self; + data->cancellable = g_cancellable_new (); + data->mount = NULL; + + self->find_mount_cancellable = data->cancellable; + g_file_find_enclosing_mount_async (location, + G_PRIORITY_DEFAULT, + data->cancellable, + found_mount_cb, + data); + + nautilus_directory_unref (directory); +} + +static void +nautilus_window_slot_connect_new_content_view (NautilusWindowSlot *self) +{ + if (self->new_content_view) + { + g_signal_connect (self->new_content_view, + "notify::loading", + G_CALLBACK (view_is_loading_changed_cb), + self); + } +} + +static void +nautilus_window_slot_disconnect_content_view (NautilusWindowSlot *self) +{ + if (self->content_view) + { + /* disconnect old view */ + g_signal_handlers_disconnect_by_func (self->content_view, + G_CALLBACK (view_is_loading_changed_cb), + self); + } +} + +static void +nautilus_window_slot_switch_new_content_view (NautilusWindowSlot *self) +{ + GtkWidget *widget; + gboolean reusing_view; + reusing_view = self->new_content_view && + gtk_widget_get_parent (GTK_WIDGET (self->new_content_view)) != NULL; + /* We are either reusing the view, so new_content_view and content_view + * are the same, or the new_content_view is invalid */ + if (self->new_content_view == NULL || reusing_view) + { + goto done; + } + + if (self->content_view != NULL) + { + g_binding_unbind (self->searching_binding); + g_binding_unbind (self->selection_binding); + g_binding_unbind (self->extensions_background_menu_binding); + g_binding_unbind (self->templates_menu_binding); + widget = GTK_WIDGET (self->content_view); + gtk_box_remove (GTK_BOX (self), widget); + g_clear_object (&self->content_view); + } + + if (self->new_content_view != NULL) + { + self->content_view = self->new_content_view; + self->new_content_view = NULL; + + widget = GTK_WIDGET (self->content_view); + gtk_box_append (GTK_BOX (self), widget); + gtk_widget_set_vexpand (widget, TRUE); + gtk_widget_show (widget); + self->searching_binding = g_object_bind_property (self->content_view, "searching", + self, "searching", + G_BINDING_DEFAULT | G_BINDING_SYNC_CREATE); + self->selection_binding = g_object_bind_property (self->content_view, "selection", + self, "selection", + G_BINDING_DEFAULT | G_BINDING_SYNC_CREATE); + self->extensions_background_menu_binding = g_object_bind_property (self->content_view, "extensions-background-menu", + self, "extensions-background-menu", + G_BINDING_DEFAULT | G_BINDING_SYNC_CREATE); + self->templates_menu_binding = g_object_bind_property (self->content_view, "templates-menu", + self, "templates-menu", + G_BINDING_DEFAULT | G_BINDING_SYNC_CREATE); + g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_ICON_NAME]); + g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_TOOLBAR_MENU_SECTIONS]); + g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_EXTENSIONS_BACKGROUND_MENU]); + g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_TEMPLATES_MENU]); + g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_TOOLTIP]); + } + +done: + /* Clean up, so we don't confuse having a new_content_view available or + * just that we didn't care about it here */ + self->new_content_view = NULL; +} + +/* This is called when we have decided we can actually change to the new view/location situation. */ +static void +change_view (NautilusWindowSlot *self) +{ + /* Switch to the new content view. + * Destroy the extra location widgets first, since they might hold + * a pointer to the old view, which will possibly be destroyed inside + * nautilus_window_slot_switch_new_content_view(). + */ + nautilus_window_slot_remove_extra_location_widgets (self); + nautilus_window_slot_switch_new_content_view (self); + + if (self->pending_location != NULL) + { + /* Tell the window we are finished. */ + nautilus_window_slot_update_for_new_location (self); + } + + /* Now that we finished switching to the new location, + * add back the extra location widgets. + */ + nautilus_window_slot_setup_extra_location_widgets (self); +} + +static void +nautilus_window_slot_dispose (GObject *object) +{ + NautilusWindowSlot *self; + self = NAUTILUS_WINDOW_SLOT (object); + + g_signal_handlers_disconnect_by_data (nautilus_preferences, self); + + nautilus_window_slot_clear_forward_list (self); + nautilus_window_slot_clear_back_list (self); + + nautilus_window_slot_remove_extra_location_widgets (self); + + g_clear_pointer (&self->searching_binding, g_binding_unbind); + g_clear_pointer (&self->selection_binding, g_binding_unbind); + g_clear_pointer (&self->extensions_background_menu_binding, g_binding_unbind); + g_clear_pointer (&self->templates_menu_binding, g_binding_unbind); + + g_clear_object (&self->templates_menu); + g_clear_object (&self->extensions_background_menu); + + if (self->content_view) + { + gtk_box_remove (GTK_BOX (self), GTK_WIDGET (self->content_view)); + g_clear_object (&self->content_view); + } + + if (self->new_content_view) + { + gtk_box_remove (GTK_BOX (self), GTK_WIDGET (self->new_content_view)); + g_clear_object (&self->new_content_view); + } + + nautilus_window_slot_set_viewed_file (self, NULL); + + g_clear_object (&self->location); + g_clear_object (&self->pending_file_to_activate); + g_clear_pointer (&self->pending_selection, nautilus_file_list_free); + + g_clear_object (&self->current_location_bookmark); + g_clear_object (&self->last_location_bookmark); + g_clear_object (&self->slot_action_group); + g_clear_object (&self->pending_search_query); + + g_clear_pointer (&self->find_mount_cancellable, g_cancellable_cancel); + + if (self->query_editor) + { + g_clear_object (&self->query_editor); + } + + free_location_change (self); + + G_OBJECT_CLASS (nautilus_window_slot_parent_class)->dispose (object); +} + +static void +nautilus_window_slot_finalize (GObject *object) +{ + NautilusWindowSlot *self; + self = NAUTILUS_WINDOW_SLOT (object); + g_clear_pointer (&self->title, g_free); + + G_OBJECT_CLASS (nautilus_window_slot_parent_class)->finalize (object); +} + +static gboolean +nautilus_window_slot_grab_focus (GtkWidget *widget) +{ + NautilusWindowSlot *self; + self = NAUTILUS_WINDOW_SLOT (widget); + + if (nautilus_window_slot_get_search_visible (self)) + { + return gtk_widget_grab_focus (GTK_WIDGET (self->query_editor)); + } + else if (self->content_view != NULL) + { + return gtk_widget_grab_focus (GTK_WIDGET (self->content_view)); + } + else if (self->new_content_view != NULL) + { + return gtk_widget_grab_focus (GTK_WIDGET (self->new_content_view)); + } + + return GTK_WIDGET_CLASS (nautilus_window_slot_parent_class)->grab_focus (widget); +} + +static void +nautilus_window_slot_class_init (NautilusWindowSlotClass *klass) +{ + GObjectClass *oclass = G_OBJECT_CLASS (klass); + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); + + oclass->dispose = nautilus_window_slot_dispose; + oclass->finalize = nautilus_window_slot_finalize; + oclass->constructed = nautilus_window_slot_constructed; + oclass->set_property = nautilus_window_slot_set_property; + oclass->get_property = nautilus_window_slot_get_property; + + widget_class->grab_focus = nautilus_window_slot_grab_focus; + + properties[PROP_ACTIVE] = + g_param_spec_boolean ("active", + "Whether the slot is active", + "Whether the slot is the active slot of the window", + FALSE, + G_PARAM_READWRITE); + + properties[PROP_LOADING] = + g_param_spec_boolean ("loading", + "Whether the slot loading", + "Whether the slot is loading a new location", + FALSE, + G_PARAM_READABLE); + + properties[PROP_SEARCHING] = + g_param_spec_boolean ("searching", + "Whether the current view of the slot is searching", + "Whether the current view of the slot is searching. Proxy property from the view", + FALSE, + G_PARAM_READWRITE); + + properties[PROP_SELECTION] = + g_param_spec_pointer ("selection", + "Selection of the current view of the slot", + "The selection of the current view of the slot. Proxy property from the view", + G_PARAM_READWRITE); + + properties[PROP_WINDOW] = + g_param_spec_object ("window", + "The NautilusWindow", + "The NautilusWindow this slot is part of", + NAUTILUS_TYPE_WINDOW, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT); + + properties[PROP_ICON_NAME] = + g_param_spec_string ("icon-name", + "Icon that represents the slot", + "The icon that represents the slot", + NULL, + G_PARAM_READABLE); + + properties[PROP_TOOLBAR_MENU_SECTIONS] = + g_param_spec_pointer ("toolbar-menu-sections", + "Menu sections for the toolbar menu", + "The menu sections to add to the toolbar menu for this slot", + G_PARAM_READABLE); + + properties[PROP_EXTENSIONS_BACKGROUND_MENU] = + g_param_spec_object ("extensions-background-menu", + "Background menu of extensions", + "Proxy property from the view for the background menu for extensions", + G_TYPE_MENU_MODEL, + G_PARAM_READWRITE); + + properties[PROP_TEMPLATES_MENU] = + g_param_spec_object ("templates-menu", + "Templates menu", + "Proxy property from the view for the templates menu", + G_TYPE_MENU_MODEL, + G_PARAM_READWRITE); + + properties[PROP_LOCATION] = + g_param_spec_object ("location", + "Current location visible on the slot", + "Either the location that is used currently, or the pending location. Clients will see the same value they set, and therefore it will be cosistent from clients point of view.", + G_TYPE_FILE, + G_PARAM_READWRITE); + + properties[PROP_TOOLTIP] = + g_param_spec_string ("tooltip", + "Tooltip that represents the slot", + "The tooltip that represents the slot", + NULL, + G_PARAM_READWRITE); + + properties[PROP_ALLOW_STOP] = + g_param_spec_boolean ("allow-stop", "", "", + FALSE, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + + properties[PROP_TITLE] = + g_param_spec_string ("title", "", "", + NULL, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + + g_object_class_install_properties (oclass, NUM_PROPERTIES, properties); +} + +GFile * +nautilus_window_slot_get_location (NautilusWindowSlot *self) +{ + g_return_val_if_fail (NAUTILUS_IS_WINDOW_SLOT (self), NULL); + + return self->location; +} + +GFile * +nautilus_window_slot_get_pending_location (NautilusWindowSlot *self) +{ + g_return_val_if_fail (NAUTILUS_IS_WINDOW_SLOT (self), NULL); + + return self->pending_location; +} + +const gchar * +nautilus_window_slot_get_title (NautilusWindowSlot *self) +{ + return self->title; +} + +char * +nautilus_window_slot_get_location_uri (NautilusWindowSlot *self) +{ + g_assert (NAUTILUS_IS_WINDOW_SLOT (self)); + + if (self->location) + { + return g_file_get_uri (self->location); + } + return NULL; +} + +NautilusWindow * +nautilus_window_slot_get_window (NautilusWindowSlot *self) +{ + g_assert (NAUTILUS_IS_WINDOW_SLOT (self)); + + return self->window; +} + +void +nautilus_window_slot_set_window (NautilusWindowSlot *self, + NautilusWindow *window) +{ + g_assert (NAUTILUS_IS_WINDOW_SLOT (self)); + g_assert (NAUTILUS_IS_WINDOW (window)); + + if (self->window != window) + { + self->window = window; + g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_WINDOW]); + } +} + +/* nautilus_window_slot_update_title: + * + * Re-calculate the slot title. + * Called when the location or view has changed. + * @slot: The NautilusWindowSlot in question. + * + */ +void +nautilus_window_slot_update_title (NautilusWindowSlot *self) +{ + NautilusWindow *window; + g_autofree char *title = NULL; + title = nautilus_compute_title_for_location (self->location); + window = nautilus_window_slot_get_window (self); + + if (g_strcmp0 (title, self->title) != 0) + { + g_free (self->title); + self->title = g_steal_pointer (&title); + + g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_TITLE]); + + nautilus_window_sync_title (window, self); + } +} + +gboolean +nautilus_window_slot_get_allow_stop (NautilusWindowSlot *self) +{ + return self->allow_stop; +} + +void +nautilus_window_slot_set_allow_stop (NautilusWindowSlot *self, + gboolean allow) +{ + NautilusWindow *window; + g_assert (NAUTILUS_IS_WINDOW_SLOT (self)); + + self->allow_stop = allow; + + window = nautilus_window_slot_get_window (self); + nautilus_window_sync_allow_stop (window, self); + + g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_ALLOW_STOP]); +} + +void +nautilus_window_slot_stop_loading (NautilusWindowSlot *self) +{ + GFile *location; + NautilusDirectory *directory; + location = nautilus_window_slot_get_location (self); + directory = nautilus_directory_get (self->location); + + if (NAUTILUS_IS_FILES_VIEW (self->content_view)) + { + nautilus_files_view_stop_loading (NAUTILUS_FILES_VIEW (self->content_view)); + } + + nautilus_directory_unref (directory); + + if (self->pending_location != NULL && + location != NULL && + self->content_view != NULL && + NAUTILUS_IS_FILES_VIEW (self->content_view)) + { + /* No need to tell the new view - either it is the + * same as the old view, in which case it will already + * be told, or it is the very pending change we wish + * to cancel. + */ + g_autolist (NautilusFile) selection = NULL; + + selection = nautilus_view_get_selection (self->content_view); + load_new_location (self, + location, + selection, + NULL, + TRUE, + FALSE); + } + + end_location_change (self); + + if (self->new_content_view) + { + g_object_unref (self->new_content_view); + self->new_content_view = NULL; + } +} + +NautilusView * +nautilus_window_slot_get_current_view (NautilusWindowSlot *self) +{ + if (self->content_view != NULL) + { + return self->content_view; + } + else if (self->new_content_view) + { + return self->new_content_view; + } + + return NULL; +} + +NautilusBookmark * +nautilus_window_slot_get_bookmark (NautilusWindowSlot *self) +{ + return self->current_location_bookmark; +} + +GList * +nautilus_window_slot_get_back_history (NautilusWindowSlot *self) +{ + return self->back_list; +} + +GList * +nautilus_window_slot_get_forward_history (NautilusWindowSlot *self) +{ + return self->forward_list; +} + +NautilusWindowSlot * +nautilus_window_slot_new (NautilusWindow *window) +{ + return g_object_new (NAUTILUS_TYPE_WINDOW_SLOT, + "window", window, + NULL); +} + +const gchar * +nautilus_window_slot_get_icon_name (NautilusWindowSlot *self) +{ + guint current_view_id; + g_return_val_if_fail (NAUTILUS_IS_WINDOW_SLOT (self), NULL); + + if (self->content_view == NULL) + { + return ""; + } + + current_view_id = nautilus_view_get_view_id (NAUTILUS_VIEW (self->content_view)); + switch (current_view_id) + { + case NAUTILUS_VIEW_LIST_ID: + { + return nautilus_view_get_icon_name (NAUTILUS_VIEW_GRID_ID); + } + break; + + case NAUTILUS_VIEW_GRID_ID: + { + return nautilus_view_get_icon_name (NAUTILUS_VIEW_LIST_ID); + } + break; + + case NAUTILUS_VIEW_OTHER_LOCATIONS_ID: + { + return nautilus_view_get_icon_name (NAUTILUS_VIEW_OTHER_LOCATIONS_ID); + } + break; + + default: + { + return NULL; + } + } +} + +const gchar * +nautilus_window_slot_get_tooltip (NautilusWindowSlot *self) +{ + guint current_view_id; + g_return_val_if_fail (NAUTILUS_IS_WINDOW_SLOT (self), NULL); + + if (self->content_view == NULL) + { + return NULL; + } + + current_view_id = nautilus_view_get_view_id (NAUTILUS_VIEW (self->content_view)); + switch (current_view_id) + { + case NAUTILUS_VIEW_LIST_ID: + { + return nautilus_view_get_tooltip (NAUTILUS_VIEW_GRID_ID); + } + break; + + case NAUTILUS_VIEW_GRID_ID: + { + return nautilus_view_get_tooltip (NAUTILUS_VIEW_LIST_ID); + } + break; + + case NAUTILUS_VIEW_OTHER_LOCATIONS_ID: + { + return nautilus_view_get_tooltip (NAUTILUS_VIEW_OTHER_LOCATIONS_ID); + } + break; + + default: + { + return NULL; + } + } +} + +NautilusToolbarMenuSections * +nautilus_window_slot_get_toolbar_menu_sections (NautilusWindowSlot *self) +{ + NautilusView *view; + + g_return_val_if_fail (NAUTILUS_IS_WINDOW_SLOT (self), NULL); + + view = nautilus_window_slot_get_current_view (self); + + return view ? nautilus_view_get_toolbar_menu_sections (view) : NULL; +} + +gboolean +nautilus_window_slot_get_active (NautilusWindowSlot *self) +{ + g_return_val_if_fail (NAUTILUS_IS_WINDOW_SLOT (self), FALSE); + + return self->active; +} + +void +nautilus_window_slot_set_active (NautilusWindowSlot *self, + gboolean active) +{ + NautilusWindow *window; + + g_return_if_fail (NAUTILUS_IS_WINDOW_SLOT (self)); + + if (self->active != active) + { + self->active = active; + + if (active) + { + AdwTabView *tab_view; + AdwTabPage *page; + + window = self->window; + + tab_view = nautilus_window_get_tab_view (window); + page = adw_tab_view_get_page (tab_view, GTK_WIDGET (self)); + + adw_tab_view_set_selected_page (tab_view, page); + + /* sync window to new slot */ + nautilus_window_sync_allow_stop (window, self); + nautilus_window_sync_title (window, self); + nautilus_window_sync_location_widgets (window); + nautilus_window_slot_sync_actions (self); + + gtk_widget_insert_action_group (GTK_WIDGET (window), "slot", self->slot_action_group); + } + else + { + window = nautilus_window_slot_get_window (self); + g_assert (self == nautilus_window_get_active_slot (window)); + + gtk_widget_insert_action_group (GTK_WIDGET (window), "slot", NULL); + } + + g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_ACTIVE]); + } +} + +static void +nautilus_window_slot_set_loading (NautilusWindowSlot *self, + gboolean loading) +{ + g_return_if_fail (NAUTILUS_IS_WINDOW_SLOT (self)); + + self->loading = loading; + + g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_LOADING]); +} + +gboolean +nautilus_window_slot_get_loading (NautilusWindowSlot *self) +{ + g_return_val_if_fail (NAUTILUS_IS_WINDOW_SLOT (self), FALSE); + + return self->loading; +} + +NautilusQueryEditor * +nautilus_window_slot_get_query_editor (NautilusWindowSlot *self) +{ + g_return_val_if_fail (NAUTILUS_IS_WINDOW_SLOT (self), NULL); + + return self->query_editor; +} |