/*
* 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-file-undo-manager.h"
#include "nautilus-global-preferences.h"
#include "nautilus-history-controls.h"
#include "nautilus-location-entry.h"
#include "nautilus-pathbar.h"
#include "nautilus-progress-indicator.h"
#include "nautilus-view-controls.h"
#include "nautilus-ui-utilities.h"
#include "nautilus-window.h"
struct _NautilusToolbar
{
AdwBin parent_instance;
NautilusWindow *window;
GtkWidget *path_bar_container;
GtkWidget *location_entry_container;
GtkWidget *search_container;
GtkWidget *toolbar_switcher;
GtkWidget *path_bar;
GtkWidget *location_entry;
gboolean show_location_entry;
GtkWidget *app_button;
GMenuModel *undo_redo_section;
GtkWidget *sidebar_button;
gboolean show_sidebar_button;
gboolean sidebar_button_active;
gboolean show_toolbar_children;
GtkWidget *search_button;
GtkWidget *location_entry_close_button;
/* active slot & bindings */
NautilusWindowSlot *window_slot;
GBinding *search_binding;
};
enum
{
PROP_0,
PROP_SHOW_LOCATION_ENTRY,
PROP_WINDOW_SLOT,
PROP_SEARCHING,
PROP_SHOW_SIDEBAR_BUTTON,
PROP_SIDEBAR_BUTTON_ACTIVE,
PROP_SHOW_TOOLBAR_CHILDREN,
NUM_PROPERTIES
};
static GParamSpec *properties[NUM_PROPERTIES] = { NULL, };
G_DEFINE_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_searching (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");
}
}
static void
update_action (NautilusToolbar *self,
const char *action_name,
gboolean enabled)
{
GtkWidget *window;
GAction *action;
window = gtk_widget_get_ancestor (GTK_WIDGET (self), NAUTILUS_TYPE_WINDOW);
/* Activate/deactivate */
action = g_action_map_lookup_action (G_ACTION_MAP (window), action_name);
g_simple_action_set_enabled (G_SIMPLE_ACTION (action), enabled);
}
static void
undo_manager_changed (NautilusToolbar *self)
{
NautilusFileUndoInfo *info;
NautilusFileUndoManagerState undo_state;
gboolean undo_active;
gboolean redo_active;
g_autofree gchar *undo_label = NULL;
g_autofree gchar *redo_label = NULL;
g_autofree gchar *undo_description = NULL;
g_autofree gchar *redo_description = NULL;
gboolean is_undo;
g_autoptr (GMenu) updated_section = g_menu_new ();
g_autoptr (GMenuItem) undo_menu_item = NULL;
g_autoptr (GMenuItem) redo_menu_item = NULL;
/* Look up the last action from the undo manager, and get the text that
* describes it, e.g. "Undo Create Folder"/"Redo Create Folder"
*/
info = nautilus_file_undo_manager_get_action ();
undo_state = nautilus_file_undo_manager_get_state ();
undo_active = redo_active = FALSE;
if (info != NULL && undo_state > NAUTILUS_FILE_UNDO_MANAGER_STATE_NONE)
{
is_undo = undo_state == NAUTILUS_FILE_UNDO_MANAGER_STATE_UNDO;
/* The last action can either be undone/redone. Activate the corresponding
* menu item and deactivate the other
*/
undo_active = is_undo;
redo_active = !is_undo;
nautilus_file_undo_info_get_strings (info, &undo_label, &undo_description,
&redo_label, &redo_description);
}
/* Set the label of the undo and redo menu items, and activate them appropriately
*/
if (!undo_active || undo_label == NULL)
{
g_free (undo_label);
undo_label = g_strdup (_("_Undo"));
}
undo_menu_item = g_menu_item_new (undo_label, "win.undo");
g_menu_append_item (updated_section, undo_menu_item);
update_action (self, "undo", undo_active);
if (!redo_active || redo_label == NULL)
{
g_free (redo_label);
redo_label = g_strdup (_("_Redo"));
}
redo_menu_item = g_menu_item_new (redo_label, "win.redo");
g_menu_append_item (updated_section, redo_menu_item);
update_action (self, "redo", redo_active);
nautilus_gmenu_set_from_model (G_MENU (self->undo_redo_section),
G_MENU_MODEL (updated_section));
}
static void
on_location_entry_close (GtkWidget *close_button,
NautilusToolbar *self)
{
nautilus_toolbar_set_show_location_entry (self, FALSE);
}
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_set_show_location_entry (toolbar, FALSE);
}
static void
nautilus_toolbar_constructed (GObject *object)
{
NautilusToolbar *self = NAUTILUS_TOOLBAR (object);
GtkEventController *controller;
self->path_bar = GTK_WIDGET (g_object_new (NAUTILUS_TYPE_PATH_BAR, NULL));
gtk_box_append (GTK_BOX (self->path_bar_container),
self->path_bar);
self->location_entry = nautilus_location_entry_new ();
gtk_box_append (GTK_BOX (self->location_entry_container),
self->location_entry);
self->location_entry_close_button = gtk_button_new_from_icon_name ("window-close-symbolic");
gtk_box_append (GTK_BOX (self->location_entry_container),
self->location_entry_close_button);
g_signal_connect (self->location_entry_close_button, "clicked",
G_CALLBACK (on_location_entry_close), self);
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);
toolbar_update_appearance (self);
}
static void
nautilus_toolbar_init (NautilusToolbar *self)
{
g_type_ensure (NAUTILUS_TYPE_HISTORY_CONTROLS);
g_type_ensure (NAUTILUS_TYPE_PROGRESS_INDICATOR);
g_type_ensure (NAUTILUS_TYPE_VIEW_CONTROLS);
gtk_widget_init_template (GTK_WIDGET (self));
}
void
nautilus_toolbar_on_window_constructed (NautilusToolbar *self)
{
/* undo_manager_changed manipulates the window actions, so set it up
* after the window and it's actions have been constructed
*/
g_signal_connect_object (nautilus_file_undo_manager_get (),
"undo-changed",
G_CALLBACK (undo_manager_changed),
self,
G_CONNECT_SWAPPED);
undo_manager_changed (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_SHOW_LOCATION_ENTRY:
{
g_value_set_boolean (value, self->show_location_entry);
}
break;
case PROP_WINDOW_SLOT:
{
g_value_set_object (value, self->window_slot);
}
break;
case PROP_SEARCHING:
{
g_value_set_boolean (value, gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (self->search_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_TOOLBAR_CHILDREN:
{
g_value_set_boolean (value, self->show_toolbar_children);
}
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);
/* The window slot was finalized, and the binding has already been removed.
* Null it here, so that dispose() does not trip over itself when removing it.
*/
self->search_binding = NULL;
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_SHOW_LOCATION_ENTRY:
{
nautilus_toolbar_set_show_location_entry (self, g_value_get_boolean (value));
}
break;
case PROP_WINDOW_SLOT:
{
nautilus_toolbar_set_window_slot (self, g_value_get_object (value));
}
break;
case PROP_SEARCHING:
{
gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (self->search_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_TOOLBAR_CHILDREN:
{
self->show_toolbar_children = 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;
self = NAUTILUS_TOOLBAR (object);
g_clear_pointer (&self->search_binding, g_binding_unbind);
G_OBJECT_CLASS (nautilus_toolbar_parent_class)->dispose (object);
}
static void
nautilus_toolbar_finalize (GObject *obj)
{
NautilusToolbar *self = NAUTILUS_TOOLBAR (obj);
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;
oclass->constructed = nautilus_toolbar_constructed;
properties[PROP_SHOW_LOCATION_ENTRY] =
g_param_spec_boolean ("show-location-entry",
"Whether to show the location entry",
"Whether to show the location entry instead of the pathbar",
FALSE,
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
properties [PROP_WINDOW_SLOT] =
g_param_spec_object ("window-slot",
"Window slot currently active",
"Window slot currently acive",
NAUTILUS_TYPE_WINDOW_SLOT,
(G_PARAM_READWRITE |
G_PARAM_STATIC_STRINGS));
properties [PROP_SEARCHING] =
g_param_spec_boolean ("searching",
"Current view is searching",
"Whether the current view is searching or not",
FALSE,
G_PARAM_READWRITE);
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_TOOLBAR_CHILDREN] =
g_param_spec_boolean ("show-toolbar-children", 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, app_button);
gtk_widget_class_bind_template_child (widget_class, NautilusToolbar, undo_redo_section);
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_container);
gtk_widget_class_bind_template_child (widget_class, NautilusToolbar, location_entry_container);
gtk_widget_class_bind_template_child (widget_class, NautilusToolbar, search_button);
gtk_widget_class_set_accessible_role (widget_class, GTK_ACCESSIBLE_ROLE_TOOLBAR);
}
GtkWidget *
nautilus_toolbar_new (void)
{
return g_object_new (NAUTILUS_TYPE_TOOLBAR,
NULL);
}
GtkWidget *
nautilus_toolbar_get_path_bar (NautilusToolbar *self)
{
return self->path_bar;
}
GtkWidget *
nautilus_toolbar_get_location_entry (NautilusToolbar *self)
{
return self->location_entry;
}
void
nautilus_toolbar_set_show_location_entry (NautilusToolbar *self,
gboolean show_location_entry)
{
if (show_location_entry != self->show_location_entry)
{
self->show_location_entry = show_location_entry;
toolbar_update_appearance (self);
g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_SHOW_LOCATION_ENTRY]);
}
}
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
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);
}
/* 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)
{
g_autoptr (GList) children = NULL;
self->window_slot = slot;
if (self->window_slot != NULL)
{
g_object_weak_ref (G_OBJECT (self->window_slot),
on_window_slot_destroyed,
self);
self->search_binding = g_object_bind_property (self->window_slot, "searching",
self, "searching",
G_BINDING_DEFAULT | G_BINDING_SYNC_CREATE);
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::searching",
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]);
g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_SEARCHING]);
}
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;
}
g_clear_pointer (&self->search_binding, g_binding_unbind);
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);
}
gboolean
nautilus_toolbar_is_menu_visible (NautilusToolbar *self)
{
GtkWidget *menu;
g_return_val_if_fail (NAUTILUS_IS_TOOLBAR (self), FALSE);
menu = GTK_WIDGET (gtk_menu_button_get_popover (GTK_MENU_BUTTON (self->app_button)));
g_return_val_if_fail (menu != NULL, FALSE);
return gtk_widget_is_visible (menu);
}