/*
* 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);
}