/* * Nautilus * * Copyright (C) 2011, Red Hat, Inc. * * Nautilus 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. * * Nautilus 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 . * * Author: Cosimo Cecchi * */ #include "nautilus-toolbar.h" #include #include #include "nautilus-application.h" #include "nautilus-bookmark.h" #include "nautilus-global-preferences.h" #include "nautilus-history-controls.h" #include "nautilus-location-entry.h" #include "nautilus-pathbar.h" #include "nautilus-view-controls.h" #include "nautilus-ui-utilities.h" #include "nautilus-window-slot.h" struct _NautilusToolbar { AdwBin parent_instance; GtkWidget *history_controls_stack; GtkWidget *history_controls; GtkWidget *history_controls_placeholder; GtkWidget *location_entry_container; GtkWidget *search_container; GtkWidget *toolbar_switcher; GtkWidget *path_bar; GtkWidget *location_entry; GtkWidget *search_button_stack; GtkWidget *search_button; GtkWidget *search_button_placeholder; gboolean show_location_entry; GtkWidget *focus_before_location_entry; GtkWidget *sidebar_button; gboolean show_new_folder_button; gboolean show_sidebar_button; gboolean sidebar_button_active; gboolean show_view_controls; gboolean show_history_controls; /* active slot & bindings */ NautilusWindowSlot *window_slot; }; enum { PROP_0, PROP_WINDOW_SLOT, PROP_SHOW_NEW_FOLDER_BUTTON, PROP_SHOW_SIDEBAR_BUTTON, PROP_SIDEBAR_BUTTON_ACTIVE, PROP_SHOW_VIEW_CONTROLS, PROP_SHOW_HISTORY_CONTROLS, NUM_PROPERTIES }; static GParamSpec *properties[NUM_PROPERTIES] = { NULL, }; G_DEFINE_FINAL_TYPE (NautilusToolbar, nautilus_toolbar, ADW_TYPE_BIN); static void nautilus_toolbar_set_window_slot_real (NautilusToolbar *self, NautilusWindowSlot *slot); static void toolbar_update_appearance (NautilusToolbar *self) { gboolean show_location_entry; show_location_entry = self->show_location_entry || g_settings_get_boolean (nautilus_preferences, NAUTILUS_PREFERENCES_ALWAYS_USE_LOCATION_ENTRY); if (self->window_slot != NULL && nautilus_window_slot_get_search_visible (self->window_slot)) { gtk_stack_set_visible_child_name (GTK_STACK (self->toolbar_switcher), "search"); } else if (show_location_entry) { gtk_stack_set_visible_child_name (GTK_STACK (self->toolbar_switcher), "location"); } else { gtk_stack_set_visible_child_name (GTK_STACK (self->toolbar_switcher), "pathbar"); } /* Adjust to global search mode. */ gboolean search_global = (self->window_slot != NULL && nautilus_window_slot_get_search_global (self->window_slot)); gtk_stack_set_visible_child (GTK_STACK (self->search_button_stack), search_global ? self->search_button_placeholder : self->search_button); gtk_stack_set_visible_child (GTK_STACK (self->history_controls_stack), search_global ? self->history_controls_placeholder : self->history_controls); } static void nautilus_toolbar_open_location_entry (NautilusToolbar *self, const char *special_text) { if (self->show_location_entry) { return; } /* Remember focus widget. */ GtkRoot *root = gtk_widget_get_root (GTK_WIDGET (self)); GtkWidget *focus_widget = (root != NULL) ? gtk_root_get_focus (root) : NULL; g_set_weak_pointer (&self->focus_before_location_entry, focus_widget); self->show_location_entry = TRUE; toolbar_update_appearance (self); gtk_widget_grab_focus (self->location_entry); if (special_text != NULL) { nautilus_location_entry_set_special_text (NAUTILUS_LOCATION_ENTRY (self->location_entry), special_text); gtk_editable_set_position (GTK_EDITABLE (self->location_entry), -1); } } static void nautilus_toolbar_close_location_entry (NautilusToolbar *self) { if (!self->show_location_entry) { return; } self->show_location_entry = FALSE; toolbar_update_appearance (self); if (self->focus_before_location_entry != NULL) { /* Restore focus widget. */ gtk_widget_grab_focus (self->focus_before_location_entry); g_clear_weak_pointer (&self->focus_before_location_entry); } } static void on_path_bar_open_location (NautilusPathBar *path_bar, GFile *location, NautilusOpenFlags open_flags, gpointer user_data) { NautilusToolbar *self = NAUTILUS_TOOLBAR (user_data); if (open_flags & (NAUTILUS_OPEN_FLAG_NEW_WINDOW | NAUTILUS_OPEN_FLAG_NEW_TAB)) { nautilus_application_open_location_full (NAUTILUS_APPLICATION (g_application_get_default ()), location, open_flags, NULL, NULL); } else { nautilus_window_slot_open_location_full (self->window_slot, location, NULL); } } static void on_location_entry_focus_leave (GtkEventControllerFocus *controller, gpointer user_data) { NautilusToolbar *toolbar; GtkWidget *focus_widget; toolbar = NAUTILUS_TOOLBAR (user_data); /* The location entry is a transient: it should hide when it loses focus. * * However, if we lose focus because the window itself lost focus, then the * location entry should persist, because this may happen due to the user * switching keyboard layout/input method; or they may want to copy/drop * an path from another window/app. We detect this case by looking at the * focus widget of the window (GtkRoot). */ focus_widget = gtk_root_get_focus (gtk_widget_get_root (GTK_WIDGET (toolbar))); if (focus_widget != NULL && gtk_widget_is_ancestor (focus_widget, GTK_WIDGET (toolbar->location_entry))) { return; } nautilus_toolbar_close_location_entry (toolbar); } static void on_location_entry_location_changed (NautilusLocationEntry *entry, GFile *location, gpointer user_data) { NautilusToolbar *self = NAUTILUS_TOOLBAR (user_data); nautilus_toolbar_close_location_entry (self); nautilus_window_slot_open_location_full (self->window_slot, location, NULL); } static void action_edit_location (GtkWidget *widget, const char *action_name, GVariant *parameter) { NautilusToolbar *self = NAUTILUS_TOOLBAR (widget); nautilus_toolbar_open_location_entry (self, NULL); } static void action_prompt_root_location (GtkWidget *widget, const char *action_name, GVariant *parameter) { NautilusToolbar *self = NAUTILUS_TOOLBAR (widget); nautilus_toolbar_open_location_entry (self, "/"); } static void action_prompt_home_location (GtkWidget *widget, const char *action_name, GVariant *parameter) { NautilusToolbar *self = NAUTILUS_TOOLBAR (widget); nautilus_toolbar_open_location_entry (self, "~/"); } static void nautilus_toolbar_init (NautilusToolbar *self) { g_type_ensure (NAUTILUS_TYPE_HISTORY_CONTROLS); g_type_ensure (NAUTILUS_TYPE_VIEW_CONTROLS); g_type_ensure (NAUTILUS_TYPE_PATH_BAR); g_type_ensure (NAUTILUS_TYPE_LOCATION_ENTRY); gtk_widget_init_template (GTK_WIDGET (self)); GtkShortcutController *shortcuts = GTK_SHORTCUT_CONTROLLER (gtk_shortcut_controller_new ()); gtk_shortcut_controller_set_scope (shortcuts, GTK_SHORTCUT_SCOPE_MANAGED); gtk_widget_add_controller (GTK_WIDGET (self), GTK_EVENT_CONTROLLER (shortcuts)); #define ADD_SHORTCUT_FOR_ACTION(controller, action, trigger) \ (gtk_shortcut_controller_add_shortcut ((controller), \ gtk_shortcut_new (gtk_shortcut_trigger_parse_string ((trigger)), \ gtk_named_action_new ((action))))) ADD_SHORTCUT_FOR_ACTION (shortcuts, "toolbar.edit-location", "l|Go|OpenURL"); ADD_SHORTCUT_FOR_ACTION (shortcuts, "toolbar.prompt-root-location", "slash|KP_Divide"); /* Support keyboard layouts which have a dead tilde key but not a tilde key. */ ADD_SHORTCUT_FOR_ACTION (shortcuts, "toolbar.prompt-home-location", "asciitilde|dead_tilde"); #undef ADD_SHORTCUT_FOR_ACTION /* Setup path bar */ g_signal_connect_object (self->path_bar, "open-location", G_CALLBACK (on_path_bar_open_location), self, G_CONNECT_DEFAULT); /* Setup location entry */ GtkEventController *controller = gtk_event_controller_focus_new (); gtk_widget_add_controller (self->location_entry, controller); g_signal_connect (controller, "leave", G_CALLBACK (on_location_entry_focus_leave), self); /* Setting a max width on one entry to effectively set a max expansion for * the whole title widget. */ gtk_editable_set_max_width_chars (GTK_EDITABLE (self->location_entry), 88); g_signal_connect_object (self->location_entry, "location-changed", G_CALLBACK (on_location_entry_location_changed), self, G_CONNECT_DEFAULT); g_signal_connect_object (self->location_entry, "cancel", G_CALLBACK (nautilus_toolbar_close_location_entry), self, G_CONNECT_SWAPPED); toolbar_update_appearance (self); } static void nautilus_toolbar_get_property (GObject *object, guint property_id, GValue *value, GParamSpec *pspec) { NautilusToolbar *self = NAUTILUS_TOOLBAR (object); switch (property_id) { case PROP_WINDOW_SLOT: { g_value_set_object (value, self->window_slot); } break; case PROP_SHOW_NEW_FOLDER_BUTTON: { g_value_set_boolean (value, self->show_new_folder_button); } break; case PROP_SHOW_SIDEBAR_BUTTON: { g_value_set_boolean (value, self->show_sidebar_button); } break; case PROP_SIDEBAR_BUTTON_ACTIVE: { g_value_set_boolean (value, self->sidebar_button_active); } break; case PROP_SHOW_VIEW_CONTROLS: { g_value_set_boolean (value, self->show_view_controls); } break; case PROP_SHOW_HISTORY_CONTROLS: { g_value_set_boolean (value, self->show_history_controls); } break; default: { G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); } break; } } static void on_window_slot_destroyed (gpointer data, GObject *where_the_object_was) { NautilusToolbar *self; self = NAUTILUS_TOOLBAR (data); nautilus_toolbar_set_window_slot_real (self, NULL); } static void nautilus_toolbar_set_property (GObject *object, guint property_id, const GValue *value, GParamSpec *pspec) { NautilusToolbar *self = NAUTILUS_TOOLBAR (object); switch (property_id) { case PROP_WINDOW_SLOT: { nautilus_toolbar_set_window_slot (self, g_value_get_object (value)); } break; case PROP_SHOW_NEW_FOLDER_BUTTON: { self->show_new_folder_button = g_value_get_boolean (value); } break; case PROP_SHOW_SIDEBAR_BUTTON: { self->show_sidebar_button = g_value_get_boolean (value); } break; case PROP_SIDEBAR_BUTTON_ACTIVE: { self->sidebar_button_active = g_value_get_boolean (value); } break; case PROP_SHOW_VIEW_CONTROLS: { self->show_view_controls = g_value_get_boolean (value); } break; case PROP_SHOW_HISTORY_CONTROLS: { self->show_history_controls = g_value_get_boolean (value); } break; default: { G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); } break; } } static void nautilus_toolbar_dispose (GObject *object) { NautilusToolbar *self = NAUTILUS_TOOLBAR (object); adw_bin_set_child (ADW_BIN (object), NULL); gtk_widget_dispose_template (GTK_WIDGET (self), NAUTILUS_TYPE_TOOLBAR); G_OBJECT_CLASS (nautilus_toolbar_parent_class)->dispose (object); } static void nautilus_toolbar_finalize (GObject *obj) { NautilusToolbar *self = NAUTILUS_TOOLBAR (obj); g_clear_weak_pointer (&self->focus_before_location_entry); g_signal_handlers_disconnect_by_func (nautilus_preferences, toolbar_update_appearance, self); if (self->window_slot != NULL) { g_signal_handlers_disconnect_by_data (self->window_slot, self); g_object_weak_unref (G_OBJECT (self->window_slot), on_window_slot_destroyed, self); self->window_slot = NULL; } G_OBJECT_CLASS (nautilus_toolbar_parent_class)->finalize (obj); } static void nautilus_toolbar_class_init (NautilusToolbarClass *klass) { GObjectClass *oclass; GtkWidgetClass *widget_class; widget_class = GTK_WIDGET_CLASS (klass); oclass = G_OBJECT_CLASS (klass); oclass->get_property = nautilus_toolbar_get_property; oclass->set_property = nautilus_toolbar_set_property; oclass->dispose = nautilus_toolbar_dispose; oclass->finalize = nautilus_toolbar_finalize; properties [PROP_WINDOW_SLOT] = g_param_spec_object ("window-slot", "Window slot currently active", "Window slot currently active", NAUTILUS_TYPE_WINDOW_SLOT, (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); properties[PROP_SHOW_NEW_FOLDER_BUTTON] = g_param_spec_boolean ("show-new-folder-button", NULL, NULL, FALSE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); properties[PROP_SHOW_SIDEBAR_BUTTON] = g_param_spec_boolean ("show-sidebar-button", NULL, NULL, FALSE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); properties[PROP_SIDEBAR_BUTTON_ACTIVE] = g_param_spec_boolean ("sidebar-button-active", NULL, NULL, FALSE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); properties[PROP_SHOW_HISTORY_CONTROLS] = g_param_spec_boolean ("show-history-controls", NULL, NULL, TRUE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); properties[PROP_SHOW_VIEW_CONTROLS] = g_param_spec_boolean ("show-view-controls", NULL, NULL, TRUE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); g_object_class_install_properties (oclass, NUM_PROPERTIES, properties); gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/nautilus/ui/nautilus-toolbar.ui"); gtk_widget_class_bind_template_child (widget_class, NautilusToolbar, history_controls_stack); gtk_widget_class_bind_template_child (widget_class, NautilusToolbar, history_controls); gtk_widget_class_bind_template_child (widget_class, NautilusToolbar, history_controls_placeholder); gtk_widget_class_bind_template_child (widget_class, NautilusToolbar, toolbar_switcher); gtk_widget_class_bind_template_child (widget_class, NautilusToolbar, search_container); gtk_widget_class_bind_template_child (widget_class, NautilusToolbar, path_bar); gtk_widget_class_bind_template_child (widget_class, NautilusToolbar, location_entry); gtk_widget_class_bind_template_child (widget_class, NautilusToolbar, search_button_stack); gtk_widget_class_bind_template_child (widget_class, NautilusToolbar, search_button); gtk_widget_class_bind_template_child (widget_class, NautilusToolbar, search_button_placeholder); gtk_widget_class_bind_template_callback (widget_class, nautilus_toolbar_close_location_entry); gtk_widget_class_set_accessible_role (widget_class, GTK_ACCESSIBLE_ROLE_TOOLBAR); gtk_widget_class_install_action (widget_class, "toolbar.edit-location", NULL, action_edit_location); gtk_widget_class_install_action (widget_class, "toolbar.prompt-root-location", NULL, action_prompt_root_location); gtk_widget_class_install_action (widget_class, "toolbar.prompt-home-location", NULL, action_prompt_home_location); } GtkWidget * nautilus_toolbar_new (void) { return g_object_new (NAUTILUS_TYPE_TOOLBAR, NULL); } static void box_remove_all_children (GtkBox *box) { GtkWidget *child; while ((child = gtk_widget_get_first_child (GTK_WIDGET (box))) != NULL) { gtk_box_remove (GTK_BOX (box), child); } } static void on_slot_location_changed (NautilusToolbar *self) { g_assert (self->window_slot != NULL); GFile *location = nautilus_window_slot_get_location (self->window_slot); if (location == NULL) { return; } nautilus_location_entry_set_location (NAUTILUS_LOCATION_ENTRY (self->location_entry), location); nautilus_path_bar_set_path (NAUTILUS_PATH_BAR (self->path_bar), location); } static void slot_on_extensions_background_menu_changed (NautilusToolbar *self, GParamSpec *param, NautilusWindowSlot *slot) { g_autoptr (GMenuModel) menu = NULL; menu = nautilus_window_slot_get_extensions_background_menu (slot); nautilus_path_bar_set_extensions_background_menu (NAUTILUS_PATH_BAR (self->path_bar), menu); } static void slot_on_templates_menu_changed (NautilusToolbar *self, GParamSpec *param, NautilusWindowSlot *slot) { g_autoptr (GMenuModel) menu = NULL; menu = nautilus_window_slot_get_templates_menu (slot); nautilus_path_bar_set_templates_menu (NAUTILUS_PATH_BAR (self->path_bar), menu); } void nautilus_toolbar_show_current_location_menu (NautilusToolbar *self) { nautilus_path_bar_show_current_location_menu (NAUTILUS_PATH_BAR (self->path_bar)); } /* Called from on_window_slot_destroyed(), since bindings and signal handlers * are automatically removed once the slot goes away. */ static void nautilus_toolbar_set_window_slot_real (NautilusToolbar *self, NautilusWindowSlot *slot) { self->window_slot = slot; if (self->window_slot != NULL) { g_object_weak_ref (G_OBJECT (self->window_slot), on_window_slot_destroyed, self); on_slot_location_changed (self); g_signal_connect_swapped (self->window_slot, "notify::location", G_CALLBACK (on_slot_location_changed), self); g_signal_connect_swapped (self->window_slot, "notify::extensions-background-menu", G_CALLBACK (slot_on_extensions_background_menu_changed), self); g_signal_connect_swapped (self->window_slot, "notify::templates-menu", G_CALLBACK (slot_on_templates_menu_changed), self); g_signal_connect_swapped (self->window_slot, "notify::search-visible", G_CALLBACK (toolbar_update_appearance), self); g_signal_connect_swapped (self->window_slot, "notify::search-global", G_CALLBACK (toolbar_update_appearance), self); } box_remove_all_children (GTK_BOX (self->search_container)); if (self->window_slot != NULL) { gtk_box_append (GTK_BOX (self->search_container), GTK_WIDGET (nautilus_window_slot_get_query_editor (self->window_slot))); } toolbar_update_appearance (self); g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_WINDOW_SLOT]); } void nautilus_toolbar_set_window_slot (NautilusToolbar *self, NautilusWindowSlot *window_slot) { g_return_if_fail (NAUTILUS_IS_TOOLBAR (self)); g_return_if_fail (window_slot == NULL || NAUTILUS_IS_WINDOW_SLOT (window_slot)); if (self->window_slot == window_slot) { return; } if (self->window_slot != NULL) { g_signal_handlers_disconnect_by_data (self->window_slot, self); g_object_weak_unref (G_OBJECT (self->window_slot), on_window_slot_destroyed, self); } nautilus_toolbar_set_window_slot_real (self, window_slot); }