/*
* Nautilus
*
* Copyright (C) 1999, 2000, 2004 Red Hat, Inc.
* Copyright (C) 1999, 2000, 2001 Eazel, 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 .
*
* Authors: Elliot Lee
* John Sullivan
* Alexander Larsson
*/
/* nautilus-window.c: Implementation of the main window object */
#define G_LOG_DOMAIN "nautilus-window"
#include
#include "nautilus-window.h"
#include
#include
#include
#include
#include
#ifdef GDK_WINDOWING_WAYLAND
#include
#endif
#ifdef HAVE_MALLOC_TRIM
#include
#endif
#include "nautilus-application.h"
#include "nautilus-bookmark.h"
#include "nautilus-bookmark-list.h"
#include "nautilus-clipboard.h"
#include "nautilus-dnd.h"
#include "nautilus-enums.h"
#include "nautilus-file.h"
#include "nautilus-file-operations.h"
#include "nautilus-file-undo-manager.h"
#include "nautilus-file-undo-operations.h"
#include "nautilus-file-utilities.h"
#include "nautilus-global-preferences.h"
#include "nautilus-metadata.h"
#include "nautilus-network-address-bar.h"
#include "nautilus-mime-actions.h"
#include "nautilus-module.h"
#include "nautilus-progress-indicator.h"
#include "nautilus-scheme.h"
#include "nautilus-shortcut-manager.h"
#include "nautilus-sidebar.h"
#include "nautilus-signaller.h"
#include "nautilus-toolbar.h"
#include "nautilus-trash-monitor.h"
#include "nautilus-ui-utilities.h"
#include "nautilus-window-slot.h"
static void nautilus_window_initialize_actions (NautilusWindow *window);
static void nautilus_window_sync_location_widgets (NautilusWindow *window);
static void update_cursor (NautilusWindow *window);
static void
set_active_slot (NautilusWindow *window,
NautilusWindowSlot *new_slot);
/* Sanity check: highest mouse button value I could find was 14. 5 is our
* lower threshold (well-documented to be the one of the button events for the
* scrollwheel), so it's hardcoded in the functions below. However, if you have
* a button that registers higher and want to map it, file a bug and
* we'll move the bar. Makes you wonder why the X guys don't have
* defined values for these like the XKB stuff, huh?
*/
struct _NautilusWindow
{
AdwApplicationWindow parent_instance;
GMenuModel *undo_redo_section;
AdwTabOverview *tab_overview;
AdwTabView *tab_view;
AdwTabBar *tab_bar;
AdwTabPage *menu_page;
GList *slots;
NautilusWindowSlot *active_slot; /* weak reference */
GtkWidget *split_view;
/* Side Pane */
NautilusSidebar *places_sidebar;
GVolume *selected_volume; /* the selected volume in the sidebar popup callback */
GFile *selected_file; /* the selected file in the sidebar popup callback */
/* Notifications */
AdwToastOverlay *toast_overlay;
AdwToast *last_undo_toast;
/* Toolbar */
GtkWidget *toolbar;
gboolean temporary_navigation_bar;
GtkWidget *network_address_bar;
guint sidebar_width_handler_id;
GQueue *tab_data_queue;
/* Pad controller which holds a reference to the window. Kept around to
* break reference-counting cycles during finalization. */
GtkPadController *pad_controller;
};
enum
{
LOCATIONS_CHANGED,
LAST_SIGNAL
};
static guint signals[LAST_SIGNAL] = { 0 };
G_DEFINE_FINAL_TYPE (NautilusWindow, nautilus_window, ADW_TYPE_APPLICATION_WINDOW);
enum
{
PROP_0,
PROP_ACTIVE_SLOT,
N_PROPS
};
static GParamSpec *properties[N_PROPS];
static const GtkPadActionEntry pad_actions[] =
{
{ GTK_PAD_ACTION_BUTTON, 0, -1, N_("Home"), "go-home" },
{ GTK_PAD_ACTION_BUTTON, 1, -1, N_("New tab"), "new-tab" },
{ GTK_PAD_ACTION_BUTTON, 2, -1, N_("Close current view"), "close-current-view" },
/* Button number sequence continues in window-slot.c */
};
#ifdef HAVE_MALLOC_TRIM
static guint malloc_trim_idle_id = 0;
static void
malloc_trim_idle_cb (gpointer user_data)
{
malloc_trim (8 * 1024 * 1024);
malloc_trim_idle_id = 0;
}
static void
schedule_trim (void)
{
if (malloc_trim_idle_id == 0)
{
malloc_trim_idle_id = g_idle_add_once (malloc_trim_idle_cb, NULL);
}
}
#endif
static AdwTabPage *
get_current_page (NautilusWindow *window)
{
if (window->menu_page != NULL)
{
return window->menu_page;
}
return adw_tab_view_get_selected_page (window->tab_view);
}
static void
action_open_tab_overview (GSimpleAction *action,
GVariant *state,
gpointer user_data)
{
NautilusWindow *window = user_data;
adw_tab_overview_set_open (window->tab_overview, TRUE);
}
static void
action_close_current_view (GSimpleAction *action,
GVariant *state,
gpointer user_data)
{
NautilusWindow *window = user_data;
AdwTabPage *page = get_current_page (window);
if (adw_tab_view_get_n_pages (window->tab_view) <= 1)
{
nautilus_window_close (window);
return;
}
adw_tab_view_close_page (window->tab_view, page);
}
static void
action_close_other_tabs (GSimpleAction *action,
GVariant *parameters,
gpointer user_data)
{
NautilusWindow *window = user_data;
AdwTabPage *page = get_current_page (window);
adw_tab_view_close_other_pages (window->tab_view, page);
}
static void
action_go_home (GSimpleAction *action,
GVariant *state,
gpointer user_data)
{
NautilusWindow *window;
GFile *home;
window = NAUTILUS_WINDOW (user_data);
home = g_file_new_for_path (g_get_home_dir ());
nautilus_window_open_location_full (window, home, 0, NULL);
g_object_unref (home);
}
static void
action_new_tab (GSimpleAction *action,
GVariant *state,
gpointer user_data)
{
nautilus_window_new_tab (NAUTILUS_WINDOW (user_data));
}
static void
action_tab_move_left (GSimpleAction *action,
GVariant *state,
gpointer user_data)
{
NautilusWindow *window = user_data;
AdwTabPage *page = get_current_page (window);
adw_tab_view_reorder_backward (window->tab_view, page);
}
static void
action_tab_move_right (GSimpleAction *action,
GVariant *state,
gpointer user_data)
{
NautilusWindow *window = user_data;
AdwTabPage *page = get_current_page (window);
adw_tab_view_reorder_forward (window->tab_view, page);
}
static void
action_go_to_tab (GSimpleAction *action,
GVariant *value,
gpointer user_data)
{
NautilusWindow *window = NAUTILUS_WINDOW (user_data);
gint16 num;
num = g_variant_get_int32 (value);
if (num < adw_tab_view_get_n_pages (window->tab_view))
{
AdwTabPage *page = adw_tab_view_get_nth_page (window->tab_view, num);
adw_tab_view_set_selected_page (window->tab_view, page);
}
}
static void
action_redo (GSimpleAction *action,
GVariant *state,
gpointer user_data)
{
NautilusWindow *window = user_data;
nautilus_file_undo_manager_redo (GTK_WINDOW (window), NULL);
}
static void
action_undo (GSimpleAction *action,
GVariant *state,
gpointer user_data)
{
NautilusWindow *window = user_data;
nautilus_file_undo_manager_undo (GTK_WINDOW (window), NULL);
}
static void
action_show_current_location_menu (GSimpleAction *action,
GVariant *state,
gpointer user_data)
{
NautilusWindow *self = NAUTILUS_WINDOW (user_data);
nautilus_toolbar_show_current_location_menu (NAUTILUS_TOOLBAR (self->toolbar));
}
static void
on_location_changed (NautilusWindow *window)
{
nautilus_window_sync_location_widgets (window);
}
static void
on_slot_location_changed (NautilusWindowSlot *slot,
GParamSpec *pspec,
NautilusWindow *window)
{
if (window->active_slot == slot)
{
on_location_changed (window);
}
g_signal_emit (window, signals[LOCATIONS_CHANGED], 0);
}
static AdwTabPage *
create_tab_cb (NautilusWindow *window)
{
gtk_widget_activate_action (GTK_WIDGET (window), "win.new-tab", NULL);
return adw_tab_view_get_selected_page (window->tab_view);
}
static void
tab_view_setup_menu_cb (AdwTabView *tab_view,
AdwTabPage *page,
NautilusWindow *window)
{
GAction *move_tab_left_action;
GAction *move_tab_right_action;
GAction *restore_tab_action;
GAction *close_other_tabs_action;
GAction *move_tab_to_new_window_action;
int position, n_pages;
gboolean menu_is_closed = (page == NULL);
window->menu_page = page;
if (!menu_is_closed)
{
position = adw_tab_view_get_page_position (tab_view, page);
n_pages = adw_tab_view_get_n_pages (tab_view);
}
move_tab_left_action = g_action_map_lookup_action (G_ACTION_MAP (window),
"tab-move-left");
move_tab_right_action = g_action_map_lookup_action (G_ACTION_MAP (window),
"tab-move-right");
restore_tab_action = g_action_map_lookup_action (G_ACTION_MAP (window),
"restore-tab");
close_other_tabs_action = g_action_map_lookup_action (G_ACTION_MAP (window),
"close-other-tabs");
move_tab_to_new_window_action = g_action_map_lookup_action (G_ACTION_MAP (window),
"tab-move-new-window");
g_simple_action_set_enabled (G_SIMPLE_ACTION (move_tab_left_action),
menu_is_closed || position > 0);
g_simple_action_set_enabled (G_SIMPLE_ACTION (move_tab_right_action),
menu_is_closed || position < n_pages - 1);
g_simple_action_set_enabled (G_SIMPLE_ACTION (restore_tab_action),
g_queue_get_length (window->tab_data_queue) > 0);
g_simple_action_set_enabled (G_SIMPLE_ACTION (close_other_tabs_action),
menu_is_closed || n_pages > 1);
g_simple_action_set_enabled (G_SIMPLE_ACTION (move_tab_to_new_window_action),
menu_is_closed || n_pages > 1);
}
static void
tab_view_notify_selected_page_cb (AdwTabView *tab_view,
GParamSpec *pspec,
NautilusWindow *window)
{
AdwTabPage *page = adw_tab_view_get_selected_page (tab_view);
NautilusWindowSlot *slot = NAUTILUS_WINDOW_SLOT (adw_tab_page_get_child (page));
g_return_if_fail (slot != NULL);
set_active_slot (window, slot);
}
static void
disconnect_slot (NautilusWindow *window,
NautilusWindowSlot *slot)
{
g_signal_handlers_disconnect_by_data (slot, window);
}
static void
on_update_page_tooltip (NautilusWindowSlot *slot,
GParamSpec *pspec,
AdwTabPage *page)
{
GFile *location = nautilus_window_slot_get_location (slot);
g_autofree char *escaped_name = NULL;
if (location == NULL)
{
return;
}
/* Set the tooltip on the label's parent (the tab label hbox),
* so it covers all of the tab label.
*/
if (g_file_has_uri_scheme (location, SCHEME_SEARCH))
{
escaped_name = g_markup_escape_text (nautilus_window_slot_get_title (slot), -1);
}
else
{
g_autofree gchar *location_name = g_file_get_parse_name (location);
escaped_name = g_markup_escape_text (location_name, -1);
}
adw_tab_page_set_tooltip (page, escaped_name);
}
static NautilusWindowSlot *
nautilus_window_create_and_init_slot (NautilusWindow *window)
{
g_autoptr (NautilusWindowSlot) slot = nautilus_window_slot_new (NAUTILUS_MODE_BROWSE);
g_signal_connect_swapped (slot, "notify::allow-stop",
G_CALLBACK (update_cursor), window);
g_signal_connect (slot, "notify::location",
G_CALLBACK (on_slot_location_changed), window);
AdwTabPage *current = get_current_page (window);
AdwTabPage *page = adw_tab_view_add_page (window->tab_view, GTK_WIDGET (slot), current);
g_object_bind_property (slot, "allow-stop",
page, "loading",
G_BINDING_SYNC_CREATE);
g_object_bind_property (slot, "title",
page, "title",
G_BINDING_SYNC_CREATE);
g_signal_connect (slot, "notify::title", G_CALLBACK (on_update_page_tooltip), page);
/* Assure that AdwTabView has added slot to slot list */
g_warn_if_fail (g_list_find (window->slots, slot) != NULL);
return slot;
}
static NautilusWindowSlot *
get_slot_with_open_location (NautilusWindow *self,
GFile *location)
{
for (GList *l = self->slots; l != NULL; l = l->next)
{
NautilusWindowSlot *slot = l->data;
GFile *slot_location = nautilus_window_slot_get_location (slot);
if (slot_location != NULL &&
g_file_equal (location, slot_location))
{
return slot;
}
}
return NULL;
}
void
nautilus_window_open_location_full (NautilusWindow *window,
GFile *location,
NautilusOpenFlags flags,
NautilusFileList *selection)
{
/* Assert that we are not managing new windows */
g_warn_if_fail ((flags & NAUTILUS_OPEN_FLAG_NEW_WINDOW) == 0);
NautilusWindowSlot *target_slot = (flags & NAUTILUS_OPEN_FLAG_REUSE_EXISTING) != 0
? get_slot_with_open_location (window, location)
: window->active_slot;
if (target_slot == NULL || (flags & NAUTILUS_OPEN_FLAG_NEW_TAB) != 0)
{
target_slot = nautilus_window_create_and_init_slot (window);
}
/* Make the opened location the one active if we weren't asked for the
* opposite, since it's the most usual use case */
if ((flags & NAUTILUS_OPEN_FLAG_DONT_MAKE_ACTIVE) == 0)
{
gtk_window_present (GTK_WINDOW (window));
set_active_slot (window, target_slot);
}
nautilus_window_slot_open_location_full (target_slot, location, selection);
}
static gboolean
nautilus_window_grab_focus (GtkWidget *widget)
{
NautilusWindow *self = NAUTILUS_WINDOW (widget);
if (self->active_slot != NULL)
{
return gtk_widget_grab_focus (GTK_WIDGET (self->active_slot));
}
return GTK_WIDGET_CLASS (nautilus_window_parent_class)->grab_focus (widget);
}
static void
remove_slot_from_window (NautilusWindowSlot *slot,
NautilusWindow *window)
{
g_return_if_fail (NAUTILUS_IS_WINDOW_SLOT (slot));
g_return_if_fail (NAUTILUS_WINDOW (window));
g_debug ("Removing slot %p", slot);
disconnect_slot (window, slot);
window->slots = g_list_remove (window->slots, slot);
g_signal_emit (window, signals[LOCATIONS_CHANGED], 0);
}
void
nautilus_window_new_tab (NautilusWindow *window)
{
NautilusWindowSlot *current_slot;
AdwTabPage *page;
GFile *location;
page = get_current_page (window);
current_slot = NAUTILUS_WINDOW_SLOT (adw_tab_page_get_child (page));
location = nautilus_window_slot_get_location (current_slot);
if (location != NULL)
{
if (g_file_has_uri_scheme (location, SCHEME_SEARCH))
{
location = g_file_new_for_path (g_get_home_dir ());
}
else
{
g_object_ref (location);
}
nautilus_window_open_location_full (window, location,
NAUTILUS_OPEN_FLAG_NEW_TAB,
NULL);
g_object_unref (location);
}
}
static void
update_cursor (NautilusWindow *window)
{
if (!gtk_widget_get_realized (GTK_WIDGET (window)))
{
return;
}
if (window->active_slot != NULL &&
nautilus_window_slot_get_allow_stop (window->active_slot))
{
gtk_widget_set_cursor_from_name (GTK_WIDGET (window), "progress");
}
else
{
gtk_widget_set_cursor (GTK_WIDGET (window), NULL);
}
}
/* Callback used when the places sidebar needs to know the drag action to suggest */
static GdkDragAction
places_sidebar_drag_action_requested_cb (NautilusSidebar *sidebar,
NautilusFile *dest_file,
GList *source_file_list)
{
return nautilus_dnd_get_preferred_action (dest_file, source_file_list->data);
}
#if 0 && NAUTILUS_DND_NEEDS_GTK4_REIMPLEMENTATION
/* Callback used when the places sidebar needs us to pop up a menu with possible drag actions */
static GdkDragAction
places_sidebar_drag_action_ask_cb (NautilusSidebar *sidebar,
GdkDragAction actions,
gpointer user_data)
{
return nautilus_drag_drop_action_ask (GTK_WIDGET (sidebar), actions);
}
#endif
static GList *
build_uri_list_from_gfile_list (GSList *file_list)
{
GList *result;
GSList *l;
result = NULL;
for (l = file_list; l; l = l->next)
{
GFile *file = l->data;
char *uri;
uri = g_file_get_uri (file);
result = g_list_prepend (result, uri);
}
return g_list_reverse (result);
}
/* Callback used when the places sidebar has URIs dropped into it. We do a normal file operation for them. */
static void
places_sidebar_drag_perform_drop_cb (NautilusSidebar *sidebar,
GFile *dest_file,
GSList *source_file_list,
GdkDragAction action,
gpointer user_data)
{
char *dest_uri;
GList *source_uri_list;
dest_uri = g_file_get_uri (dest_file);
source_uri_list = build_uri_list_from_gfile_list (source_file_list);
nautilus_file_operations_copy_move (source_uri_list, dest_uri, action, GTK_WIDGET (sidebar), NULL, NULL, NULL);
g_free (dest_uri);
g_list_free_full (source_uri_list, g_free);
}
static void
action_restore_tab (GSimpleAction *action,
GVariant *state,
gpointer user_data)
{
NautilusWindow *window = NAUTILUS_WINDOW (user_data);
NautilusWindowSlot *slot;
NautilusNavigationState *data;
if (g_queue_get_length (window->tab_data_queue) == 0)
{
return;
}
data = g_queue_pop_head (window->tab_data_queue);
slot = nautilus_window_create_and_init_slot (window);
nautilus_window_slot_restore_navigation_state (slot, data);
free_navigation_state (data);
g_simple_action_set_enabled (action, g_queue_get_length (window->tab_data_queue) > 0);
}
static void
action_toggle_sidebar (GSimpleAction *action,
GVariant *state,
gpointer user_data)
{
NautilusWindow *window = NAUTILUS_WINDOW (user_data);
gboolean revealed;
revealed = adw_overlay_split_view_get_show_sidebar (ADW_OVERLAY_SPLIT_VIEW (window->split_view));
adw_overlay_split_view_set_show_sidebar (ADW_OVERLAY_SPLIT_VIEW (window->split_view), !revealed);
}
static void
nautilus_window_set_up_sidebar (NautilusWindow *window)
{
nautilus_sidebar_set_open_flags (window->places_sidebar,
(NAUTILUS_OPEN_FLAG_NORMAL
| NAUTILUS_OPEN_FLAG_NEW_TAB
| NAUTILUS_OPEN_FLAG_NEW_WINDOW));
g_signal_connect (window->places_sidebar, "drag-action-requested",
G_CALLBACK (places_sidebar_drag_action_requested_cb), window);
#if 0 && NAUTILUS_DND_NEEDS_GTK4_REIMPLEMENTATION
g_signal_connect (window->places_sidebar, "drag-action-ask",
G_CALLBACK (places_sidebar_drag_action_ask_cb), window);
#endif
g_signal_connect (window->places_sidebar, "drag-perform-drop",
G_CALLBACK (places_sidebar_drag_perform_drop_cb), window);
}
static void
window_slot_close (NautilusWindow *window,
NautilusWindowSlot *slot)
{
NautilusNavigationState *data;
AdwTabPage *page;
g_debug ("Requesting to remove slot %p from window %p", slot, window);
if (window == NULL || slot == NULL)
{
return;
}
data = nautilus_window_slot_get_navigation_state (slot);
if (data != NULL)
{
g_queue_push_head (window->tab_data_queue, data);
}
remove_slot_from_window (slot, window);
page = adw_tab_view_get_page (window->tab_view, GTK_WIDGET (slot));
/* this will destroy the slot */
adw_tab_view_close_page (window->tab_view, page);
/* If that was the last slot in the window, close the window. */
if (window->slots == NULL)
{
g_debug ("Last slot removed, closing the window");
nautilus_window_close (window);
}
else
{
GAction *restore_action = g_action_map_lookup_action (G_ACTION_MAP (window), "restore-tab");
g_simple_action_set_enabled (G_SIMPLE_ACTION (restore_action), TRUE);
}
#ifdef HAVE_MALLOC_TRIM
schedule_trim ();
#endif
}
static void
nautilus_window_sync_location_widgets (NautilusWindow *window)
{
/* This function can only be called when there is a slot. */
g_return_if_fail (window->active_slot != NULL);
GFile *location = nautilus_window_slot_get_location (window->active_slot);
if (location != NULL)
{
gtk_widget_set_visible (window->network_address_bar,
g_file_has_uri_scheme (location, SCHEME_NETWORK_VIEW));
}
}
static gchar *
toast_undo_deleted_get_label (NautilusFileUndoInfo *undo_info)
{
g_autoptr (GList) files = nautilus_file_undo_info_trash_get_files (NAUTILUS_FILE_UNDO_INFO_TRASH (undo_info));
gchar *file_label;
gchar *label;
gint length;
length = g_list_length (files);
if (length == 1)
{
file_label = g_file_get_basename (files->data);
/* Translators: only one item has been moved to trash and %s is its name. */
label = g_markup_printf_escaped (_("“%s” moved to trash"), file_label);
g_free (file_label);
}
else
{
/* Translators: one or more items might have been moved to trash, and %d
* is the count. */
label = g_markup_printf_escaped (ngettext ("%d file moved to trash", "%d files moved to trash", length), length);
}
return label;
}
static gchar *
toast_undo_unstar_get_label (NautilusFileUndoInfo *undo_info)
{
GList *files;
gchar *label;
gint length;
files = nautilus_file_undo_info_starred_get_files (NAUTILUS_FILE_UNDO_INFO_STARRED (undo_info));
length = g_list_length (files);
if (length == 1)
{
const char *file_label = nautilus_file_get_display_name (files->data);
/* Translators: one item has been unstarred and %s is its name. */
label = g_markup_printf_escaped (_("“%s” unstarred"), file_label);
}
else
{
/* Translators: one or more items have been unstarred, and %d
* is the count. */
label = g_markup_printf_escaped (ngettext ("%d file unstarred", "%d files unstarred", length), length);
}
return label;
}
static void
update_undo_redo_menu_items (NautilusWindow *window,
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;
GAction *action;
/* Look at the last action from the undo manager, and get the text that
* describes it, e.g. "Undo Create Folder"/"Redo Create Folder"
*/
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_set_str (&undo_label, _("_Undo"));
}
undo_menu_item = g_menu_item_new (undo_label, "win.undo");
g_menu_append_item (updated_section, undo_menu_item);
action = g_action_map_lookup_action (G_ACTION_MAP (window), "undo");
g_simple_action_set_enabled (G_SIMPLE_ACTION (action), undo_active);
if (!redo_active || redo_label == NULL)
{
g_set_str (&redo_label, _("_Redo"));
}
redo_menu_item = g_menu_item_new (redo_label, "win.redo");
g_menu_append_item (updated_section, redo_menu_item);
action = g_action_map_lookup_action (G_ACTION_MAP (window), "redo");
g_simple_action_set_enabled (G_SIMPLE_ACTION (action), redo_active);
nautilus_gmenu_set_from_model (G_MENU (window->undo_redo_section),
G_MENU_MODEL (updated_section));
}
static void
nautilus_window_on_undo_changed (NautilusFileUndoManager *manager,
NautilusWindow *window)
{
NautilusFileUndoInfo *undo_info;
NautilusFileUndoManagerState state;
AdwToast *toast;
undo_info = nautilus_file_undo_manager_get_action ();
state = nautilus_file_undo_manager_get_state ();
update_undo_redo_menu_items (window, undo_info, state);
if (undo_info != NULL &&
state == NAUTILUS_FILE_UNDO_MANAGER_STATE_UNDO)
{
gboolean popup_toast = FALSE;
g_autofree gchar *label = NULL;
if (nautilus_file_undo_info_get_op_type (undo_info) == NAUTILUS_FILE_UNDO_OP_MOVE_TO_TRASH)
{
g_autoptr (GList) files = NULL;
files = nautilus_file_undo_info_trash_get_files (NAUTILUS_FILE_UNDO_INFO_TRASH (undo_info));
/* Don't pop up a notification if user canceled the operation or the focus
* is not in the this window. This is an easy way to know from which window
* was the delete operation made */
if (files != NULL && gtk_window_is_active (GTK_WINDOW (window)))
{
popup_toast = TRUE;
label = toast_undo_deleted_get_label (undo_info);
}
}
else if (nautilus_file_undo_info_get_op_type (undo_info) == NAUTILUS_FILE_UNDO_OP_STARRED)
{
if (window->active_slot == NULL)
{
return;
}
GFile *location = nautilus_window_slot_get_location (window->active_slot);
/* Don't pop up a notification if the focus is not in the this
* window. This is an easy way to know from which window was the
* unstart operation made */
if (g_file_has_uri_scheme (location, SCHEME_STARRED) &&
gtk_window_is_active (GTK_WINDOW (window)) &&
!nautilus_file_undo_info_starred_is_starred (NAUTILUS_FILE_UNDO_INFO_STARRED (undo_info)))
{
popup_toast = TRUE;
label = toast_undo_unstar_get_label (undo_info);
}
}
if (window->last_undo_toast != NULL)
{
adw_toast_dismiss (window->last_undo_toast);
g_clear_weak_pointer (&window->last_undo_toast);
}
if (popup_toast)
{
toast = adw_toast_new (label);
adw_toast_set_button_label (toast, _("Undo"));
adw_toast_set_action_name (toast, "win.undo");
adw_toast_set_priority (toast, ADW_TOAST_PRIORITY_HIGH);
g_set_weak_pointer (&window->last_undo_toast, toast);
adw_toast_overlay_add_toast (window->toast_overlay, toast);
}
}
}
void
nautilus_window_show_operation_notification (NautilusWindow *window,
gchar *main_label,
GFile *folder_to_open,
gboolean was_quick)
{
gboolean is_current_location;
AdwToast *toast;
if (window->active_slot == NULL || !gtk_window_is_active (GTK_WINDOW (window)))
{
return;
}
is_current_location = g_file_equal (folder_to_open,
nautilus_window_slot_get_location (window->active_slot));
if (is_current_location && was_quick)
{
return;
}
toast = adw_toast_new (main_label);
adw_toast_set_priority (toast, ADW_TOAST_PRIORITY_HIGH);
adw_toast_set_use_markup (toast, FALSE);
if (!is_current_location)
{
GVariant *target;
target = g_variant_new_take_string (g_file_get_uri (folder_to_open));
adw_toast_set_button_label (toast, _("Open Folder"));
adw_toast_set_action_name (toast, "slot.open-location");
adw_toast_set_action_target_value (toast, target);
}
adw_toast_overlay_add_toast (window->toast_overlay, toast);
}
static gboolean
tab_view_close_page_cb (AdwTabView *view,
AdwTabPage *page,
NautilusWindow *window)
{
NautilusWindowSlot *slot;
slot = NAUTILUS_WINDOW_SLOT (adw_tab_page_get_child (page));
window_slot_close (window, slot);
return GDK_EVENT_PROPAGATE;
}
static void
tab_view_page_detached_cb (AdwTabView *tab_view,
AdwTabPage *page,
gint position,
NautilusWindow *window)
{
NautilusWindowSlot *slot = NAUTILUS_WINDOW_SLOT (adw_tab_page_get_child (page));
/* If the tab has been moved to another window, we need to remove the slot
* from the current window here. Otherwise, if the tab has been closed, then
* we have*/
if (g_list_find (window->slots, slot))
{
remove_slot_from_window (slot, window);
}
}
static void
tab_view_page_attached_cb (AdwTabView *tab_view,
AdwTabPage *page,
gint position,
NautilusWindow *window)
{
NautilusWindowSlot *slot = NAUTILUS_WINDOW_SLOT (adw_tab_page_get_child (page));
window->slots = g_list_prepend (window->slots, slot);
g_signal_emit (window, signals[LOCATIONS_CHANGED], 0);
}
static AdwTabView *
tab_view_create_window_cb (AdwTabView *tab_view,
NautilusWindow *window)
{
NautilusApplication *app;
NautilusWindow *new_window;
app = NAUTILUS_APPLICATION (g_application_get_default ());
new_window = nautilus_application_create_window (app);
gtk_window_set_display (GTK_WINDOW (new_window),
gtk_widget_get_display (GTK_WIDGET (tab_view)));
gtk_window_present (GTK_WINDOW (new_window));
return new_window->tab_view;
}
static void
action_tab_move_new_window (GSimpleAction *action,
GVariant *parameter,
gpointer user_data)
{
NautilusWindow *window = user_data;
AdwTabPage *page = get_current_page (window);
AdwTabView *new_view = tab_view_create_window_cb (window->tab_view, window);
adw_tab_view_transfer_page (window->tab_view, page, new_view, 0);
}
static GdkDragAction
extra_drag_value_cb (AdwTabBar *self,
AdwTabPage *page,
const GValue *value,
gpointer user_data)
{
NautilusWindowSlot *slot = NAUTILUS_WINDOW_SLOT (adw_tab_page_get_child (page));
g_autoptr (NautilusFile) file = nautilus_file_get (nautilus_window_slot_get_location (slot));
GdkDragAction action = 0;
if (value != NULL)
{
if (G_VALUE_HOLDS (value, GDK_TYPE_FILE_LIST))
{
GSList *file_list = g_value_get_boxed (value);
if (file_list != NULL)
{
action = nautilus_dnd_get_preferred_action (file, G_FILE (file_list->data));
}
}
else if (G_VALUE_HOLDS (value, G_TYPE_STRING) || G_VALUE_HOLDS (value, GDK_TYPE_TEXTURE))
{
action = GDK_ACTION_COPY;
}
}
return action;
}
static gboolean
extra_drag_drop_cb (AdwTabBar *self,
AdwTabPage *page,
const GValue *value,
gpointer user_data)
{
NautilusWindowSlot *slot = NAUTILUS_WINDOW_SLOT (adw_tab_page_get_child (page));
NautilusFilesView *view = nautilus_window_slot_get_current_view (slot);
GFile *target_location = nautilus_window_slot_get_location (slot);
GdkDragAction action = adw_tab_bar_get_extra_drag_preferred_action (self);
return nautilus_dnd_perform_drop (view, value, action, target_location);
}
const GActionEntry win_entries[] =
{
{ .name = "current-location-menu", .activate = action_show_current_location_menu },
{ .name = "new-tab", .activate = action_new_tab },
{ .name = "undo", .activate = action_undo },
{ .name = "redo", .activate = action_redo },
{ .name = "open-tab-overview", .activate = action_open_tab_overview },
/* Only accessible by shortcuts */
{ .name = "close-current-view", .activate = action_close_current_view },
{ .name = "close-other-tabs", .activate = action_close_other_tabs },
{ .name = "go-home", .activate = action_go_home },
{ .name = "tab-move-left", .activate = action_tab_move_left },
{ .name = "tab-move-right", .activate = action_tab_move_right },
{ .name = "tab-move-new-window", .activate = action_tab_move_new_window },
{ .name = "go-to-tab", .parameter_type = "i", .state = "0", .change_state = action_go_to_tab },
{ .name = "restore-tab", .activate = action_restore_tab },
{ .name = "toggle-sidebar", .activate = action_toggle_sidebar },
};
static void
nautilus_window_initialize_actions (NautilusWindow *window)
{
GApplication *app;
GAction *action;
gchar detailed_action[80];
gchar accel[80];
gint i;
g_action_map_add_action_entries (G_ACTION_MAP (window),
win_entries, G_N_ELEMENTS (win_entries),
window);
#define ACCELS(...) ((const char *[]) { __VA_ARGS__, NULL })
app = g_application_get_default ();
nautilus_application_set_accelerator (app, "win.new-tab", "t");
nautilus_application_set_accelerator (app, "win.open-tab-overview", "o");
nautilus_application_set_accelerator (app, "win.close-current-view", "w");
nautilus_application_set_accelerator (app, "win.undo", "z");
nautilus_application_set_accelerator (app, "win.redo", "z");
/* Only accessible by shortcuts */
nautilus_application_set_accelerator (app, "win.tab-move-left", "Page_Up");
nautilus_application_set_accelerator (app, "win.tab-move-right", "Page_Down");
nautilus_application_set_accelerator (app, "win.current-location-menu", "F10");
nautilus_application_set_accelerator (app, "win.restore-tab", "t");
nautilus_application_set_accelerator (app, "win.toggle-sidebar", "F9");
/* Alt+N for the first 9 tabs */
for (i = 0; i < 9; ++i)
{
g_snprintf (detailed_action, sizeof (detailed_action), "win.go-to-tab(%i)", i);
g_snprintf (accel, sizeof (accel), "%i", i + 1);
nautilus_application_set_accelerator (app, detailed_action, accel);
}
nautilus_window_on_undo_changed (nautilus_file_undo_manager_get (), window);
#undef ACCELS
action = g_action_map_lookup_action (G_ACTION_MAP (window), "toggle-sidebar");
g_object_bind_property (window->split_view, "collapsed",
action, "enabled", G_BINDING_SYNC_CREATE);
action = g_action_map_lookup_action (G_ACTION_MAP (window), "restore-tab");
g_simple_action_set_enabled (G_SIMPLE_ACTION (action), FALSE);
}
static gboolean
is_layout_reversed (void)
{
GtkSettings *settings = gtk_settings_get_default ();
g_autofree char *layout = NULL;
g_auto (GStrv) parts = NULL;
if (settings != NULL)
{
g_object_get (settings, "gtk-decoration-layout", &layout, NULL);
}
if (layout != NULL)
{
parts = g_strsplit (layout, ":", 2);
}
/* Invalid layout, don't even try */
if (parts == NULL || g_strv_length (parts) < 2)
{
return FALSE;
}
g_return_val_if_fail (parts[0] != NULL, FALSE);
return (g_strrstr (parts[0], "close") != NULL);
}
static void
notify_decoration_layout_cb (NautilusWindow *self)
{
gboolean inverted = is_layout_reversed ();
if (ADW_IS_TAB_BAR (self->tab_bar))
{
adw_tab_bar_set_inverted (self->tab_bar, inverted);
}
}
static void
nautilus_window_constructed (GObject *self)
{
NautilusWindow *window;
NautilusApplication *application;
GtkSettings *settings;
window = NAUTILUS_WINDOW (self);
G_OBJECT_CLASS (nautilus_window_parent_class)->constructed (self);
application = NAUTILUS_APPLICATION (g_application_get_default ());
gtk_window_set_application (GTK_WINDOW (window), GTK_APPLICATION (application));
/* Only allow tab DnD in Wayland. We are using a hack in list-base to
* get the preferred action which we can not replicate here because we
* don't have access to the GtkDropTarget to check the actions on the GdkDrag.
* See: https://gitlab.gnome.org/GNOME/gtk/-/merge_requests/4982
*/
#ifdef GDK_WINDOWING_WAYLAND
if (GDK_IS_WAYLAND_DISPLAY (gtk_widget_get_display (GTK_WIDGET (window))))
{
adw_tab_bar_setup_extra_drop_target (window->tab_bar,
GDK_ACTION_COPY | GDK_ACTION_MOVE,
(GType [3]) {GDK_TYPE_TEXTURE, GDK_TYPE_FILE_LIST, G_TYPE_STRING}, 3);
adw_tab_bar_set_extra_drag_preload (window->tab_bar, TRUE);
g_signal_connect (window->tab_bar, "extra-drag-value", G_CALLBACK (extra_drag_value_cb), NULL);
g_signal_connect (window->tab_bar, "extra-drag-drop", G_CALLBACK (extra_drag_drop_cb), NULL);
}
#endif
settings = gtk_settings_get_default ();
g_signal_connect_object (settings, "notify::gtk-decoration-layout",
G_CALLBACK (notify_decoration_layout_cb), window,
G_CONNECT_SWAPPED);
notify_decoration_layout_cb (window);
nautilus_window_set_up_sidebar (window);
g_signal_connect_object (nautilus_file_undo_manager_get (), "undo-changed",
G_CALLBACK (nautilus_window_on_undo_changed), self,
G_CONNECT_AFTER);
/* Is required that the UI is constructed before initializing the actions, since
* some actions trigger UI widgets to show/hide. */
nautilus_window_initialize_actions (window);
}
static void
nautilus_window_dispose (GObject *object)
{
NautilusWindow *window;
GList *slots_copy;
window = NAUTILUS_WINDOW (object);
g_debug ("Destroying window");
/* close all slots safely */
slots_copy = g_list_copy (window->slots);
g_list_foreach (slots_copy, (GFunc) remove_slot_from_window, window);
g_list_free (slots_copy);
/* the slots list should now be empty */
g_warn_if_fail (window->slots == NULL);
g_clear_weak_pointer (&window->active_slot);
gtk_widget_dispose_template (GTK_WIDGET (window), NAUTILUS_TYPE_WINDOW);
G_OBJECT_CLASS (nautilus_window_parent_class)->dispose (object);
}
static void
nautilus_window_finalize (GObject *object)
{
NautilusWindow *window;
window = NAUTILUS_WINDOW (object);
g_clear_handle_id (&window->sidebar_width_handler_id, g_source_remove);
g_clear_object (&window->selected_file);
g_clear_object (&window->selected_volume);
g_signal_handlers_disconnect_by_func (nautilus_file_undo_manager_get (),
G_CALLBACK (nautilus_window_on_undo_changed),
window);
g_queue_free_full (window->tab_data_queue, free_navigation_state);
/* nautilus_window_close() should have run */
g_warn_if_fail (window->slots == NULL);
G_OBJECT_CLASS (nautilus_window_parent_class)->finalize (object);
}
static void
nautilus_window_save_geometry (NautilusWindow *window)
{
gint width;
gint height;
GVariant *initial_size;
gtk_window_get_default_size (GTK_WINDOW (window), &width, &height);
initial_size = g_variant_new_parsed ("(%i, %i)", width, height);
g_settings_set_value (nautilus_window_state,
NAUTILUS_WINDOW_STATE_INITIAL_SIZE,
initial_size);
}
void
nautilus_window_close (NautilusWindow *window)
{
g_return_if_fail (NAUTILUS_IS_WINDOW (window));
nautilus_window_save_geometry (window);
set_active_slot (window, NULL);
/* The pad controller hold a reference to the window, creating a cycle.
* Usually, reference cycles are resolved in dispose(), but GTK removes the
* controllers in finalize(), so our only option is to manually remove it
* here before starting the destruction of the window. */
if (window->pad_controller != NULL)
{
gtk_widget_remove_controller (GTK_WIDGET (window),
GTK_EVENT_CONTROLLER (window->pad_controller));
g_clear_weak_pointer (&window->pad_controller);
}
gtk_window_destroy (GTK_WINDOW (window));
#ifdef HAVE_MALLOC_TRIM
schedule_trim ();
#endif
}
void
set_active_slot (NautilusWindow *window,
NautilusWindowSlot *new_slot)
{
NautilusWindowSlot *old_slot = window->active_slot;
g_return_if_fail (new_slot == NULL ||
gtk_widget_is_ancestor (GTK_WIDGET (new_slot), GTK_WIDGET (window)));
if (old_slot == new_slot)
{
return;
}
g_debug ("Setting new slot %p as active, old slot inactive %p", new_slot, old_slot);
/* make old slot inactive if it exists (may be NULL after init, for example) */
if (old_slot != NULL)
{
/* inform slot & view */
nautilus_window_slot_set_active (old_slot, FALSE);
}
g_set_weak_pointer (&window->active_slot, new_slot);
/* make new slot active, if it exists */
if (new_slot)
{
AdwTabPage *page = adw_tab_view_get_page (window->tab_view,
GTK_WIDGET (new_slot));
adw_tab_view_set_selected_page (window->tab_view, page);
/* inform slot & view */
nautilus_window_slot_set_active (new_slot, TRUE);
on_location_changed (window);
update_cursor (window);
}
g_object_notify_by_pspec (G_OBJECT (window), properties[PROP_ACTIVE_SLOT]);
}
static void
nautilus_window_realize (GtkWidget *widget)
{
GTK_WIDGET_CLASS (nautilus_window_parent_class)->realize (widget);
update_cursor (NAUTILUS_WINDOW (widget));
}
static gboolean
nautilus_window_key_bubble (GtkEventControllerKey *controller,
unsigned int keyval,
unsigned int keycode,
GdkModifierType state,
gpointer user_data)
{
GtkWidget *widget;
NautilusWindow *window;
widget = gtk_event_controller_get_widget (GTK_EVENT_CONTROLLER (controller));
window = NAUTILUS_WINDOW (widget);
if (window->active_slot != NULL &&
nautilus_window_slot_handle_event (window->active_slot, controller, keyval, state))
{
return GDK_EVENT_STOP;
}
return GDK_EVENT_PROPAGATE;
}
/**
* nautilus_window_show:
* @widget: GtkWidget
*
* Call parent and then show/hide window items
* base on user prefs.
*/
static void
nautilus_window_show (GtkWidget *widget)
{
GTK_WIDGET_CLASS (nautilus_window_parent_class)->show (widget);
}
gboolean
nautilus_window_has_open_location (NautilusWindow *self,
GFile *location)
{
g_return_val_if_fail (NAUTILUS_IS_WINDOW (self), FALSE);
g_return_val_if_fail (location != NULL, FALSE);
return get_slot_with_open_location (self, location) != NULL;
}
GFile *
nautilus_window_get_active_location (NautilusWindow *self)
{
g_return_val_if_fail (NAUTILUS_IS_WINDOW (self), NULL);
g_return_val_if_fail (self->active_slot != NULL, NULL);
return nautilus_window_slot_get_location (self->active_slot);
}
GList *
nautilus_window_get_locations (NautilusWindow *self)
{
g_return_val_if_fail (NAUTILUS_IS_WINDOW (self), NULL);
GFileList *locations = NULL;
for (GList *l = self->slots; l != NULL; l = l->next)
{
NautilusWindowSlot *slot = l->data;
GFile *location = nautilus_window_slot_get_location (slot);
if (location != NULL)
{
locations = g_list_prepend (locations, location);
}
}
return locations;
}
static void
on_is_maximized_changed (GObject *object,
GParamSpec *pspec,
gpointer user_data)
{
gboolean is_maximized;
is_maximized = gtk_window_is_maximized (GTK_WINDOW (object));
g_settings_set_boolean (nautilus_window_state,
NAUTILUS_WINDOW_STATE_MAXIMIZED, is_maximized);
}
static gboolean
nautilus_window_close_request (GtkWindow *window)
{
nautilus_window_close (NAUTILUS_WINDOW (window));
/* Window got destroyed, don't call other GtkWindow::close-request handlers */
return FALSE;
}
void
nautilus_window_back_or_forward_in_new_tab (NautilusWindow *window,
int distance)
{
NautilusNavigationState *state;
state = nautilus_window_slot_get_navigation_state (window->active_slot);
/* Manually fix up the back / forward lists and location.
* This way we don't have to unnecessary load the current location
* and then load back / forward */
if (distance == NAUTILUS_NAVIGATION_DIRECTION_FORWARD)
{
state->back_list = g_list_prepend (state->back_list, state->current_location_bookmark);
state->current_location_bookmark = state->forward_list->data;
state->forward_list = state->forward_list->next;
}
else if (distance == NAUTILUS_NAVIGATION_DIRECTION_BACK)
{
state->forward_list = g_list_prepend (state->forward_list, state->current_location_bookmark);
state->current_location_bookmark = state->back_list->data;
state->back_list = state->back_list->next;
}
else
{
g_warn_if_reached ();
}
NautilusWindowSlot *new_slot = nautilus_window_create_and_init_slot (window);
nautilus_window_slot_restore_navigation_state (new_slot, state);
free_navigation_state (state);
}
static void
on_click_gesture_pressed (GtkGestureClick *gesture,
gint n_press,
gdouble x,
gdouble y,
gpointer user_data)
{
GtkWidget *widget;
NautilusWindow *window;
guint button;
widget = gtk_event_controller_get_widget (GTK_EVENT_CONTROLLER (gesture));
window = NAUTILUS_WINDOW (widget);
button = gtk_gesture_single_get_current_button (GTK_GESTURE_SINGLE (gesture));
if (window->active_slot == NULL)
{
return;
}
if (nautilus_global_preferences_get_use_extra_buttons () &&
(button == nautilus_global_preferences_get_back_button ()))
{
nautilus_window_slot_navigate (window->active_slot, -1);
}
else if (nautilus_global_preferences_get_use_extra_buttons () &&
(button == nautilus_global_preferences_get_forward_button ()))
{
nautilus_window_slot_navigate (window->active_slot, 1);
}
}
static void
nautilus_window_init (NautilusWindow *window)
{
GtkWindowGroup *window_group;
GtkEventController *controller;
GtkPadController *pad_controller;
g_type_ensure (NAUTILUS_TYPE_NETWORK_ADDRESS_BAR);
g_type_ensure (NAUTILUS_TYPE_TOOLBAR);
g_type_ensure (NAUTILUS_TYPE_PLACES_SIDEBAR);
g_type_ensure (NAUTILUS_TYPE_PROGRESS_INDICATOR);
g_type_ensure (NAUTILUS_TYPE_SHORTCUT_MANAGER);
gtk_widget_init_template (GTK_WIDGET (window));
g_signal_connect (window, "notify::maximized",
G_CALLBACK (on_is_maximized_changed), NULL);
window->slots = NULL;
window->active_slot = NULL;
gtk_widget_add_css_class (GTK_WIDGET (window), "nautilus-window");
window_group = gtk_window_group_new ();
gtk_window_group_add_window (window_group, GTK_WINDOW (window));
g_object_unref (window_group);
window->tab_data_queue = g_queue_new ();
/* Attention: this creates a reference cycle: the pad controller owns a
* reference to the window (as an action group) and the window (as a widget)
* owns a reference to the pad controller. To break this, we must remove
* the controller from the window before destroying the window. But we need
* to know the controller is still alive before trying to remove it, so a
* weak reference is added. */
pad_controller = gtk_pad_controller_new (G_ACTION_GROUP (window), NULL);
g_set_weak_pointer (&window->pad_controller, pad_controller);
gtk_pad_controller_set_action_entries (window->pad_controller,
pad_actions, G_N_ELEMENTS (pad_actions));
gtk_widget_add_controller (GTK_WIDGET (window),
GTK_EVENT_CONTROLLER (window->pad_controller));
controller = GTK_EVENT_CONTROLLER (gtk_gesture_click_new ());
gtk_widget_add_controller (GTK_WIDGET (window), controller);
gtk_event_controller_set_propagation_phase (controller, GTK_PHASE_CAPTURE);
gtk_gesture_single_set_button (GTK_GESTURE_SINGLE (controller), 0);
g_signal_connect (controller, "pressed",
G_CALLBACK (on_click_gesture_pressed), NULL);
controller = gtk_event_controller_key_new ();
gtk_widget_add_controller (GTK_WIDGET (window), controller);
gtk_event_controller_set_propagation_phase (controller, GTK_PHASE_BUBBLE);
g_signal_connect (controller, "key-pressed",
G_CALLBACK (nautilus_window_key_bubble), NULL);
}
static void
nautilus_window_get_property (GObject *object,
guint prop_id,
GValue *value,
GParamSpec *pspec)
{
NautilusWindow *self = NAUTILUS_WINDOW (object);
switch (prop_id)
{
case PROP_ACTIVE_SLOT:
{
g_value_set_object (value, G_OBJECT (self->active_slot));
}
break;
default:
{
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
}
}
}
static void
nautilus_window_set_property (GObject *object,
guint prop_id,
const GValue *value,
GParamSpec *pspec)
{
NautilusWindow *self = NAUTILUS_WINDOW (object);
switch (prop_id)
{
case PROP_ACTIVE_SLOT:
{
set_active_slot (self, NAUTILUS_WINDOW_SLOT (g_value_get_object (value)));
}
break;
default:
{
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
}
}
}
static void
nautilus_window_class_init (NautilusWindowClass *class)
{
GObjectClass *oclass = G_OBJECT_CLASS (class);
GtkWidgetClass *wclass = GTK_WIDGET_CLASS (class);
GtkWindowClass *winclass = GTK_WINDOW_CLASS (class);
oclass->dispose = nautilus_window_dispose;
oclass->finalize = nautilus_window_finalize;
oclass->constructed = nautilus_window_constructed;
oclass->get_property = nautilus_window_get_property;
oclass->set_property = nautilus_window_set_property;
wclass->show = nautilus_window_show;
wclass->realize = nautilus_window_realize;
wclass->grab_focus = nautilus_window_grab_focus;
winclass->close_request = nautilus_window_close_request;
properties[PROP_ACTIVE_SLOT] =
g_param_spec_object ("active-slot",
NULL, NULL,
NAUTILUS_TYPE_WINDOW_SLOT,
G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
g_object_class_install_properties (oclass, N_PROPS, properties);
gtk_widget_class_set_template_from_resource (wclass,
"/org/gnome/nautilus/ui/nautilus-window.ui");
gtk_widget_class_bind_template_child (wclass, NautilusWindow, undo_redo_section);
gtk_widget_class_bind_template_child (wclass, NautilusWindow, toolbar);
gtk_widget_class_bind_template_child (wclass, NautilusWindow, split_view);
gtk_widget_class_bind_template_child (wclass, NautilusWindow, places_sidebar);
gtk_widget_class_bind_template_child (wclass, NautilusWindow, toast_overlay);
gtk_widget_class_bind_template_child (wclass, NautilusWindow, tab_overview);
gtk_widget_class_bind_template_child (wclass, NautilusWindow, tab_view);
gtk_widget_class_bind_template_child (wclass, NautilusWindow, tab_bar);
gtk_widget_class_bind_template_child (wclass, NautilusWindow, network_address_bar);
gtk_widget_class_bind_template_callback (wclass, create_tab_cb);
gtk_widget_class_bind_template_callback (wclass, tab_view_close_page_cb);
gtk_widget_class_bind_template_callback (wclass, tab_view_setup_menu_cb);
gtk_widget_class_bind_template_callback (wclass, tab_view_notify_selected_page_cb);
gtk_widget_class_bind_template_callback (wclass, tab_view_create_window_cb);
gtk_widget_class_bind_template_callback (wclass, tab_view_page_attached_cb);
gtk_widget_class_bind_template_callback (wclass, tab_view_page_detached_cb);
signals[LOCATIONS_CHANGED] =
g_signal_new ("locations-changed",
G_TYPE_FROM_CLASS (class),
G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
0,
NULL, NULL,
g_cclosure_marshal_VOID__VOID,
G_TYPE_NONE, 0);
}
NautilusWindow *
nautilus_window_new (void)
{
return g_object_new (NAUTILUS_TYPE_WINDOW,
"icon-name", APPLICATION_ID,
NULL);
}
void
nautilus_window_show_about_dialog (NautilusWindow *window)
{
AdwDialog *dialog;
g_autofree gchar *module_names = nautilus_module_get_installed_module_names ();
g_autofree gchar *debug_info = NULL;
const gchar *designers[] =
{
_("The GNOME Project"),
NULL
};
const gchar *developers[] =
{
_("The GNOME Project"),
NULL
};
const gchar *documenters[] =
{
_("The GNOME Project"),
"Sun Microsystems",
NULL
};
if (module_names == NULL)
{
debug_info = g_strdup (_("No plugins currently installed."));
}
else
{
debug_info = g_strconcat (_("Currently installed plugins:"), "\n\n",
module_names, "\n\n",
_("For bug testing only, the following command can be used:"), "\n"
"NAUTILUS_DISABLE_PLUGINS=TRUE nautilus", NULL);
}
dialog = adw_about_dialog_new_from_appdata ("/org/gnome/nautilus/appdata", NULL);
adw_about_dialog_set_version (ADW_ABOUT_DIALOG (dialog), VERSION);
adw_about_dialog_set_debug_info (ADW_ABOUT_DIALOG (dialog), debug_info);
adw_about_dialog_set_copyright (ADW_ABOUT_DIALOG (dialog), "© 1999 The Files Authors");
adw_about_dialog_set_developers (ADW_ABOUT_DIALOG (dialog), developers);
adw_about_dialog_set_designers (ADW_ABOUT_DIALOG (dialog), designers);
adw_about_dialog_set_documenters (ADW_ABOUT_DIALOG (dialog), documenters);
adw_about_dialog_set_support_url (ADW_ABOUT_DIALOG (dialog), "https://discourse.gnome.org/tag/nautilus");
/* Translators should localize the following string which will be displayed at the bottom of
* the about box to give credit to the translator(s). */
adw_about_dialog_set_translator_credits (ADW_ABOUT_DIALOG (dialog), _("translator-credits"));
adw_dialog_present (dialog, window ? GTK_WIDGET (window) : NULL);
}
void
nautilus_window_search (NautilusWindow *window,
NautilusQuery *query)
{
if (window->active_slot != NULL)
{
nautilus_window_slot_search (window->active_slot, query);
}
else
{
g_warning ("Trying search on a slot but no active slot present");
}
}