/* nautilus-files-view.c
 *
 * Copyright (C) 1999, 2000  Free Software Foundation
 * Copyright (C) 2000, 2001  Eazel, Inc.
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License as
 * published by the Free Software Foundation; either version 2 of the
 * License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public
 * License along with this program; if not, see <http://www.gnu.org/licenses/>.
 *
 * Authors: Ettore Perazzoli,
 *          John Sullivan <sullivan@eazel.com>,
 *          Darin Adler <darin@bentspoon.com>,
 *          Pavel Cisler <pavel@eazel.com>,
 *          David Emory Watson <dwatson@cs.ucr.edu>
 */
#define G_LOG_DOMAIN "nautilus-view"

#include "nautilus-files-view.h"

#include <eel/eel-stock-dialogs.h>
#include <gdk/gdk.h>
#include <gio/gio.h>
#include <glib/gi18n.h>
#include <glib/gstdio.h>
#include <gtk/gtk.h>
#include <gnome-autoar/gnome-autoar.h>
#include <libportal/portal.h>
#include <libportal-gtk4/portal-gtk4.h>
#include <nautilus-extension.h>

#include "nautilus-application.h"
#include "nautilus-app-chooser.h"
#include "nautilus-batch-rename-dialog.h"
#include "nautilus-batch-rename-utilities.h"
#include "nautilus-clipboard.h"
#include "nautilus-compress-dialog.h"
#include "nautilus-dbus-launcher.h"
#include "nautilus-directory.h"
#include "nautilus-dnd.h"
#include "nautilus-enums.h"
#include "nautilus-error-reporting.h"
#include "nautilus-file-operations.h"
#include "nautilus-file-utilities.h"
#include "nautilus-file.h"
#include "nautilus-filename-utilities.h"
#include "nautilus-floating-bar.h"
#include "nautilus-global-preferences.h"
#include "nautilus-grid-view.h"
#include "nautilus-icon-info.h"
#include "nautilus-list-view.h"
#include "nautilus-metadata.h"
#include "nautilus-mime-actions.h"
#include "nautilus-network-view.h"
#include "nautilus-module.h"
#include "nautilus-new-folder-dialog.h"
#include "nautilus-query.h"
#include "nautilus-previewer.h"
#include "nautilus-program-choosing.h"
#include "nautilus-properties-window.h"
#include "nautilus-recent-servers.h"
#include "nautilus-rename-file-popover.h"
#include "nautilus-scheme.h"
#include "nautilus-search-directory.h"
#include "nautilus-signaller.h"
#include "nautilus-tag-manager.h"
#include "nautilus-toolbar-menu-sections.h"
#include "nautilus-trash-monitor.h"
#include "nautilus-ui-utilities.h"
#include "nautilus-view-info.h"
#include "nautilus-view-item.h"
#include "nautilus-view-model.h"
#include "nautilus-window-slot.h"

/* Minimum starting update interval */
#define UPDATE_INTERVAL_MIN 100
/* Maximum update interval */
#define UPDATE_INTERVAL_MAX 2000
/* Amount of milliseconds the update interval is increased */
#define UPDATE_INTERVAL_INC 250
/* Interval at which the update interval is increased */
#define UPDATE_INTERVAL_TIMEOUT_INTERVAL 250
/* Milliseconds that have to pass without a change to reset the update interval */
#define UPDATE_INTERVAL_RESET 1000

#define SILENT_WINDOW_OPEN_LIMIT 5

#define MAX_MENU_LEVELS 5
#define TEMPLATE_LIMIT 30

#define SHORTCUTS_PATH "/nautilus/scripts-accels"

/* Delay to show the Loading... floating bar */
#define FLOATING_BAR_LOADING_DELAY 200 /* ms */

/* Delay to clear search results (avoid while flashing while typing) */
#define SEARCH_TRANSITION_TIMEOUT 200 /* ms */

#define MIN_COMMON_FILENAME_PREFIX_LENGTH 4

enum
{
    ADD_FILES,
    BEGIN_LOADING,
    CLEAR,
    END_FILE_CHANGES,
    END_LOADING,
    FILE_CHANGED,
    REMOVE_FILES,
    SELECTION_CHANGED,
    LAST_SIGNAL
};

enum
{
    PROP_WINDOW_SLOT = 1,
    PROP_SEARCHING,
    PROP_LOADING,
    PROP_SELECTION,
    PROP_LOCATION,
    PROP_EXTENSIONS_BACKGROUND_MENU,
    PROP_TEMPLATES_MENU,
    NUM_PROPERTIES
};

static GParamSpec *properties[NUM_PROPERTIES];
static guint signals[LAST_SIGNAL];

static char *scripts_directory_uri = NULL;
static int scripts_directory_uri_length;

static GHashTable *script_accels = NULL;

struct _NautilusFilesView
{
    AdwBin parent;

    /* Main components */
    GtkWidget *overlay;
    NautilusListBase *list_base;

    NautilusWindowSlot *slot;
    NautilusDirectory *directory;
    NautilusFile *directory_as_file;
    GFile *location;

    NautilusViewModel *model;
    GtkSelectionFilterModel *selection;

    NautilusQuery *search_query;
    GFile *location_before_search;
    NautilusDirectory *outgoing_search;

    GtkWidget *rename_file_popover;

    GList *scripts_directory_list;
    GList *templates_directory_list;
    gboolean scripts_menu_updated;
    gboolean templates_menu_updated;

    guint display_selection_idle_id;
    guint update_context_menus_timeout_id;
    guint update_status_idle_id;

    guint search_transition_timeout_id;
    gboolean begin_loading_delayed;

    guint display_pending_source_id;
    guint changes_timeout_id;

    guint update_interval;
    guint64 last_queued;

    gulong files_added_handler_id;
    gulong files_changed_handler_id;
    gulong load_error_handler_id;
    gulong done_loading_handler_id;
    gulong file_changed_handler_id;

    /* Containers with FileAndDirectory* elements */
    GList *new_added_files;
    GList *new_changed_files;

    NautilusFileList *pending_selection;
    GHashTable *pending_reveal;
    GHashTable *awaiting_acknowledge;

    /* whether we are in the active slot */
    gboolean active;

    /* loading indicates whether this view has begun loading a directory.
     * This flag should need not be set inside subclasses. NautilusFilesView automatically
     * sets 'loading' to TRUE before it begins loading a directory's contents and to FALSE
     * after it finishes loading the directory and its view.
     */
    gboolean loading;

    gboolean in_destruction;

    gboolean show_hidden_files;

    gboolean selection_was_removed;

    gboolean metadata_for_directory_as_file_pending;
    gboolean metadata_for_files_in_directory_pending;

    NautilusDirectoryList *subdirectory_list;
    NautilusDirectoryList *subdirectories_loading;

    GMenu *selection_menu_model;
    GMenu *background_menu_model;

    GtkWidget *selection_menu;
    GtkWidget *background_menu;

    GActionGroup *view_action_group;
    GtkShortcutController *shortcuts;

    /* Empty states */
    GtkWidget *empty_view_page;

    /* Floating bar */
    guint floating_bar_set_status_timeout_id;
    guint floating_bar_loading_timeout_id;
    guint floating_bar_set_passthrough_timeout_id;
    GtkWidget *floating_bar;

    /* Toolbar menu */
    NautilusToolbarMenuSections *toolbar_menu_sections;

    /* Exposed menus, for the path bar etc. */
    GMenuModel *extensions_background_menu;
    GMenuModel *templates_menu;

    /* Non exported menu, only for caching */
    GMenuModel *scripts_menu;

    GCancellable *clipboard_cancellable;

    GCancellable *starred_cancellable;
};

/**
 * FileAndDirectory:
 * @file: A #NautilusFile
 * @directory: A #NautilusDirectory where @file is present.
 *
 * The #FileAndDirectory struct is used to relate files to the directories they
 * are displayed in. This is necessary because the same file can appear multiple
 * times in the same view, by expanding folders as a tree in a list of search
 * results. (Adapted from commit 671e4bdaa4d07b039015bedfcb5d42026e5d099e)
 */
typedef struct
{
    NautilusFile *file;
    NautilusDirectory *directory;
} FileAndDirectory;

typedef struct
{
    NautilusFilesView *view;
    NautilusFileList *selection;
} CompressCallbackData;

typedef struct
{
    GList *added_files;
    NautilusFilesView *directory_view;
} CopyMoveDoneData;

/* forward declarations */

static gboolean display_selection_info_idle_callback (gpointer data);
static void     load_directory (NautilusFilesView *view,
                                NautilusDirectory *directory);
static void on_clipboard_owner_changed (GdkClipboard *clipboard,
                                        gpointer      user_data);
static void     schedule_update_context_menus (NautilusFilesView *view);
static void     remove_update_context_menus_timeout_callback (NautilusFilesView *view);
static void     schedule_update_status (NautilusFilesView *view);
static void     remove_update_status_idle_callback (NautilusFilesView *view);
static void     reset_update_interval (NautilusFilesView *view);
static void     schedule_idle_display_of_pending_files (NautilusFilesView *view);
static void     unschedule_display_of_pending_files (NautilusFilesView *view);
static void     disconnect_directory_handlers (NautilusFilesView *view);
static void     metadata_for_directory_as_file_ready_callback (NautilusFile *file,
                                                               gpointer      callback_data);
static void     metadata_for_files_in_directory_ready_callback (NautilusDirectory *directory,
                                                                GList             *files,
                                                                gpointer           callback_data);
static void     nautilus_files_view_trash_state_changed_callback (NautilusTrashMonitor *trash,
                                                                  gboolean              state,
                                                                  gpointer              callback_data);
static void     update_templates_directory (NautilusFilesView *view);

static void     extract_files (NautilusFilesView *view,
                               GList             *files,
                               GFile             *destination_directory);
static void     extract_files_to_chosen_location (NautilusFilesView *view,
                                                  GList             *files);

static gboolean nautilus_files_view_is_read_only (NautilusFilesView *view);
static void copy_move_done_callback (GHashTable *debuting_files,
                                     gboolean    success,
                                     gpointer    data);
static CopyMoveDoneData * pre_copy_move (NautilusFilesView *directory_view);
static void search_transition_emit_delayed_signals_if_pending (NautilusFilesView *view);

static void     nautilus_files_view_display_selection_info (NautilusFilesView *view);
static char *   nautilus_files_view_get_uri (NautilusFilesView *view);
static gboolean nautilus_files_view_should_show_file (NautilusFilesView *view,
                                                      NautilusFile      *file);
static void nautilus_files_view_pop_up_selection_context_menu (NautilusFilesView *view,
                                                               graphene_point_t  *point);
static void nautilus_files_view_pop_up_background_context_menu (NautilusFilesView *view,
                                                                graphene_point_t  *point);

G_DEFINE_FINAL_TYPE (NautilusFilesView, nautilus_files_view, ADW_TYPE_BIN);

static inline NautilusViewItem *
get_view_item (GListModel *model,
               guint       position)
{
    g_autoptr (GtkTreeListRow) row = g_list_model_get_item (model, position);

    g_return_val_if_fail (GTK_IS_TREE_LIST_ROW (row), NULL);
    return NAUTILUS_VIEW_ITEM (gtk_tree_list_row_get_item (row));
}

static gboolean
list_len_is_one (GList *list)
{
    return list != NULL && list->next == NULL;
}

/*
 * Floating Bar code
 */
static void
remove_loading_floating_bar (NautilusFilesView *self)
{
    if (self->floating_bar_loading_timeout_id != 0)
    {
        g_source_remove (self->floating_bar_loading_timeout_id);
        self->floating_bar_loading_timeout_id = 0;
    }

    gtk_widget_set_visible (self->floating_bar, FALSE);
    nautilus_floating_bar_set_show_stop (NAUTILUS_FLOATING_BAR (self->floating_bar), FALSE);
}

static void
real_setup_loading_floating_bar (NautilusFilesView *self)
{
    nautilus_floating_bar_set_primary_label (NAUTILUS_FLOATING_BAR (self->floating_bar),
                                             nautilus_files_view_is_searching (self) ? _("Searching…") : _("Loading…"));
    nautilus_floating_bar_set_details_label (NAUTILUS_FLOATING_BAR (self->floating_bar), NULL);
    nautilus_floating_bar_set_show_stop (NAUTILUS_FLOATING_BAR (self->floating_bar), self->loading);

    gtk_widget_set_visible (self->floating_bar, TRUE);
}

static gboolean
setup_loading_floating_bar_timeout_cb (gpointer user_data)
{
    NautilusFilesView *self = user_data;

    self->floating_bar_loading_timeout_id = 0;
    real_setup_loading_floating_bar (self);

    return FALSE;
}

static void
setup_loading_floating_bar (NautilusFilesView *self)
{
    /* setup loading overlay */
    if (self->floating_bar_set_status_timeout_id != 0)
    {
        g_source_remove (self->floating_bar_set_status_timeout_id);
        self->floating_bar_set_status_timeout_id = 0;
    }

    if (self->floating_bar_loading_timeout_id != 0)
    {
        g_source_remove (self->floating_bar_loading_timeout_id);
        self->floating_bar_loading_timeout_id = 0;
    }

    self->floating_bar_loading_timeout_id =
        g_timeout_add (FLOATING_BAR_LOADING_DELAY, setup_loading_floating_bar_timeout_cb, self);
}

static void
floating_bar_stop_cb (NautilusFloatingBar *floating_bar,
                      NautilusFilesView   *self)
{
    remove_loading_floating_bar (self);
    nautilus_window_slot_stop_loading (self->slot);
}

static void
real_floating_bar_set_short_status (NautilusFilesView *self,
                                    const gchar       *primary_status,
                                    const gchar       *detail_status)
{
    if (self->loading)
    {
        return;
    }

    nautilus_floating_bar_set_show_stop (NAUTILUS_FLOATING_BAR (self->floating_bar),
                                         FALSE);

    if (primary_status == NULL && detail_status == NULL)
    {
        gtk_widget_set_visible (self->floating_bar, FALSE);
        nautilus_floating_bar_remove_hover_timeout (NAUTILUS_FLOATING_BAR (self->floating_bar));
        return;
    }

    nautilus_floating_bar_set_labels (NAUTILUS_FLOATING_BAR (self->floating_bar),
                                      primary_status,
                                      detail_status);

    gtk_widget_set_visible (self->floating_bar, TRUE);
}

typedef struct
{
    gchar *primary_status;
    gchar *detail_status;
    NautilusFilesView *view;
} FloatingBarSetStatusData;

static void
floating_bar_set_status_data_free (gpointer data)
{
    FloatingBarSetStatusData *status_data = data;

    g_free (status_data->primary_status);
    g_free (status_data->detail_status);

    g_slice_free (FloatingBarSetStatusData, data);
}

static gboolean
floating_bar_set_status_timeout_cb (gpointer data)
{
    FloatingBarSetStatusData *status_data = data;

    status_data->view->floating_bar_set_status_timeout_id = 0;
    real_floating_bar_set_short_status (status_data->view,
                                        status_data->primary_status,
                                        status_data->detail_status);

    return FALSE;
}

static gboolean
remove_floating_bar_passthrough (gpointer data)
{
    NautilusFilesView *self = data;

    gtk_widget_set_can_target (self->floating_bar, TRUE);
    self->floating_bar_set_passthrough_timeout_id = 0;

    return G_SOURCE_REMOVE;
}

static void
set_floating_bar_status (NautilusFilesView *self,
                         const gchar       *primary_status,
                         const gchar       *detail_status)
{
    GtkSettings *settings;
    gint double_click_time;
    FloatingBarSetStatusData *status_data;

    if (self->floating_bar_set_status_timeout_id != 0)
    {
        g_source_remove (self->floating_bar_set_status_timeout_id);
        self->floating_bar_set_status_timeout_id = 0;
    }

    settings = gtk_settings_get_for_display (gtk_widget_get_display (GTK_WIDGET (self)));
    g_object_get (settings,
                  "gtk-double-click-time", &double_click_time,
                  NULL);

    status_data = g_slice_new0 (FloatingBarSetStatusData);
    status_data->primary_status = g_strdup (primary_status);
    status_data->detail_status = g_strdup (detail_status);
    status_data->view = self;

    if (self->floating_bar_set_passthrough_timeout_id != 0)
    {
        g_source_remove (self->floating_bar_set_passthrough_timeout_id);
        self->floating_bar_set_passthrough_timeout_id = 0;
    }
    /* Activate passthrough on the floating bar just long enough for a
     * potential double click to happen, so to not interfere with it */
    gtk_widget_set_can_target (self->floating_bar, FALSE);
    self->floating_bar_set_passthrough_timeout_id = g_timeout_add ((guint) double_click_time,
                                                                   remove_floating_bar_passthrough,
                                                                   self);

    /* waiting for half of the double-click-time before setting
     * the status seems to be a good approximation of not setting it
     * too often and not delaying the statusbar too much.
     */
    self->floating_bar_set_status_timeout_id =
        g_timeout_add_full (G_PRIORITY_DEFAULT,
                            (guint) (double_click_time / 2),
                            floating_bar_set_status_timeout_cb,
                            status_data,
                            floating_bar_set_status_data_free);
}

/**
 * escape_underscores:
 * @to_escape: input string
 *
 * This is used to preserve underscore characters in strings, when they would
 * otherwise be used for mnemonics.
 *
 * Returns: A copy of @to_escape, with underscore characters duplicated.
 * If @to_escape doesn't contain underscores, returns a copy of it.
 * If @to_escape is %NULL, returns an empty string.
 */
static char *
escape_underscores (const char *to_escape)
{
    GString *string = g_string_new (to_escape);

    g_string_replace (string, "_", "__", 0);

    return g_string_free_and_steal (string);
}

static void
on_sort_action_state_changed (GActionGroup *action_group,
                              gchar        *action_name,
                              GVariant     *value,
                              gpointer      user_data)
{
    NautilusFilesView *self = NAUTILUS_FILES_VIEW (user_data);
    const gchar *target_name;
    gboolean reversed;

    g_variant_get (value, "(&sb)", &target_name, &reversed);

    nautilus_file_set_metadata (self->directory_as_file,
                                NAUTILUS_METADATA_KEY_ICON_VIEW_SORT_BY,
                                NULL,
                                target_name);
    nautilus_file_set_boolean_metadata (self->directory_as_file,
                                        NAUTILUS_METADATA_KEY_ICON_VIEW_SORT_REVERSED,
                                        reversed);
}

static const char *
get_directory_sort_by (NautilusFile *file,
                       gboolean     *reversed)
{
    NautilusFileSortType default_sort = nautilus_file_get_default_sort_type (file, reversed);

    if (default_sort == NAUTILUS_FILE_SORT_BY_RECENCY ||
        default_sort == NAUTILUS_FILE_SORT_BY_TRASHED_TIME ||
        default_sort == NAUTILUS_FILE_SORT_BY_SEARCH_RELEVANCE)
    {
        /* These defaults are important. Ignore metadata. */
        return nautilus_file_sort_type_get_attribute (default_sort);
    }

    *reversed = nautilus_file_get_boolean_metadata (file,
                                                    NAUTILUS_METADATA_KEY_ICON_VIEW_SORT_REVERSED,
                                                    *reversed);

    return nautilus_file_get_metadata (file,
                                       NAUTILUS_METADATA_KEY_ICON_VIEW_SORT_BY,
                                       nautilus_file_sort_type_get_attribute (default_sort));
}

static void
update_sort_order_from_metadata_and_preferences (NautilusFilesView *self)
{
    gboolean reversed;
    const char *sort_attribute = get_directory_sort_by (self->directory_as_file, &reversed);

    g_signal_handlers_block_by_func (self->view_action_group, on_sort_action_state_changed, self);

    g_action_group_change_action_state (self->view_action_group,
                                        "sort",
                                        g_variant_new ("(sb)",
                                                       sort_attribute,
                                                       reversed));

    g_signal_handlers_unblock_by_func (self->view_action_group, on_sort_action_state_changed, self);
}

static gboolean
focus_is_on_popup (GtkRoot *window)
{
    GtkWidget *focus = gtk_root_get_focus (window);

    if (focus != NULL)
    {
        GtkNative *native = gtk_widget_get_native (focus);

        if (native != NULL)
        {
            return GDK_IS_POPUP (gtk_native_get_surface (native));
        }
    }

    return FALSE;
}

static void
files_view_begin_loading (NautilusFilesView *self)
{
    update_sort_order_from_metadata_and_preferences (self);

    /* We could have changed to the trash directory or to searching, and then
     * we need to update the menus */
    nautilus_files_view_update_context_menus (self);
    nautilus_files_view_update_toolbar_menus (self);

    nautilus_list_base_setup_directory (self->list_base, self->directory);

    GtkRoot *window = gtk_widget_get_root (GTK_WIDGET (self));

    if (self->active && !focus_is_on_popup (window))
    {
        /* This mysterious gtk_widget_grab_focus() call has been carried over
         * many refactorings, along the way having been wrapped in multiple ways
         * (nautilus_window_grab_focus(), nautilus_window_pane_grab_focus(),
         * nautilus_files_view_grab_focus()). It's been added in a series of bugfixes
         * related to extra pane.[0] If I had to guess, I'd say it was to deal
         * with side effects of the grandparent commit[1], which removed a much
         * older bugfix[2].
         *
         * Regardless of its original purpose, we still rely on this call to
         * prevent a "lost focus" situation. This is easily reproduced, in a
         * build where this call is removed, by refreshing a view where nothing
         * is selected. This "lost focus" state means, e.g., some keybindings,
         * such as Ctrl+A, won't work.
         *
         * [0] Commit c69f3a2ba2d0bd23de5a218b8ce13d256481213a
         * [1] Commit 4efd42312584b46f248e2839582a87776a7baebe
         * [2] Commit 4f7ae827f7e13cf06965cc72ca60cf7801906a33
         */
        gtk_widget_grab_focus (GTK_WIDGET (window));
    }
}

static void
update_cut_status_callback (GObject      *source_object,
                            GAsyncResult *res,
                            gpointer      user_data)
{
    NautilusFilesView *self;
    const GValue *value;
    GList *cut_files = NULL;
    g_autoptr (GError) error = NULL;

    value = gdk_clipboard_read_value_finish (GDK_CLIPBOARD (source_object), res, &error);

    if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
    {
        /* The files view has already been disposed, bailout */
        return;
    }

    self = NAUTILUS_FILES_VIEW (user_data);

    if (value != NULL &&
        G_VALUE_HOLDS (value, NAUTILUS_TYPE_CLIPBOARD))
    {
        NautilusClipboard *clip = g_value_get_boxed (value);
        if (clip != NULL && nautilus_clipboard_is_cut (clip))
        {
            cut_files = nautilus_clipboard_peek_files (clip);
        }
    }

    nautilus_view_model_set_cut_files (self->model, cut_files);
}

static void
update_cut_status (NautilusFilesView *self)
{
    GdkClipboard *clipboard = gtk_widget_get_clipboard (GTK_WIDGET (self));
    GdkContentFormats *formats = gdk_clipboard_get_formats (clipboard);

    if (gdk_content_formats_contain_gtype (formats, NAUTILUS_TYPE_CLIPBOARD))
    {
        gdk_clipboard_read_value_async (clipboard, NAUTILUS_TYPE_CLIPBOARD,
                                        G_PRIORITY_DEFAULT,
                                        self->clipboard_cancellable,
                                        update_cut_status_callback,
                                        self);
    }
    else
    {
        nautilus_view_model_set_cut_files (self->model, NULL);
    }
}

static void
files_view_end_loading (NautilusFilesView *self,
                        gboolean           all_files_seen)
{
    update_cut_status (self);
}

/**
 *
 * nautilus_files_view_get_backing_uri:
 *
 * Returns the URI for the target location of new directory, new file, new
 * link and paste operations.
 */

char *
nautilus_files_view_get_backing_uri (NautilusFilesView *self)
{
    g_return_val_if_fail (NAUTILUS_IS_FILES_VIEW (self), NULL);

    g_autoptr (NautilusViewItem) item = nautilus_list_base_get_backing_item (self->list_base);

    if (item != NULL)
    {
        return nautilus_file_get_uri (nautilus_view_item_get_file (item));
    }
    else if (self->directory != NULL)
    {
        return nautilus_directory_get_uri (self->directory);
    }

    return NULL;
}

/**
 * nautilus_files_view_select_all:
 *
 * select all the items in the view
 *
 **/
static void
nautilus_files_view_select_all (NautilusFilesView *self)
{
    gtk_selection_model_select_all (GTK_SELECTION_MODEL (self->model));
}

static void
nautilus_files_view_select_first (NautilusFilesView *self)
{
    if (g_list_model_get_n_items (G_LIST_MODEL (self->model)) > 0)
    {
        nautilus_list_base_set_cursor (self->list_base, 0, TRUE, TRUE);
    }
}

static void
nautilus_files_view_call_set_selection (NautilusFilesView *self,
                                        NautilusFileList  *selection)
{
    g_autoptr (NautilusFileList) files_to_find = g_list_copy (selection);
    g_autoptr (GtkBitset) update_set = NULL;
    g_autoptr (GtkBitset) new_selection_set = NULL;
    g_autoptr (GtkBitset) old_selection_set = NULL;
    guint n_items;

    old_selection_set = gtk_selection_model_get_selection (GTK_SELECTION_MODEL (self->model));
    /* We aren't allowed to modify the actual selection bitset */
    update_set = gtk_bitset_copy (old_selection_set);
    new_selection_set = gtk_bitset_new_empty ();

    /* Convert file list into set of model indices */
    n_items = g_list_model_get_n_items (G_LIST_MODEL (self->model));
    for (guint position = 0; position < n_items; position++)
    {
        g_autoptr (NautilusViewItem) item = get_view_item (G_LIST_MODEL (self->model), position);

        GList *link = g_list_find (files_to_find, nautilus_view_item_get_file (item));
        if (link != NULL)
        {
            /* Found item to select */
            gtk_bitset_add (new_selection_set, position);

            /* Remove found file from the list of files yet to find. */
            files_to_find = g_list_delete_link (files_to_find, link);
            if (files_to_find == NULL)
            {
                /* We've matched everything. */
                break;
            }
        }
    }

    /* Set focus on the first selected row, and scroll it into view. */
    if (!gtk_bitset_is_empty (new_selection_set))
    {
        guint first_position = gtk_bitset_get_nth (new_selection_set, 0);

        /* We pass TRUE for the third parameter (`select`) here, which appears
         * to be redundact, but is necessary in order to fix the bug reported in
         * https://gitlab.gnome.org/GNOME/nautilus/-/issues/2294 . See also
         * GTK ticket: https://gitlab.gnome.org/GNOME/gtk/-/issues/5485 */
        nautilus_list_base_set_cursor (self->list_base, first_position, TRUE, TRUE);
    }

    gtk_bitset_union (update_set, new_selection_set);
    gtk_selection_model_set_selection (GTK_SELECTION_MODEL (self->model),
                                       new_selection_set,
                                       update_set);
}

static gboolean
is_ancestor_selected (GtkTreeListRow *row,
                      GtkBitset      *selection)
{
    g_autoptr (GtkTreeListRow) parent = gtk_tree_list_row_get_parent (row);
    GtkTreeListRow *grandparent;

    /* Walk up the tree looking for a selected ancestor. */
    while (parent != NULL)
    {
        guint parent_position = gtk_tree_list_row_get_position (parent);
        if (gtk_bitset_contains (selection, parent_position))
        {
            return TRUE;
        }

        grandparent = gtk_tree_list_row_get_parent (parent);
        g_object_unref (parent);
        parent = grandparent;
    }

    return FALSE;
}

static NautilusFileList *
get_selection_internal (NautilusFilesView *self,
                        gboolean           for_file_transfer)
{
    g_autoptr (GtkBitset) selection = NULL;
    GtkBitsetIter iter;
    guint i;
    NautilusFileList *selected_files = NULL;

    selection = gtk_selection_model_get_selection (GTK_SELECTION_MODEL (self->model));

    for (gtk_bitset_iter_init_last (&iter, selection, &i);
         gtk_bitset_iter_is_valid (&iter);
         gtk_bitset_iter_previous (&iter, &i))
    {
        g_autoptr (GtkTreeListRow) row = NULL;
        g_autoptr (NautilusViewItem) item = NULL;
        NautilusFile *file;

        row = GTK_TREE_LIST_ROW (g_list_model_get_item (G_LIST_MODEL (self->model), i));

        if (for_file_transfer && is_ancestor_selected (row, selection))
        {
            /* If an ancestor is already selected, don't include its descendants
             * in the selection of files to copy/move. */
            continue;
        }

        item = NAUTILUS_VIEW_ITEM (gtk_tree_list_row_get_item (row));
        file = nautilus_view_item_get_file (item);

        selected_files = g_list_prepend (selected_files, g_object_ref (file));
    }

    return selected_files;
}

/* The difference from get_selection() is that any files in the selection that
 * also has a parent folder in the selection is not included */
static NautilusFileList *
nautilus_files_view_get_selection_for_file_transfer (NautilusFilesView *view)
{
    g_return_val_if_fail (NAUTILUS_IS_FILES_VIEW (view), NULL);

    return get_selection_internal (view, TRUE);
}

static void
nautilus_files_view_invert_selection (NautilusFilesView *self)
{
    GtkSelectionModel *selection_model = GTK_SELECTION_MODEL (self->model);
    g_autoptr (GtkBitset) selected = NULL;
    g_autoptr (GtkBitset) all = NULL;
    g_autoptr (GtkBitset) new_selected = NULL;

    selected = gtk_selection_model_get_selection (selection_model);

    /* We are going to flip the selection state of every item in the model. */
    all = gtk_bitset_new_range (0, g_list_model_get_n_items (G_LIST_MODEL (self->model)));

    /* The new selection is all items minus the ones currently selected. */
    new_selected = gtk_bitset_copy (all);
    gtk_bitset_subtract (new_selected, selected);

    gtk_selection_model_set_selection (selection_model, new_selected, all);
}

/**
 * nautilus_files_view_get_toolbar_menu_sections:
 * @self: a #NautilusFilesView
 *
 * Retrieves the menu sections that should be added to the toolbar menu when
 * this view is active
 *
 * Returns: (transfer none): a #NautilusToolbarMenuSections with the details of
 * which menu sections should be added to the menu
 */
NautilusToolbarMenuSections *
nautilus_files_view_get_toolbar_menu_sections (NautilusFilesView *self)
{
    g_return_val_if_fail (NAUTILUS_IS_FILES_VIEW (self), NULL);

    if (NAUTILUS_IS_NETWORK_VIEW (self->list_base))
    {
        return NULL;
    }

    return self->toolbar_menu_sections;
}

static void
real_set_extensions_background_menu (NautilusFilesView *self,
                                     GMenuModel        *menu)
{
    g_return_if_fail (NAUTILUS_IS_FILES_VIEW (self));

    if (g_set_object (&self->extensions_background_menu, menu))
    {
        g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_EXTENSIONS_BACKGROUND_MENU]);
    }
}

static void
real_set_templates_menu (NautilusFilesView *self,
                         GMenuModel        *menu)
{
    g_return_if_fail (NAUTILUS_IS_FILES_VIEW (self));

    if (g_set_object (&self->templates_menu, menu))
    {
        g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_TEMPLATES_MENU]);
    }
}

static gboolean
showing_trash_directory (NautilusFilesView *self)
{
    return self->directory_as_file != NULL &&
           nautilus_file_is_in_trash (self->directory_as_file);
}

static gboolean
showing_recent_directory (NautilusFilesView *self)
{
    return self->directory_as_file != NULL &&
           nautilus_file_is_in_recent (self->directory_as_file);
}

static gboolean
showing_starred_directory (NautilusFilesView *self)
{
    return self->directory_as_file != NULL &&
           nautilus_file_is_in_starred (self->directory_as_file);
}

static gboolean
nautilus_files_view_supports_creating_files (NautilusFilesView *view)
{
    g_return_val_if_fail (NAUTILUS_IS_FILES_VIEW (view), FALSE);

    return !nautilus_files_view_is_read_only (view)
           && !showing_trash_directory (view)
           && !showing_recent_directory (view)
           && !showing_starred_directory (view);
}

static gboolean
nautilus_files_view_supports_extract_here (NautilusFilesView *view)
{
    g_return_val_if_fail (NAUTILUS_IS_FILES_VIEW (view), FALSE);

    return nautilus_files_view_supports_creating_files (view)
           && !nautilus_files_view_is_searching (view);
}

static gboolean
nautilus_files_view_is_empty (NautilusFilesView *self)
{
    return g_list_model_get_n_items (G_LIST_MODEL (self->model)) == 0;
}

/**
 * nautilus_files_view_is_searching:
 * @self: a #NautilusFilesView
 *
 * Returns: %TRUE if @self is searching, %FALSE otherwise.
 */
gboolean
nautilus_files_view_is_searching (NautilusFilesView *self)
{
    if (!self->directory)
    {
        return FALSE;
    }

    return NAUTILUS_IS_SEARCH_DIRECTORY (self->directory);
}

/**
 * nautilus_files_view_get_view_id:
 * @self: a #NautilusFilesView
 *
 * Retrieves the view id that represents the @self type.
 *
 * Returns: a guint representing the view type
 */
guint
nautilus_files_view_get_view_id (NautilusFilesView *self)
{
    return nautilus_list_base_get_view_info (self->list_base).view_id;
}

/**
 * nautilus_files_view_get_selection:
 * @self: a #NautilusFilesView
 *
 * Get the current selection of the view.
 *
 * Returns: (transfer full) (type GFile): a newly allocated list
 * of the currently selected files.
 */
NautilusFileList *
nautilus_files_view_get_selection (NautilusFilesView *self)
{
    g_return_val_if_fail (NAUTILUS_IS_FILES_VIEW (self), NULL);

    return get_selection_internal (self, FALSE);
}

typedef struct
{
    NautilusFile *file;
    NautilusFilesView *directory_view;
} ScriptLaunchParameters;

typedef struct
{
    NautilusFile *file;
    NautilusFilesView *directory_view;
} CreateTemplateParameters;

static FileAndDirectory *
file_and_directory_new (NautilusFile      *file,
                        NautilusDirectory *directory)
{
    FileAndDirectory *fad;

    fad = g_new0 (FileAndDirectory, 1);
    fad->directory = nautilus_directory_ref (directory);
    fad->file = nautilus_file_ref (file);

    return fad;
}

static NautilusFile *
file_and_directory_get_file (FileAndDirectory *fad)
{
    g_return_val_if_fail (fad != NULL, NULL);

    return fad->file;
}

static void
file_and_directory_free (gpointer data)
{
    FileAndDirectory *fad = data;

    nautilus_directory_unref (fad->directory);
    nautilus_file_unref (fad->file);
    g_free (fad);
}

G_DEFINE_AUTOPTR_CLEANUP_FUNC (FileAndDirectory, file_and_directory_free)

static guint
file_and_directory_hash (gconstpointer key)
{
    const FileAndDirectory *fad = key;

    return g_direct_hash (fad->file) ^ g_direct_hash (fad->directory);
}

static gboolean
file_and_directory_equal (gconstpointer a,
                          gconstpointer b)
{
    const FileAndDirectory *fad_1 = a;
    const FileAndDirectory *fad_2 = b;

    return fad_1->file == fad_2->file && fad_1->directory == fad_2->directory;
}

static ScriptLaunchParameters *
script_launch_parameters_new (NautilusFile      *file,
                              NautilusFilesView *directory_view)
{
    ScriptLaunchParameters *result;

    result = g_new0 (ScriptLaunchParameters, 1);
    result->directory_view = directory_view;
    nautilus_file_ref (file);
    result->file = file;

    return result;
}

static void
script_launch_parameters_free (ScriptLaunchParameters *parameters)
{
    nautilus_file_unref (parameters->file);
    g_free (parameters);
}

static CreateTemplateParameters *
create_template_parameters_new (NautilusFile      *file,
                                NautilusFilesView *directory_view)
{
    CreateTemplateParameters *result;

    result = g_new0 (CreateTemplateParameters, 1);
    result->directory_view = directory_view;
    nautilus_file_ref (file);
    result->file = file;

    return result;
}

static void
create_templates_parameters_free (CreateTemplateParameters *parameters)
{
    nautilus_file_unref (parameters->file);
    g_free (parameters);
}

/* Returns the GtkWindow that this directory view occupies, or NULL
 * if at the moment this directory view is not in a GtkWindow or the
 * GtkWindow cannot be determined. Primarily used for parenting dialogs.
 */
static GtkWindow *
nautilus_files_view_get_containing_window (NautilusFilesView *view)
{
    GtkWidget *window;

    g_assert (NAUTILUS_IS_FILES_VIEW (view));

    window = gtk_widget_get_ancestor (GTK_WIDGET (view), GTK_TYPE_WINDOW);
    if (window == NULL)
    {
        return NULL;
    }

    return GTK_WINDOW (window);
}

static char *
get_view_directory (NautilusFilesView *self)
{
    char *uri, *path;
    GFile *f;

    uri = nautilus_directory_get_uri (self->directory);
    f = g_file_new_for_uri (uri);
    path = g_file_get_path (f);
    g_object_unref (f);
    g_free (uri);

    return path;
}

static void
nautilus_files_view_preview_update (NautilusFilesView *view)
{
    g_autolist (NautilusFile) selection = NULL;

    if (!view->active ||
        !nautilus_previewer_is_visible ())
    {
        return;
    }

    if (gtk_widget_get_root (GTK_WIDGET (view)) == NULL)
    {
        return;
    }

    selection = nautilus_files_view_get_selection (view);
    if (selection == NULL)
    {
        return;
    }

    g_autofree gchar *uri = nautilus_file_get_uri (selection->data);

    nautilus_previewer_call_show_file (uri, view->slot, FALSE);
}

void
nautilus_files_view_preview_selection_event (NautilusFilesView *self,
                                             GtkDirectionType   direction)
{
    nautilus_list_base_preview_selection_event (self->list_base, direction);
}

static void
nautilus_files_view_activate_files (NautilusFilesView *view,
                                    GList             *files,
                                    NautilusOpenFlags  flags,
                                    gboolean           confirm_multiple)
{
    if (nautilus_window_slot_handle_activate_files (view->slot, files))
    {
        return;
    }

    GList *files_to_extract;
    GList *files_to_activate;
    char *path;

    if (files == NULL)
    {
        return;
    }

    files_to_extract = nautilus_file_list_filter (files,
                                                  &files_to_activate,
                                                  (NautilusFileFilterFunc) nautilus_mime_file_extracts,
                                                  NULL);

    if (nautilus_files_view_supports_extract_here (view))
    {
        g_autoptr (GFile) location = NULL;
        g_autoptr (GFile) parent = NULL;

        location = nautilus_file_get_location (NAUTILUS_FILE (g_list_first (files)->data));
        /* Get a parent from a random file. We assume all files has a common parent.
         * But don't assume the parent is the view location, since that's not the
         * case in list view when expand-folder setting is set
         */
        parent = g_file_get_parent (location);
        extract_files (view, files_to_extract, parent);
    }
    else
    {
        extract_files_to_chosen_location (view, files_to_extract);
    }

    path = get_view_directory (view);
    nautilus_mime_activate_files (nautilus_files_view_get_containing_window (view),
                                  view->slot,
                                  files_to_activate,
                                  path,
                                  flags,
                                  confirm_multiple);

    g_free (path);
    g_list_free (files_to_extract);
    g_list_free (files_to_activate);
}

void
nautilus_files_view_activate_selection (NautilusFilesView *self,
                                        NautilusOpenFlags  flags)
{
    g_autolist (NautilusFile) selection = nautilus_files_view_get_selection (self);

    if (nautilus_window_slot_get_mode (self->slot) != NAUTILUS_MODE_BROWSE &&
        flags & NAUTILUS_OPEN_FLAG_NEW_TAB)
    {
        /* Don't allow new tabs in filechooser */
        return;
    }

    nautilus_files_view_activate_files (self, selection, flags, TRUE);
}

void
nautilus_files_view_activate_file (NautilusFilesView *view,
                                   NautilusFile      *file,
                                   NautilusOpenFlags  flags)
{
    g_autoptr (GList) files = NULL;

    files = g_list_append (files, file);
    nautilus_files_view_activate_files (view, files, flags, FALSE);
}

static void
action_open_with_default_application (GSimpleAction *action,
                                      GVariant      *state,
                                      gpointer       user_data)
{
    NautilusFilesView *view;

    view = NAUTILUS_FILES_VIEW (user_data);
    nautilus_files_view_activate_selection (view, 0);
}

static void
action_open_item_location (GSimpleAction *action,
                           GVariant      *state,
                           gpointer       user_data)
{
    NautilusFilesView *self = user_data;
    g_autolist (NautilusFile) selection = NULL;
    NautilusFile *item;
    GFile *activation_location;
    NautilusFile *activation_file;
    NautilusFile *parent;
    g_autoptr (GFile) parent_location = NULL;

    selection = nautilus_files_view_get_selection (self);

    if (!selection)
    {
        return;
    }

    item = NAUTILUS_FILE (selection->data);
    activation_location = nautilus_file_get_activation_location (item);
    activation_file = nautilus_file_get (activation_location);
    parent = nautilus_file_get_parent (activation_file);
    parent_location = nautilus_file_get_location (parent);

    if (nautilus_file_is_in_recent (item))
    {
        /* Selection logic will check against a NautilusFile of the
         * activation uri, not the recent:// one. Fixes bug 784516 */
        nautilus_file_unref (item);
        item = nautilus_file_ref (activation_file);
        selection->data = item;
    }

    nautilus_window_slot_open_location_full (self->slot, parent_location, selection);

    nautilus_file_unref (parent);
    nautilus_file_unref (activation_file);
    g_object_unref (activation_location);
}

static void
action_open_item_new_tab (GSimpleAction *action,
                          GVariant      *state,
                          gpointer       user_data)
{
    NautilusFilesView *view;
    g_autolist (NautilusFile) selection = NULL;

    view = NAUTILUS_FILES_VIEW (user_data);
    selection = nautilus_files_view_get_selection (view);

    nautilus_files_view_activate_files (view,
                                        selection,
                                        NAUTILUS_OPEN_FLAG_NEW_TAB |
                                        NAUTILUS_OPEN_FLAG_DONT_MAKE_ACTIVE,
                                        TRUE);
}

static void
sandboxed_choose_program_callback (GObject      *source_object,
                                   GAsyncResult *res,
                                   gpointer      user_data)
{
    XdpPortal *portal = XDP_PORTAL (source_object);
    GList *files = user_data;

    if (xdp_portal_open_uri_finish (portal, res, NULL))
    {
        /* Extract URI from the opened file and add it to recents. */
        g_autofree gchar *uri = nautilus_file_get_uri (NAUTILUS_FILE (files->data));
        gtk_recent_manager_add_item (gtk_recent_manager_get_default (), uri);
    }

    /* Remove hanlded file from the list top of the list. */
    g_object_unref (files->data);
    files = g_list_delete_link (files, files);

    if (files != NULL)
    {
        /* Ask to choose app and open the next file. */
        g_autofree gchar *uri = nautilus_file_get_uri (NAUTILUS_FILE (files->data));

        xdp_portal_open_uri (portal,
                             g_object_get_data (G_OBJECT (portal), "nautilus-parent"),
                             uri,
                             XDP_OPEN_URI_FLAG_ASK,
                             NULL,
                             sandboxed_choose_program_callback,
                             g_steal_pointer (&files));
    }
}

static void
sandboxed_choose_program (GList     *files,
                          GtkWindow *window)
{
    g_autoptr (XdpPortal) portal = xdp_portal_new ();
    XdpParent *parent = xdp_parent_new_gtk (window);
    g_autofree gchar *uri = nautilus_file_get_uri (NAUTILUS_FILE (files->data));

    g_object_set_data_full (G_OBJECT (portal), "nautilus-parent",
                            parent, (GDestroyNotify) xdp_parent_free);

    /* Ask to choose app and open the first file. */
    xdp_portal_open_uri (portal,
                         parent,
                         uri,
                         XDP_OPEN_URI_FLAG_ASK,
                         NULL,
                         sandboxed_choose_program_callback,
                         g_steal_pointer (&files));
}

static void
app_choosen (AdwDialog *dialog,
             gpointer   user_data)
{
    GtkWindow *parent_window;
    NautilusFileList *files;
    GAppInfo *info;

    parent_window = user_data;
    files = g_object_get_data (G_OBJECT (dialog), "directory-view:files");

    info = nautilus_app_chooser_get_app_info (NAUTILUS_APP_CHOOSER (dialog));

    nautilus_launch_application (info, files, parent_window);

    g_object_unref (info);

    adw_dialog_close (ADW_DIALOG (dialog));
}

static void
choose_program (NautilusFilesView *view,
                GList             *files)
{
    NautilusAppChooser *dialog;
    GtkWindow *parent_window;

    g_assert (NAUTILUS_IS_FILES_VIEW (view));

    parent_window = nautilus_files_view_get_containing_window (view);

    if (nautilus_application_is_sandboxed ())
    {
        sandboxed_choose_program (g_steal_pointer (&files), parent_window);
        return;
    }

    dialog = nautilus_app_chooser_new (files);
    g_object_set_data_full (G_OBJECT (dialog),
                            "directory-view:files",
                            files,
                            (GDestroyNotify) nautilus_file_list_free);

    adw_dialog_present (ADW_DIALOG (dialog), GTK_WIDGET (parent_window));

    g_signal_connect_object (dialog, "app-selected",
                             G_CALLBACK (app_choosen),
                             parent_window, 0);
}

static void
open_with_other_program (NautilusFilesView *view)
{
    g_autolist (NautilusFile) selection = NULL;

    g_assert (NAUTILUS_IS_FILES_VIEW (view));

    selection = nautilus_files_view_get_selection (view);
    choose_program (view, g_steal_pointer (&selection));
}

static void
action_open_with_other_application (GSimpleAction *action,
                                    GVariant      *state,
                                    gpointer       user_data)
{
    g_assert (NAUTILUS_IS_FILES_VIEW (user_data));

    open_with_other_program (NAUTILUS_FILES_VIEW (user_data));
}

static void
action_open_current_directory_with_other_application (GSimpleAction *action,
                                                      GVariant      *state,
                                                      gpointer       user_data)
{
    GList *files;

    g_return_if_fail (NAUTILUS_IS_FILES_VIEW (user_data));

    NautilusFilesView *self = user_data;

    if (self->directory_as_file != NULL)
    {
        files = g_list_append (NULL, nautilus_file_ref (self->directory_as_file));
        choose_program (self, files);
    }
}

static void
action_open_current_directory_with_files (GSimpleAction *action,
                                          GVariant      *state,
                                          gpointer       user_data)
{
    g_return_if_fail (NAUTILUS_IS_FILES_VIEW (user_data));

    NautilusFilesView *self = NAUTILUS_FILES_VIEW (user_data);
    if (self->directory_as_file != NULL)
    {
        nautilus_files_view_activate_files (self,
                                            &(GList){ .data = self->directory_as_file, .next = NULL },
                                            NAUTILUS_OPEN_FLAG_NEW_WINDOW,
                                            FALSE);
    }
}

static void
trash_or_delete_done_cb (GHashTable        *debuting_uris,
                         gboolean           user_cancel,
                         NautilusFilesView *self)
{
    if (user_cancel)
    {
        self->selection_was_removed = FALSE;
    }
}

static void
trash_or_delete_selected_files (NautilusFilesView *view)
{
    /* This might be rapidly called multiple times for the same selection
     * when using keybindings. So we remember if the current selection
     * was already removed (but the view doesn't know about it yet).
     */
    if (!view->selection_was_removed)
    {
        view->selection_was_removed = TRUE;

        g_autolist (NautilusFile) selection = NULL;
        selection = nautilus_files_view_get_selection_for_file_transfer (view);
        g_autolist (GFile) locations = nautilus_location_list_from_file_list (selection);

        nautilus_file_operations_trash_or_delete_async (locations,
                                                        nautilus_files_view_get_containing_window (view),
                                                        NULL,
                                                        (NautilusDeleteCallback) trash_or_delete_done_cb,
                                                        view);
    }
}

static void
action_move_to_trash (GSimpleAction *action,
                      GVariant      *state,
                      gpointer       user_data)
{
    trash_or_delete_selected_files (NAUTILUS_FILES_VIEW (user_data));
}

static void
action_remove_from_recent (GSimpleAction *action,
                           GVariant      *state,
                           gpointer       user_data)
{
    /* gvfs handles this with normal delete operations if the path starts
     * with "recent://" */
    trash_or_delete_selected_files (NAUTILUS_FILES_VIEW (user_data));
}

static void
delete_selected_files (NautilusFilesView *view)
{
    g_autolist (NautilusFile) selection = nautilus_files_view_get_selection_for_file_transfer (view);

    if (selection == NULL)
    {
        return;
    }

    g_autolist (GFile) locations = nautilus_location_list_from_file_list (selection);

    nautilus_file_operations_delete_async (locations, nautilus_files_view_get_containing_window (view), NULL, NULL, NULL);
}

static void
action_delete (GSimpleAction *action,
               GVariant      *state,
               gpointer       user_data)
{
    delete_selected_files (NAUTILUS_FILES_VIEW (user_data));
}

static void
action_star (GSimpleAction *action,
             GVariant      *state,
             gpointer       user_data)
{
    NautilusFilesView *view;
    g_autolist (NautilusFile) selection = NULL;

    view = NAUTILUS_FILES_VIEW (user_data);
    selection = nautilus_files_view_get_selection (view);

    nautilus_tag_manager_star_files (nautilus_tag_manager_get (),
                                     G_OBJECT (view),
                                     selection,
                                     (GAsyncReadyCallback) nautilus_tag_manager_announce_starred_cb,
                                     view,
                                     view->starred_cancellable);
}

static void
action_unstar (GSimpleAction *action,
               GVariant      *state,
               gpointer       user_data)
{
    NautilusFilesView *view;
    g_autolist (NautilusFile) selection = NULL;

    view = NAUTILUS_FILES_VIEW (user_data);
    selection = nautilus_files_view_get_selection (view);

    nautilus_tag_manager_unstar_files (nautilus_tag_manager_get (),
                                       G_OBJECT (view),
                                       selection,
                                       (GAsyncReadyCallback) nautilus_tag_manager_announce_unstarred_cb,
                                       view,
                                       view->starred_cancellable);
}

static void
action_restore_from_trash (GSimpleAction *action,
                           GVariant      *state,
                           gpointer       user_data)
{
    NautilusFilesView *view;
    g_autolist (NautilusFile) selection = NULL;

    view = NAUTILUS_FILES_VIEW (user_data);

    selection = nautilus_files_view_get_selection_for_file_transfer (view);
    nautilus_restore_files_from_trash (selection,
                                       nautilus_files_view_get_containing_window (view));
}

static void
action_select_all (GSimpleAction *action,
                   GVariant      *state,
                   gpointer       user_data)
{
    NautilusFilesView *view;

    g_assert (NAUTILUS_IS_FILES_VIEW (user_data));

    view = NAUTILUS_FILES_VIEW (user_data);

    nautilus_files_view_select_all (view);
}

static void
action_invert_selection (GSimpleAction *action,
                         GVariant      *state,
                         gpointer       user_data)
{
    g_assert (NAUTILUS_IS_FILES_VIEW (user_data));

    nautilus_files_view_invert_selection (user_data);
}

static void
action_preview_selection (GSimpleAction *action,
                          GVariant      *state,
                          gpointer       user_data)
{
    NautilusFilesView *self = user_data;
    g_autolist (NautilusFile) selection = NULL;

    selection = nautilus_files_view_get_selection (self);

    g_autofree gchar *uri = nautilus_file_get_uri (selection->data);

    nautilus_previewer_call_show_file (uri, self->slot, TRUE);
}

static void
action_popup_menu (GSimpleAction *action,
                   GVariant      *state,
                   gpointer       user_data)
{
    NautilusFilesView *view = NAUTILUS_FILES_VIEW (user_data);
    g_autoptr (GtkBitset) selection = gtk_selection_model_get_selection (GTK_SELECTION_MODEL (view->model));
    gboolean no_selection = gtk_bitset_is_empty (selection);

    if (no_selection)
    {
        nautilus_files_view_pop_up_background_context_menu (view, NULL);

        return;
    }

    nautilus_files_view_pop_up_selection_context_menu (view, NULL);
}

static void
pattern_select_response_select (AdwDialog *dialog,
                                gpointer   user_data)
{
    NautilusFilesView *self = g_object_get_data (G_OBJECT (dialog), "view");
    GtkWidget *entry = g_object_get_data (G_OBJECT (dialog), "entry");
    const char *text = gtk_editable_get_text (GTK_EDITABLE (entry));
    g_autolist (NautilusFile) selection = NULL;

    selection = nautilus_directory_match_pattern (self->directory, text);

    for (NautilusDirectoryList *l = self->subdirectory_list; l != NULL; l = l->next)
    {
        NautilusDirectory *subdirectory = l->data;
        selection = g_list_concat (selection, nautilus_directory_match_pattern (subdirectory, text));
    }

    nautilus_files_view_call_set_selection (self, selection);

    adw_dialog_close (dialog);
}

static void
select_pattern (NautilusFilesView *view)
{
    g_autoptr (GtkBuilder) builder = NULL;
    GtkWidget *dialog;
    GtkWidget *example;
    GtkWidget *entry, *select_button;
    char *example_pattern;

    builder = gtk_builder_new_from_resource ("/org/gnome/nautilus/ui/nautilus-files-view-select-items.ui");
    dialog = GTK_WIDGET (gtk_builder_get_object (builder, "select_items_dialog"));

    example = GTK_WIDGET (gtk_builder_get_object (builder, "example"));
    example_pattern = g_strdup_printf ("%s<i>%s</i> ",
                                       _("Examples: "),
                                       "*.png, file\?\?.txt, pict*.\?\?\?");
    gtk_label_set_markup (GTK_LABEL (example), example_pattern);
    g_free (example_pattern);

    entry = GTK_WIDGET (gtk_builder_get_object (builder, "pattern_entry"));
    select_button = GTK_WIDGET (gtk_builder_get_object (builder, "select_button"));

    g_object_set_data (G_OBJECT (dialog), "entry", entry);
    g_object_set_data (G_OBJECT (dialog), "view", view);
    g_signal_connect_swapped (select_button, "clicked",
                              G_CALLBACK (pattern_select_response_select),
                              dialog);

    adw_dialog_present (ADW_DIALOG (dialog), GTK_WIDGET (view));
}

static void
action_select_pattern (GSimpleAction *action,
                       GVariant      *state,
                       gpointer       user_data)
{
    g_assert (NAUTILUS_IS_FILES_VIEW (user_data));

    select_pattern (user_data);
}

typedef struct
{
    NautilusFilesView *directory_view;
    GHashTable *added_locations;
    NautilusFileList *selection;
} NewFolderData;

typedef struct
{
    NautilusFilesView *directory_view;
    GHashTable *to_remove_locations;
    NautilusFile *new_folder;
} NewFolderSelectionData;

static void
track_newly_added_locations (NautilusFilesView *view,
                             GList             *new_files,
                             gpointer           user_data)
{
    GHashTable *added_locations;

    added_locations = user_data;

    while (new_files)
    {
        NautilusFile *new_file;

        new_file = NAUTILUS_FILE (new_files->data);

        g_hash_table_add (added_locations,
                          nautilus_file_get_location (new_file));

        new_files = new_files->next;
    }
}

static void
new_folder_done (GFile    *new_folder,
                 gboolean  success,
                 gpointer  user_data)
{
    NautilusFilesView *directory_view;
    NautilusFile *file;
    NewFolderData *data;

    data = (NewFolderData *) user_data;

    directory_view = data->directory_view;

    if (directory_view == NULL)
    {
        goto fail;
    }

    g_signal_handlers_disconnect_by_func (directory_view,
                                          G_CALLBACK (track_newly_added_locations),
                                          data->added_locations);

    if (new_folder == NULL)
    {
        goto fail;
    }

    file = nautilus_file_get (new_folder);

    if (data->selection != NULL)
    {
        GList *uris, *l;
        char *target_uri;

        uris = NULL;
        for (l = data->selection; l != NULL; l = l->next)
        {
            uris = g_list_prepend (uris, nautilus_file_get_uri ((NautilusFile *) l->data));
        }
        uris = g_list_reverse (uris);

        target_uri = nautilus_file_get_uri (file);

        nautilus_files_view_move_copy_items (directory_view,
                                             uris,
                                             target_uri,
                                             GDK_ACTION_MOVE);
        g_list_free_full (uris, g_free);
        g_free (target_uri);
    }

    if (g_hash_table_contains (data->added_locations, new_folder))
    {
        /* The file was already added */
        nautilus_files_view_call_set_selection (directory_view,
                                                &(NautilusFileList){ .data = file });
    }
    else
    {
        g_hash_table_add (directory_view->pending_reveal, file);
    }

    nautilus_file_unref (file);

fail:
    g_hash_table_destroy (data->added_locations);

    if (data->directory_view != NULL)
    {
        g_object_remove_weak_pointer (G_OBJECT (data->directory_view),
                                      (gpointer *) &data->directory_view);
    }

    nautilus_file_list_free (data->selection);
    g_free (data);
}


static NewFolderData *
new_folder_data_new (NautilusFilesView *directory_view,
                     gboolean           with_selection)
{
    NewFolderData *data;

    data = g_new (NewFolderData, 1);
    data->directory_view = directory_view;
    data->added_locations = g_hash_table_new_full (g_file_hash, (GEqualFunc) g_file_equal,
                                                   g_object_unref, NULL);
    if (with_selection)
    {
        data->selection = nautilus_files_view_get_selection_for_file_transfer (directory_view);
    }
    else
    {
        data->selection = NULL;
    }
    g_object_add_weak_pointer (G_OBJECT (data->directory_view),
                               (gpointer *) &data->directory_view);

    return data;
}

static void
rename_file_popover_callback (NautilusFile *target_file,
                              const char   *new_name,
                              gpointer      user_data)
{
    NautilusFilesView *self = user_data;

    /* Put it on the queue for reveal after the view acknowledges the change */
    g_hash_table_add (self->awaiting_acknowledge, target_file);

    nautilus_rename_file (target_file, new_name, NULL, NULL);
}

static gboolean
get_selected_rectangle (NautilusFilesView *self,
                        GdkRectangle      *rectangle)
{
    GtkWidget *item_ui = nautilus_list_base_get_selected_item_ui (self->list_base);
    graphene_rect_t item_rect;
    graphene_point_t view_point;

    if (item_ui == NULL)
    {
        return FALSE;
    }

    if (!gtk_widget_compute_bounds (item_ui, item_ui, &item_rect) ||
        !gtk_widget_compute_point (item_ui, GTK_WIDGET (self), &item_rect.origin, &view_point))
    {
        return FALSE;
    }

    rectangle->x = view_point.x;
    rectangle->y = view_point.y;
    rectangle->height = item_rect.size.height;
    rectangle->width = item_rect.size.width;

    return TRUE;
}

static void
nautilus_files_view_rename_file_popover_new (NautilusFilesView *view,
                                             NautilusFile      *target_file)
{
    GdkRectangle pointing_to;

    if (!get_selected_rectangle (view, &pointing_to))
    {
        g_return_if_reached ();
    }

    nautilus_rename_file_popover_show_for_file (NAUTILUS_RENAME_FILE_POPOVER (view->rename_file_popover),
                                                target_file,
                                                &pointing_to,
                                                rename_file_popover_callback,
                                                view);
}

static void
create_new_folder_callback (const char *folder_name,
                            gboolean    with_selection,
                            gpointer    user_data)
{
    NautilusFilesView *view;
    NewFolderData *data;
    g_autofree gchar *parent_uri = NULL;
    NautilusFile *parent;

    view = NAUTILUS_FILES_VIEW (user_data);

    data = new_folder_data_new (view, with_selection);

    g_signal_connect_data (view,
                           "add-files",
                           G_CALLBACK (track_newly_added_locations),
                           data->added_locations,
                           (GClosureNotify) NULL,
                           G_CONNECT_AFTER);

    parent_uri = nautilus_files_view_get_backing_uri (view);
    parent = nautilus_file_get_by_uri (parent_uri);
    nautilus_file_operations_new_folder (GTK_WIDGET (view),
                                         NULL,
                                         parent_uri, folder_name,
                                         new_folder_done, data);

    /* After the dialog is destroyed the focus, is probably in the menu item
     * that created the dialog, but we want the focus to be in the newly created
     * folder.
     */
    gtk_widget_grab_focus (GTK_WIDGET (view));

    g_object_unref (parent);
}

static void
nautilus_files_view_new_folder_dialog_new (NautilusFilesView *view,
                                           gboolean           with_selection)
{
    g_autoptr (NautilusDirectory) containing_directory = NULL;
    g_autofree char *uri = NULL;
    g_autofree char *common_prefix = NULL;

    uri = nautilus_files_view_get_backing_uri (view);
    containing_directory = nautilus_directory_get_by_uri (uri);

    if (with_selection)
    {
        g_autolist (NautilusFile) selection = NULL;
        selection = nautilus_files_view_get_selection (view);
        common_prefix = nautilus_get_common_filename_prefix (selection, MIN_COMMON_FILENAME_PREFIX_LENGTH);
    }

    (void) nautilus_new_folder_dialog_new (GTK_WIDGET (view),
                                           containing_directory,
                                           with_selection,
                                           common_prefix,
                                           create_new_folder_callback,
                                           view);
}

typedef struct
{
    NautilusFilesView *view;
    GHashTable *added_locations;
} CompressData;

static void
compress_done (GFile    *new_file,
               gboolean  success,
               gpointer  user_data)
{
    CompressData *data;
    NautilusFilesView *view;
    NautilusFile *file;
    char *uri = NULL;

    data = user_data;
    view = data->view;

    if (view == NULL)
    {
        goto out;
    }

    g_signal_handlers_disconnect_by_func (view,
                                          G_CALLBACK (track_newly_added_locations),
                                          data->added_locations);

    if (!success)
    {
        goto out;
    }

    file = nautilus_file_get (new_file);

    if (g_hash_table_contains (data->added_locations, new_file))
    {
        /* The file was already added */
        nautilus_files_view_call_set_selection (view, &(NautilusFileList){ .data = file });
    }
    else
    {
        g_hash_table_add (view->pending_reveal, file);
    }

    uri = nautilus_file_get_uri (file);
    gtk_recent_manager_add_item (gtk_recent_manager_get_default (), uri);

    nautilus_file_unref (file);
out:
    g_hash_table_destroy (data->added_locations);

    if (data->view != NULL)
    {
        g_object_remove_weak_pointer (G_OBJECT (data->view),
                                      (gpointer *) &data->view);
    }

    g_free (uri);
    g_free (data);
}

static void
create_archive_callback (const char *archive_name,
                         const char *passphrase,
                         gpointer    user_data)
{
    CompressCallbackData *callback_data = user_data;
    NautilusFilesView *view;
    g_autolist (GFile) source_files = NULL;
    CompressData *data;
    g_autoptr (GFile) output = NULL;
    g_autoptr (GFile) parent = NULL;
    NautilusCompressionFormat compression_format;
    AutoarFormat format;
    AutoarFilter filter;

    view = callback_data->view;

    source_files = nautilus_location_list_from_file_list (callback_data->selection);
    /* Get a parent from a random file. We assume all files has a common parent.
     * But don't assume the parent is the view location, since that's not the
     * case in list view when expand-folder setting is set
     */
    parent = g_file_get_parent (G_FILE (g_list_first (source_files)->data));
    output = g_file_get_child (parent, archive_name);

    data = g_new (CompressData, 1);
    data->view = view;
    data->added_locations = g_hash_table_new_full (g_file_hash, (GEqualFunc) g_file_equal,
                                                   g_object_unref, NULL);
    g_object_add_weak_pointer (G_OBJECT (data->view),
                               (gpointer *) &data->view);

    g_signal_connect_data (view,
                           "add-files",
                           G_CALLBACK (track_newly_added_locations),
                           data->added_locations,
                           NULL,
                           G_CONNECT_AFTER);

    compression_format = g_settings_get_enum (nautilus_compression_preferences,
                                              NAUTILUS_PREFERENCES_DEFAULT_COMPRESSION_FORMAT);

    switch (compression_format)
    {
        case NAUTILUS_COMPRESSION_ZIP:
        {
            format = AUTOAR_FORMAT_ZIP;
            filter = AUTOAR_FILTER_NONE;
        }
        break;

        case NAUTILUS_COMPRESSION_ENCRYPTED_ZIP:
        {
            format = AUTOAR_FORMAT_ZIP;
            filter = AUTOAR_FILTER_NONE;
        }
        break;

        case NAUTILUS_COMPRESSION_TAR_XZ:
        {
            format = AUTOAR_FORMAT_TAR;
            filter = AUTOAR_FILTER_XZ;
        }
        break;

        case NAUTILUS_COMPRESSION_7ZIP:
        {
            format = AUTOAR_FORMAT_7ZIP;
            filter = AUTOAR_FILTER_NONE;
        }
        break;

        default:
        {
            g_assert_not_reached ();
        }
    }

    nautilus_file_operations_compress (source_files, output,
                                       format,
                                       filter,
                                       passphrase,
                                       nautilus_files_view_get_containing_window (view),
                                       NULL,
                                       compress_done,
                                       data);
}

static void
compress_callback_data_free (CompressCallbackData *data)
{
    nautilus_file_list_free (data->selection);
    g_free (data);
}

static void
nautilus_files_view_compress_dialog_new (NautilusFilesView *view)
{
    NautilusDirectory *containing_directory;
    g_autolist (NautilusFile) selection = nautilus_files_view_get_selection (view);
    gboolean is_single_selection = list_len_is_one (selection);
    g_autofree char *common_prefix = NULL;
    g_autofree char *uri = NULL;
    CompressCallbackData *data;
    NautilusCompressDialog *compress_dialog;

    uri = nautilus_files_view_get_backing_uri (view);
    containing_directory = nautilus_directory_get_by_uri (uri);

    if (is_single_selection)
    {
        const char *display_name = nautilus_file_get_display_name (selection->data);

        if (nautilus_file_is_directory (selection->data))
        {
            common_prefix = g_strdup (display_name);
        }
        else
        {
            common_prefix = nautilus_filename_strip_extension (display_name);
        }
    }
    else
    {
        common_prefix = nautilus_get_common_filename_prefix (selection,
                                                             MIN_COMMON_FILENAME_PREFIX_LENGTH);
    }

    data = g_new0 (CompressCallbackData, 1);
    data->view = view;
    data->selection = nautilus_files_view_get_selection_for_file_transfer (view);

    compress_dialog = nautilus_compress_dialog_new (nautilus_files_view_get_containing_window (view),
                                                    containing_directory,
                                                    common_prefix,
                                                    create_archive_callback,
                                                    data);
    g_object_weak_ref (G_OBJECT (compress_dialog),
                       (GWeakNotify) compress_callback_data_free,
                       data);
}

static void
nautilus_files_view_new_folder (NautilusFilesView *directory_view,
                                gboolean           with_selection)
{
    nautilus_files_view_new_folder_dialog_new (directory_view, with_selection);
}

static NewFolderData *
setup_new_folder_data (NautilusFilesView *directory_view)
{
    NewFolderData *data;

    data = new_folder_data_new (directory_view, FALSE);

    g_signal_connect_data (directory_view,
                           "add-files",
                           G_CALLBACK (track_newly_added_locations),
                           data->added_locations,
                           (GClosureNotify) NULL,
                           G_CONNECT_AFTER);

    return data;
}

void
nautilus_files_view_new_file_with_initial_contents (NautilusFilesView *view,
                                                    const char        *parent_uri,
                                                    const char        *filename,
                                                    const void        *initial_contents,
                                                    gsize              length)
{
    NewFolderData *data;

    g_assert (parent_uri != NULL);

    data = setup_new_folder_data (view);

    nautilus_file_operations_new_file (GTK_WIDGET (view),
                                       parent_uri, filename,
                                       initial_contents, length,
                                       new_folder_done, data);
}

static void
nautilus_files_view_new_file (NautilusFilesView *directory_view,
                              const char        *parent_uri,
                              NautilusFile      *source)
{
    NewFolderData *data;
    char *source_uri;
    char *container_uri;

    container_uri = NULL;
    if (parent_uri == NULL)
    {
        container_uri = nautilus_files_view_get_backing_uri (directory_view);
        g_assert (container_uri != NULL);
    }

    if (source == NULL)
    {
        nautilus_files_view_new_file_with_initial_contents (directory_view,
                                                            parent_uri != NULL ? parent_uri : container_uri,
                                                            NULL,
                                                            NULL,
                                                            0);
        g_free (container_uri);
        return;
    }

    data = setup_new_folder_data (directory_view);

    source_uri = nautilus_file_get_uri (source);

    nautilus_file_operations_new_file_from_template (GTK_WIDGET (directory_view),
                                                     parent_uri != NULL ? parent_uri : container_uri,
                                                     NULL,
                                                     source_uri,
                                                     new_folder_done, data);

    g_free (source_uri);
    g_free (container_uri);
}

static void
action_empty_trash (GSimpleAction *action,
                    GVariant      *state,
                    gpointer       user_data)
{
    NautilusFilesView *view;
    GtkRoot *window;

    g_assert (NAUTILUS_IS_FILES_VIEW (user_data));

    view = NAUTILUS_FILES_VIEW (user_data);
    window = gtk_widget_get_root (GTK_WIDGET (view));

    nautilus_file_operations_empty_trash (GTK_WIDGET (window), TRUE, NULL);
}

static void
action_new_folder (GSimpleAction *action,
                   GVariant      *state,
                   gpointer       user_data)
{
    g_assert (NAUTILUS_IS_FILES_VIEW (user_data));

    nautilus_files_view_new_folder (NAUTILUS_FILES_VIEW (user_data), FALSE);
}

static void
action_new_folder_with_selection (GSimpleAction *action,
                                  GVariant      *state,
                                  gpointer       user_data)
{
    g_assert (NAUTILUS_IS_FILES_VIEW (user_data));

    nautilus_files_view_new_folder (NAUTILUS_FILES_VIEW (user_data), TRUE);
}

static void
real_open_console (NautilusFile      *file,
                   NautilusFilesView *view)
{
    GtkRoot *window = gtk_widget_get_root (GTK_WIDGET (view));
    GVariant *parameters;
    g_autofree gchar *uri = NULL;

    uri = nautilus_file_get_uri (file);
    parameters = g_variant_new_parsed ("([%s], @a{sv} {})", uri);
    nautilus_dbus_launcher_call (nautilus_dbus_launcher_get (),
                                 NAUTILUS_DBUS_LAUNCHER_CONSOLE,
                                 "Open",
                                 parameters, GTK_WINDOW (window));
}

static void
action_open_console (GSimpleAction *action,
                     GVariant      *state,
                     gpointer       user_data)
{
    NautilusFilesView *self = user_data;
    g_autolist (NautilusFile) selection = nautilus_files_view_get_selection (self);
    gboolean is_single_selection = list_len_is_one (selection);

    g_return_if_fail (is_single_selection);

    real_open_console (NAUTILUS_FILE (selection->data), self);
}

static void
action_current_dir_open_console (GSimpleAction *action,
                                 GVariant      *state,
                                 gpointer       user_data)
{
    g_return_if_fail (NAUTILUS_IS_FILES_VIEW (user_data));

    NautilusFilesView *self = user_data;
    real_open_console (self->directory_as_file, self);
}

static void
action_properties (GSimpleAction *action,
                   GVariant      *state,
                   gpointer       user_data)
{
    g_autolist (NautilusFile) selection = NULL;
    GList *files;

    g_assert (NAUTILUS_IS_FILES_VIEW (user_data));

    NautilusFilesView *self = user_data;
    selection = nautilus_files_view_get_selection (self);
    if (selection == NULL)
    {
        if (self->directory_as_file != NULL)
        {
            files = g_list_append (NULL, nautilus_file_ref (self->directory_as_file));

            nautilus_properties_window_present (files, GTK_WIDGET (self), NULL,
                                                NULL, NULL);

            nautilus_file_list_free (files);
        }
    }
    else
    {
        nautilus_properties_window_present (selection, GTK_WIDGET (self), NULL,
                                            NULL, NULL);
    }
}

static void
action_current_dir_properties (GSimpleAction *action,
                               GVariant      *state,
                               gpointer       user_data)
{
    GList *files;

    g_return_if_fail (NAUTILUS_IS_FILES_VIEW (user_data));

    NautilusFilesView *self = NAUTILUS_FILES_VIEW (user_data);

    if (self->directory_as_file != NULL)
    {
        files = g_list_append (NULL, nautilus_file_ref (self->directory_as_file));

        nautilus_properties_window_present (files, GTK_WIDGET (self), NULL,
                                            NULL, NULL);

        nautilus_file_list_free (files);
    }
}

static void
nautilus_files_view_set_show_hidden_files (NautilusFilesView *self,
                                           gboolean           show_hidden)
{
    if (show_hidden != self->show_hidden_files)
    {
        self->show_hidden_files = show_hidden;

        g_settings_set_boolean (gtk_filechooser_preferences,
                                NAUTILUS_PREFERENCES_SHOW_HIDDEN_FILES,
                                show_hidden);

        if (self->directory != NULL)
        {
            load_directory (self, self->directory);
        }
    }
}

static void
action_show_hidden_files (GSimpleAction *action,
                          GVariant      *state,
                          gpointer       user_data)
{
    gboolean show_hidden;
    NautilusFilesView *view;

    g_assert (NAUTILUS_IS_FILES_VIEW (user_data));

    view = NAUTILUS_FILES_VIEW (user_data);
    show_hidden = g_variant_get_boolean (state);

    nautilus_files_view_set_show_hidden_files (view, show_hidden);

    g_simple_action_set_state (action, state);
}

static void
action_sort_order_changed (GSimpleAction *action,
                           GVariant      *value,
                           gpointer       user_data)
{
    g_autoptr (GVariant) old_value = g_action_get_state (G_ACTION (action));

    /* Don't resort if the action is in the same state as before */
    if (g_variant_equal (value, old_value))
    {
        return;
    }

    /* Actual changes happen through binding to NautilusListBase:sort-state. */
    g_simple_action_set_state (action, value);
}

static void
action_visible_columns (GSimpleAction *action,
                        GVariant      *state,
                        gpointer       user_data)
{
    NautilusFilesView *self = NAUTILUS_FILES_VIEW (user_data);

    g_return_if_fail (NAUTILUS_IS_LIST_VIEW (self->list_base));

    nautilus_list_view_present_column_editor (NAUTILUS_LIST_VIEW (self->list_base));
}

static void
update_zoom_actions_state (NautilusFilesView *self)
{
    NautilusViewInfo view_info = nautilus_list_base_get_view_info (self->list_base);
    gint zoom_level = nautilus_list_base_get_zoom_level (self->list_base);
    GAction *action;

    action = g_action_map_lookup_action (G_ACTION_MAP (self->view_action_group),
                                         "zoom-in");
    g_simple_action_set_enabled (G_SIMPLE_ACTION (action),
                                 zoom_level < view_info.zoom_level_max);

    action = g_action_map_lookup_action (G_ACTION_MAP (self->view_action_group),
                                         "zoom-out");
    g_simple_action_set_enabled (G_SIMPLE_ACTION (action),
                                 zoom_level > view_info.zoom_level_min);

    action = g_action_map_lookup_action (G_ACTION_MAP (self->view_action_group),
                                         "zoom-standard");
    g_simple_action_set_enabled (G_SIMPLE_ACTION (action),
                                 zoom_level != view_info.zoom_level_standard);
}

static void
action_zoom_in (GSimpleAction *action,
                GVariant      *state,
                gpointer       user_data)
{
    NautilusFilesView *self = NAUTILUS_FILES_VIEW (user_data);
    gint zoom_level = nautilus_list_base_get_zoom_level (self->list_base);

    nautilus_list_base_set_zoom_level (self->list_base, zoom_level + 1);
    update_zoom_actions_state (self);
}

static void
action_zoom_out (GSimpleAction *action,
                 GVariant      *state,
                 gpointer       user_data)
{
    NautilusFilesView *self = NAUTILUS_FILES_VIEW (user_data);
    gint zoom_level = nautilus_list_base_get_zoom_level (self->list_base);

    nautilus_list_base_set_zoom_level (self->list_base, zoom_level - 1);
    update_zoom_actions_state (self);
}

static void
action_zoom_standard (GSimpleAction *action,
                      GVariant      *state,
                      gpointer       user_data)
{
    NautilusFilesView *self = NAUTILUS_FILES_VIEW (user_data);
    NautilusViewInfo view_info = nautilus_list_base_get_view_info (self->list_base);

    nautilus_list_base_set_zoom_level (self->list_base, view_info.zoom_level_standard);
    update_zoom_actions_state (self);
}

static void
action_open_item_new_window (GSimpleAction *action,
                             GVariant      *state,
                             gpointer       user_data)
{
    NautilusFilesView *view;
    g_autolist (NautilusFile) selection = NULL;

    view = NAUTILUS_FILES_VIEW (user_data);
    selection = nautilus_files_view_get_selection (view);

    nautilus_files_view_activate_files (view,
                                        selection,
                                        NAUTILUS_OPEN_FLAG_NEW_WINDOW,
                                        TRUE);
}

typedef struct _PasteCallbackData
{
    NautilusFilesView *view;
    gboolean as_link;
    gchar *dest_uri;
} PasteCallbackData;

static void
paste_callback_data_free (PasteCallbackData *data)
{
    g_free (data->dest_uri);
    g_free (data);
}

G_DEFINE_AUTOPTR_CLEANUP_FUNC (PasteCallbackData, paste_callback_data_free)

static void
handle_clipboard_data (NautilusFilesView *view,
                       GList             *item_uris,
                       char              *destination_uri,
                       GdkDragAction      action)
{
    if (item_uris != NULL && destination_uri != NULL)
    {
        nautilus_files_view_move_copy_items (view, item_uris, destination_uri,
                                             action);

        /* If items are cut then remove from clipboard */
        if (action == GDK_ACTION_MOVE)
        {
            gdk_clipboard_set_content (gtk_widget_get_clipboard (GTK_WIDGET (view)),
                                       NULL);
        }
    }
}

static void
paste_value_received_callback (GObject      *source_object,
                               GAsyncResult *result,
                               gpointer      user_data)
{
    GdkClipboard *clipboard;
    GdkDragAction action;
    const GValue *value;
    g_autoptr (PasteCallbackData) data = user_data;
    g_autoptr (GError) error = NULL;

    clipboard = GDK_CLIPBOARD (source_object);

    value = gdk_clipboard_read_value_finish (clipboard, result, &error);
    if (error != NULL)
    {
        g_warning ("Failed to read clipboard: %s", error->message);
        return;
    }

    action = data->as_link ? GDK_ACTION_LINK : GDK_ACTION_COPY;

    if (G_VALUE_HOLDS (value, NAUTILUS_TYPE_CLIPBOARD))
    {
        NautilusClipboard *clip = g_value_get_boxed (value);
        GList *item_uris = nautilus_clipboard_get_uri_list (clip);

        action = nautilus_clipboard_is_cut (clip) ? GDK_ACTION_MOVE : action;
        handle_clipboard_data (data->view, item_uris, data->dest_uri, action);

        g_list_free_full (item_uris, g_free);
    }
    else if (G_VALUE_HOLDS (value, GDK_TYPE_FILE_LIST))
    {
        GSList *source_list = g_value_get_boxed (value);
        GList *item_uris = NULL;

        for (GSList *l = source_list; l != NULL; l = l->next)
        {
            item_uris = g_list_prepend (item_uris, g_file_get_uri (l->data));
        }

        item_uris = g_list_reverse (item_uris);
        handle_clipboard_data (data->view, item_uris, data->dest_uri, action);
        g_list_free_full (item_uris, g_free);
    }
    else if (G_VALUE_HOLDS (value, G_TYPE_FILE))
    {
        GFile *location = g_value_get_object (value);
        GList *item_uris = g_list_append (NULL, g_file_get_uri (location));

        handle_clipboard_data (data->view, item_uris, data->dest_uri, action);
        g_list_free_full (item_uris, g_free);
    }
}

static void
paste_files (NautilusFilesView *view,
             gchar             *dest_uri,
             gboolean           as_link)
{
    g_autoptr (PasteCallbackData) data = NULL;
    GdkClipboard *clipboard;
    GdkContentFormats *formats;
    gchar *real_dest_uri;

    clipboard = gtk_widget_get_clipboard (GTK_WIDGET (view));
    formats = gdk_clipboard_get_formats (clipboard);

    real_dest_uri = dest_uri != NULL ? dest_uri : nautilus_files_view_get_backing_uri (view);

    if (gdk_content_formats_contain_gtype (formats, GDK_TYPE_TEXTURE))
    {
        nautilus_file_operations_paste_image_from_clipboard (GTK_WIDGET (view),
                                                             NULL,
                                                             real_dest_uri,
                                                             copy_move_done_callback,
                                                             pre_copy_move (view));
        return;
    }

    data = g_new0 (PasteCallbackData, 1);
    data->dest_uri = real_dest_uri;
    data->as_link = as_link;
    data->view = view;

    if (gdk_content_formats_contain_gtype (formats, NAUTILUS_TYPE_CLIPBOARD))
    {
        gdk_clipboard_read_value_async (clipboard, NAUTILUS_TYPE_CLIPBOARD,
                                        G_PRIORITY_DEFAULT,
                                        view->clipboard_cancellable,
                                        paste_value_received_callback,
                                        g_steal_pointer (&data));
    }
    else if (gdk_content_formats_contain_gtype (formats, GDK_TYPE_FILE_LIST))
    {
        gdk_clipboard_read_value_async (clipboard, GDK_TYPE_FILE_LIST,
                                        G_PRIORITY_DEFAULT,
                                        view->clipboard_cancellable,
                                        paste_value_received_callback,
                                        g_steal_pointer (&data));
    }
    else if (gdk_content_formats_contain_gtype (formats, G_TYPE_FILE))
    {
        gdk_clipboard_read_value_async (clipboard, G_TYPE_FILE,
                                        G_PRIORITY_DEFAULT,
                                        view->clipboard_cancellable,
                                        paste_value_received_callback,
                                        g_steal_pointer (&data));
    }
}

static void
action_paste_files (GSimpleAction *action,
                    GVariant      *state,
                    gpointer       user_data)
{
    NautilusFilesView *view;

    view = NAUTILUS_FILES_VIEW (user_data);

    paste_files (view, NULL, FALSE);
}

static void
action_paste_files_accel (GSimpleAction *action,
                          GVariant      *state,
                          gpointer       user_data)
{
    NautilusFilesView *view;

    view = NAUTILUS_FILES_VIEW (user_data);

    if (showing_starred_directory (view))
    {
        show_dialog (_("Could not paste files"),
                     _("Cannot paste files into Starred"),
                     nautilus_files_view_get_containing_window (view),
                     GTK_MESSAGE_ERROR);
    }
    else if (showing_recent_directory (view))
    {
        show_dialog (_("Could not paste files"),
                     _("Cannot paste files into Recent"),
                     nautilus_files_view_get_containing_window (view),
                     GTK_MESSAGE_ERROR);
    }
    else if (showing_trash_directory (view))
    {
        show_dialog (_("Could not paste files"),
                     _("Cannot paste files into Trash"),
                     nautilus_files_view_get_containing_window (view),
                     GTK_MESSAGE_ERROR);
    }
    else if (nautilus_files_view_is_read_only (view))
    {
        show_dialog (_("Could not paste files"),
                     _("Permissions do not allow pasting files in this directory"),
                     nautilus_files_view_get_containing_window (view),
                     GTK_MESSAGE_ERROR);
    }
    else
    {
        paste_files (view, NULL, FALSE);
    }
}

static void
action_create_links (GSimpleAction *action,
                     GVariant      *state,
                     gpointer       user_data)
{
    NautilusFilesView *view = user_data;
    g_assert (NAUTILUS_IS_FILES_VIEW (user_data));

    paste_files (view, NULL, TRUE);
}

static void
show_hidden_files_changed_callback (gpointer callback_data)
{
    NautilusFilesView *view;
    gboolean preference_value;

    view = NAUTILUS_FILES_VIEW (callback_data);

    preference_value =
        g_settings_get_boolean (gtk_filechooser_preferences, NAUTILUS_PREFERENCES_SHOW_HIDDEN_FILES);

    nautilus_files_view_set_show_hidden_files (view, preference_value);

    if (view->active)
    {
        schedule_update_context_menus (view);
    }
}

static gboolean
set_up_scripts_directory_global (void)
{
    g_autofree gchar *scripts_directory_path = NULL;
    g_autoptr (GFile) scripts_directory = NULL;
    g_autoptr (GError) error = NULL;

    if (scripts_directory_uri != NULL)
    {
        return TRUE;
    }

    scripts_directory_path = nautilus_get_scripts_directory_path ();
    scripts_directory = g_file_new_for_path (scripts_directory_path);

    g_file_make_directory_with_parents (scripts_directory, NULL, &error);

    if (error == NULL ||
        g_error_matches (error, G_IO_ERROR, G_IO_ERROR_EXISTS))
    {
        g_file_set_attribute_uint32 (scripts_directory,
                                     G_FILE_ATTRIBUTE_UNIX_MODE,
                                     S_IRWXU,
                                     G_FILE_QUERY_INFO_NONE,
                                     NULL, NULL);

        scripts_directory_uri = g_file_get_uri (scripts_directory);
        scripts_directory_uri_length = strlen (scripts_directory_uri);
    }

    return scripts_directory_uri != NULL;
}

static void
scripts_added_or_changed_callback (NautilusDirectory *directory,
                                   GList             *files,
                                   gpointer           callback_data)
{
    NautilusFilesView *self = callback_data;

    self->scripts_menu_updated = FALSE;
    if (self->active)
    {
        schedule_update_context_menus (self);
    }
}

static void
templates_added_or_changed_callback (NautilusDirectory *directory,
                                     GList             *files,
                                     gpointer           callback_data)
{
    NautilusFilesView *self = callback_data;

    self->templates_menu_updated = FALSE;
    if (self->active)
    {
        schedule_update_context_menus (self);
    }
}

static void
add_directory_to_directory_list (NautilusFilesView  *view,
                                 NautilusDirectory  *directory,
                                 GList             **directory_list,
                                 GCallback           changed_callback)
{
    NautilusFileAttributes attributes;

    if (g_list_find (*directory_list, directory) == NULL)
    {
        nautilus_directory_ref (directory);

        attributes =
            NAUTILUS_FILE_ATTRIBUTES_FOR_ICON |
            NAUTILUS_FILE_ATTRIBUTE_INFO |
            NAUTILUS_FILE_ATTRIBUTE_DIRECTORY_ITEM_COUNT;

        nautilus_directory_file_monitor_add (directory, directory_list,
                                             FALSE, attributes,
                                             (NautilusDirectoryCallback) changed_callback, view);

        g_signal_connect_object (directory, "files-added",
                                 G_CALLBACK (changed_callback), view, 0);
        g_signal_connect_object (directory, "files-changed",
                                 G_CALLBACK (changed_callback), view, 0);

        *directory_list = g_list_append (*directory_list, directory);
    }
}

static void
remove_directory_from_directory_list (NautilusFilesView  *view,
                                      NautilusDirectory  *directory,
                                      GList             **directory_list,
                                      GCallback           changed_callback)
{
    *directory_list = g_list_remove (*directory_list, directory);

    g_signal_handlers_disconnect_by_func (directory,
                                          G_CALLBACK (changed_callback),
                                          view);

    nautilus_directory_file_monitor_remove (directory, directory_list);

    nautilus_directory_unref (directory);
}


static void
add_directory_to_scripts_directory_list (NautilusFilesView *self,
                                         NautilusDirectory *directory)
{
    add_directory_to_directory_list (self, directory,
                                     &self->scripts_directory_list,
                                     G_CALLBACK (scripts_added_or_changed_callback));
}

static void
remove_directory_from_scripts_directory_list (NautilusFilesView *self,
                                              NautilusDirectory *directory)
{
    remove_directory_from_directory_list (self, directory,
                                          &self->scripts_directory_list,
                                          G_CALLBACK (scripts_added_or_changed_callback));
}

static void
add_directory_to_templates_directory_list (NautilusFilesView *self,
                                           NautilusDirectory *directory)
{
    add_directory_to_directory_list (self, directory,
                                     &self->templates_directory_list,
                                     G_CALLBACK (templates_added_or_changed_callback));
}

static void
remove_directory_from_templates_directory_list (NautilusFilesView *self,
                                                NautilusDirectory *directory)
{
    remove_directory_from_directory_list (self, directory,
                                          &self->templates_directory_list,
                                          G_CALLBACK (templates_added_or_changed_callback));
}

static void
slot_active_changed (NautilusWindowSlot *slot,
                     GParamSpec         *pspec,
                     NautilusFilesView  *view)
{
    if (view->active == nautilus_window_slot_get_active (slot))
    {
        return;
    }

    view->active = nautilus_window_slot_get_active (slot);

    if (view->active)
    {
        /* Avoid updating the toolbar withouth making sure the toolbar
         * zoom slider has the correct adjustment that changes when the
         * view mode changes
         */
        nautilus_files_view_update_context_menus (view);
        nautilus_files_view_update_toolbar_menus (view);
        nautilus_files_view_preview_update (view);

        schedule_update_context_menus (view);

        gtk_widget_insert_action_group (GTK_WIDGET (gtk_widget_get_root (GTK_WIDGET (view))),
                                        "view",
                                        G_ACTION_GROUP (view->view_action_group));
    }
    else
    {
        remove_update_context_menus_timeout_callback (view);
        gtk_widget_insert_action_group (GTK_WIDGET (gtk_widget_get_root (GTK_WIDGET (view))),
                                        "view",
                                        NULL);
    }
}

static gboolean
nautilus_files_view_grab_focus (GtkWidget *widget)
{
    /* focus the inner view if it exists */
    NautilusFilesView *self = NAUTILUS_FILES_VIEW (widget);

    if (self->list_base != NULL)
    {
        return gtk_widget_grab_focus (GTK_WIDGET (self->list_base));
    }

    return GTK_WIDGET_CLASS (nautilus_files_view_parent_class)->grab_focus (widget);
}

/**
 * nautilus_files_view_set_selection:
 * @self: a #NautilusFilesView
 * @selection: (nullable): a list of files
 *
 * Sets the current selection of the view.
 */
void
nautilus_files_view_set_selection (NautilusFilesView *self,
                                   NautilusFileList  *selection)
{
    if (!self->loading)
    {
        /* If we aren't still loading, set the selection right now,
         * and reveal the new selection.
         */
        nautilus_files_view_call_set_selection (self, selection);
    }
    else
    {
        /* If we are still loading, set the list of pending URIs instead.
         * done_loading() will eventually select the pending URIs and reveal them.
         */
        NautilusFileList *pending_selection = g_list_copy_deep (selection,
                                                                (GCopyFunc) g_object_ref, NULL);
        g_list_free_full (self->pending_selection, g_object_unref);

        self->pending_selection = pending_selection;
    }
}

static void
on_popup_background_context_menu (NautilusListBase *list_base,
                                  double            x,
                                  double            y,
                                  gpointer          user_data)
{
    NautilusFilesView *self = NAUTILUS_FILES_VIEW (user_data);
    graphene_point_t view_point;

    if (!gtk_widget_compute_point (GTK_WIDGET (list_base), GTK_WIDGET (self),
                                   &GRAPHENE_POINT_INIT (x, y),
                                   &view_point))
    {
        g_return_if_reached ();
    }

    nautilus_files_view_pop_up_background_context_menu (self, &view_point);
}

static void
on_popup_selection_context_menu (NautilusListBase *list_base,
                                 double            x,
                                 double            y,
                                 GtkWidget        *target,
                                 gpointer          user_data)
{
    NautilusFilesView *self = NAUTILUS_FILES_VIEW (user_data);
    graphene_point_t view_point;

    if (!gtk_widget_compute_point (GTK_WIDGET (target), GTK_WIDGET (self),
                                   &GRAPHENE_POINT_INIT (x, y),
                                   &view_point))
    {
        g_return_if_reached ();
    }

    nautilus_files_view_pop_up_selection_context_menu (self, &view_point);
}

static void
on_load_subdirectory (NautilusListBase *list_base,
                      NautilusViewItem *item,
                      gpointer          user_data)
{
    NautilusFilesView *self = NAUTILUS_FILES_VIEW (user_data);
    g_autoptr (NautilusDirectory) directory = nautilus_directory_get_for_file (nautilus_view_item_get_file (item));

    if (!nautilus_files_view_has_subdirectory (self, directory))
    {
        nautilus_files_view_add_subdirectory (self, directory);
    }
}

static void
on_unload_subdirectory (NautilusListBase *list_base,
                        NautilusViewItem *item,
                        gpointer          user_data)
{
    NautilusFilesView *self = NAUTILUS_FILES_VIEW (user_data);
    g_autoptr (NautilusDirectory) directory = nautilus_directory_get_for_file (nautilus_view_item_get_file (item));

    if (nautilus_files_view_has_subdirectory (self, directory))
    {
        nautilus_files_view_remove_subdirectory (self, directory);
    }
}

static void
connect_inner_view (NautilusFilesView *self)
{
    /* Model must be set before the sort-state is bound, for a new sorter to be
     * set on the model. */
    nautilus_list_base_set_model (self->list_base, self->model);
    GAction *action = g_action_map_lookup_action (G_ACTION_MAP (self->view_action_group),
                                                  "sort");
    g_object_bind_property (G_SIMPLE_ACTION (action), "state",
                            self->list_base, "sort-state",
                            G_BINDING_BIDIRECTIONAL | G_BINDING_SYNC_CREATE);

    g_signal_connect_object (self->list_base, "activate-selection",
                             G_CALLBACK (nautilus_files_view_activate_selection), self,
                             G_CONNECT_SWAPPED);
    g_signal_connect_object (self->list_base, "perform-drop",
                             G_CALLBACK (nautilus_dnd_perform_drop), self,
                             G_CONNECT_SWAPPED);
    g_signal_connect_object (self->list_base, "popup-background-context-menu",
                             G_CALLBACK (on_popup_background_context_menu), self,
                             G_CONNECT_DEFAULT);
    g_signal_connect_object (self->list_base, "popup-selection-context-menu",
                             G_CALLBACK (on_popup_selection_context_menu), self,
                             G_CONNECT_DEFAULT);

    if (NAUTILUS_IS_LIST_VIEW (self->list_base))
    {
        g_signal_connect_object (self->list_base, "load-subdirectory",
                                 G_CALLBACK (on_load_subdirectory), self,
                                 G_CONNECT_DEFAULT);
        g_signal_connect_object (self->list_base, "unload-subdirectory",
                                 G_CALLBACK (on_unload_subdirectory), self,
                                 G_CONNECT_DEFAULT);
    }

    nautilus_list_base_add_overlay (self->list_base, self->empty_view_page);
}

static void
nautilus_files_view_dispose (GObject *object)
{
    NautilusFilesView *self;
    GdkClipboard *clipboard;
    GList *node, *next;

    self = NAUTILUS_FILES_VIEW (object);

    self->in_destruction = TRUE;
    nautilus_files_view_stop_loading (self);

    g_clear_pointer (&self->selection_menu, gtk_widget_unparent);
    g_clear_pointer (&self->background_menu, gtk_widget_unparent);
    g_clear_pointer (&self->rename_file_popover, gtk_widget_unparent);

    if (self->directory)
    {
        nautilus_directory_unref (self->directory);
        self->directory = NULL;
    }

    for (node = self->scripts_directory_list; node != NULL; node = next)
    {
        next = node->next;
        remove_directory_from_scripts_directory_list (self, node->data);
    }

    for (node = self->templates_directory_list; node != NULL; node = next)
    {
        next = node->next;
        remove_directory_from_templates_directory_list (self, node->data);
    }

    g_clear_pointer (&self->subdirectories_loading, g_list_free);
    while (self->subdirectory_list != NULL)
    {
        nautilus_files_view_remove_subdirectory (self,
                                                 self->subdirectory_list->data);
    }

    remove_update_context_menus_timeout_callback (self);
    remove_update_status_idle_callback (self);

    g_clear_handle_id (&self->search_transition_timeout_id, g_source_remove);

    if (self->display_selection_idle_id != 0)
    {
        g_source_remove (self->display_selection_idle_id);
        self->display_selection_idle_id = 0;
    }

    if (self->floating_bar_set_status_timeout_id != 0)
    {
        g_source_remove (self->floating_bar_set_status_timeout_id);
        self->floating_bar_set_status_timeout_id = 0;
    }

    if (self->floating_bar_loading_timeout_id != 0)
    {
        g_source_remove (self->floating_bar_loading_timeout_id);
        self->floating_bar_loading_timeout_id = 0;
    }

    if (self->floating_bar_set_passthrough_timeout_id != 0)
    {
        g_source_remove (self->floating_bar_set_passthrough_timeout_id);
        self->floating_bar_set_passthrough_timeout_id = 0;
    }

    g_signal_handlers_disconnect_by_func (nautilus_preferences,
                                          schedule_update_context_menus, self);
    g_signal_handlers_disconnect_by_func (gtk_filechooser_preferences,
                                          show_hidden_files_changed_callback, self);
    g_signal_handlers_disconnect_by_func (nautilus_window_state,
                                          nautilus_files_view_display_selection_info, self);
    g_signal_handlers_disconnect_by_func (gnome_lockdown_preferences,
                                          schedule_update_context_menus, self);
    g_signal_handlers_disconnect_by_func (nautilus_trash_monitor_get (),
                                          nautilus_files_view_trash_state_changed_callback, self);

    clipboard = gdk_display_get_clipboard (gdk_display_get_default ());
    g_signal_handlers_disconnect_by_func (clipboard, on_clipboard_owner_changed, self);
    g_cancellable_cancel (self->clipboard_cancellable);

    nautilus_file_unref (self->directory_as_file);
    self->directory_as_file = NULL;

    g_clear_object (&self->search_query);
    g_clear_object (&self->location_before_search);
    g_clear_object (&self->outgoing_search);
    g_clear_object (&self->location);
    g_clear_object (&self->selection);
    g_clear_object (&self->model);

    adw_bin_set_child (ADW_BIN (self), NULL);
    gtk_widget_dispose_template (GTK_WIDGET (self), NAUTILUS_TYPE_FILES_VIEW);

    G_OBJECT_CLASS (nautilus_files_view_parent_class)->dispose (object);
}

static void
nautilus_files_view_finalize (GObject *object)
{
    NautilusFilesView *self = NAUTILUS_FILES_VIEW (object);

    g_clear_object (&self->empty_view_page);
    g_clear_object (&self->view_action_group);
    g_clear_object (&self->background_menu_model);
    g_clear_object (&self->selection_menu_model);
    g_clear_object (&self->toolbar_menu_sections->sort_section);
    g_clear_object (&self->extensions_background_menu);
    g_clear_object (&self->templates_menu);
    g_clear_object (&self->scripts_menu);
    /* We don't own the slot, so no unref */
    self->slot = NULL;

    g_free (self->toolbar_menu_sections);

    g_hash_table_unref (self->pending_reveal);
    g_hash_table_unref (self->awaiting_acknowledge);

    g_clear_object (&self->clipboard_cancellable);

    g_cancellable_cancel (self->starred_cancellable);
    g_clear_object (&self->starred_cancellable);

    G_OBJECT_CLASS (nautilus_files_view_parent_class)->finalize (object);
}

/**
 * nautilus_files_view_display_selection_info:
 *
 * Display information about the current selection, and notify the view frame of the changed selection.
 * @view: NautilusFilesView for which to display selection info.
 *
 **/
static void
nautilus_files_view_display_selection_info (NautilusFilesView *view)
{
    g_autolist (NautilusFile) selection = NULL;
    goffset non_folder_size;
    gboolean non_folder_size_known;
    guint non_folder_count, folder_count, folder_item_count;
    gboolean folder_item_count_known;
    guint file_item_count;
    GList *p;
    const char *first_item_name = NULL;
    char *non_folder_count_str;
    char *non_folder_item_count_str;
    char *non_folder_counts_str;
    char *folder_count_str;
    char *folder_item_count_str;
    char *folder_counts_str;
    char *primary_status;
    char *detail_status;
    NautilusFile *file;

    g_return_if_fail (NAUTILUS_IS_FILES_VIEW (view));

    if (view->list_base != NULL && NAUTILUS_IS_NETWORK_VIEW (view->list_base))
    {
        /* Selection info is not relevant on this view and visually clashes with
         * the action bar. */
        return;
    }

    selection = nautilus_files_view_get_selection (view);

    folder_item_count_known = TRUE;
    folder_count = 0;
    folder_item_count = 0;
    non_folder_count = 0;
    non_folder_size_known = FALSE;
    non_folder_size = 0;
    folder_count_str = NULL;
    folder_item_count_str = NULL;
    folder_counts_str = NULL;
    non_folder_count_str = NULL;
    non_folder_item_count_str = NULL;
    non_folder_counts_str = NULL;

    for (p = selection; p != NULL; p = p->next)
    {
        file = p->data;
        if (nautilus_file_is_directory (file))
        {
            folder_count++;
            if (nautilus_file_get_directory_item_count (file, &file_item_count, NULL))
            {
                folder_item_count += file_item_count;
            }
            else
            {
                folder_item_count_known = FALSE;
            }
        }
        else
        {
            non_folder_count++;
            if (!nautilus_file_can_get_size (file))
            {
                non_folder_size_known = TRUE;
                non_folder_size += nautilus_file_get_size (file);
            }
        }

        if (first_item_name == NULL)
        {
            first_item_name = nautilus_file_get_display_name (file);
        }
    }

    /* Break out cases for localization's sake. But note that there are still pieces
     * being assembled in a particular order, which may be a problem for some localizers.
     */

    if (folder_count != 0)
    {
        if (folder_count == 1 && non_folder_count == 0)
        {
            folder_count_str = g_strdup_printf (_("“%s” selected"), first_item_name);
        }
        else
        {
            folder_count_str = g_strdup_printf (ngettext ("%'d folder selected",
                                                          "%'d folders selected",
                                                          folder_count),
                                                folder_count);
        }

        if (folder_count == 1)
        {
            if (!folder_item_count_known)
            {
                folder_item_count_str = g_strdup ("");
            }
            else
            {
                folder_item_count_str = g_strdup_printf (ngettext ("(containing %'d item)",
                                                                   "(containing %'d items)",
                                                                   folder_item_count),
                                                         folder_item_count);
            }
        }
        else
        {
            if (!folder_item_count_known)
            {
                folder_item_count_str = g_strdup ("");
            }
            else
            {
                /* translators: this is preceded with a string of form 'N folders' (N more than 1) */
                folder_item_count_str = g_strdup_printf (ngettext ("(containing a total of %'d item)",
                                                                   "(containing a total of %'d items)",
                                                                   folder_item_count),
                                                         folder_item_count);
            }
        }
    }

    if (non_folder_count != 0)
    {
        if (folder_count == 0)
        {
            if (non_folder_count == 1)
            {
                non_folder_count_str = g_strdup_printf (_("“%s” selected"),
                                                        first_item_name);
            }
            else
            {
                non_folder_count_str = g_strdup_printf (ngettext ("%'d item selected",
                                                                  "%'d items selected",
                                                                  non_folder_count),
                                                        non_folder_count);
            }
        }
        else
        {
            /* Folders selected also, use "other" terminology */
            non_folder_count_str = g_strdup_printf (ngettext ("%'d other item selected",
                                                              "%'d other items selected",
                                                              non_folder_count),
                                                    non_folder_count);
        }

        if (non_folder_size_known)
        {
            char *size_string;

            size_string = g_format_size (non_folder_size);
            /* This is marked for translation in case a localiser
             * needs to use something other than parentheses. The
             * the message in parentheses is the size of the selected items.
             */
            non_folder_item_count_str = g_strdup_printf (_("(%s)"), size_string);
            g_free (size_string);
        }
        else
        {
            non_folder_item_count_str = g_strdup ("");
        }
    }

    if (folder_count == 0 && non_folder_count == 0)
    {
        primary_status = NULL;
        detail_status = NULL;
    }
    else if (folder_count == 0)
    {
        primary_status = g_strdup (non_folder_count_str);
        detail_status = g_strdup (non_folder_item_count_str);
    }
    else if (non_folder_count == 0)
    {
        primary_status = g_strdup (folder_count_str);
        detail_status = g_strdup (folder_item_count_str);
    }
    else
    {
        if (folder_item_count_known)
        {
            folder_counts_str = g_strconcat (folder_count_str, " ", folder_item_count_str, NULL);
        }
        else
        {
            folder_counts_str = g_strdup (folder_count_str);
        }

        if (non_folder_size_known)
        {
            non_folder_counts_str = g_strconcat (non_folder_count_str, " ", non_folder_item_count_str, NULL);
        }
        else
        {
            non_folder_counts_str = g_strdup (non_folder_count_str);
        }
        /* This is marked for translation in case a localizer
         * needs to change ", " to something else. The comma
         * is between the message about the number of folders
         * and the number of items in those folders and the
         * message about the number of other items and the
         * total size of those items.
         */
        primary_status = g_strdup_printf (_("%s, %s"),
                                          folder_counts_str,
                                          non_folder_counts_str);
        detail_status = NULL;
    }

    g_free (folder_count_str);
    g_free (folder_item_count_str);
    g_free (folder_counts_str);
    g_free (non_folder_count_str);
    g_free (non_folder_item_count_str);
    g_free (non_folder_counts_str);

    set_floating_bar_status (view, primary_status, detail_status);

    g_free (primary_status);
    g_free (detail_status);
}

static void
nautilus_files_view_send_selection_change (NautilusFilesView *view)
{
    g_signal_emit (view, signals[SELECTION_CHANGED], 0);

    /* A change in MIME type could affect the Open with menu, for one thing, so
     * we need to update menus when the selected files change.
     */
    schedule_update_context_menus (view);

    g_object_notify_by_pspec (G_OBJECT (view), properties[PROP_SELECTION]);
}

/**
 * nautilus_files_view_set_location:
 * @self: a #NautilusFilesView
 * @location: the location displayed by @self
 *
 * Sets the location of @self.
 */
void
nautilus_files_view_set_location (NautilusFilesView *self,
                                  GFile             *location)
{
    g_autoptr (NautilusDirectory) directory = nautilus_directory_get (location);

    if (NAUTILUS_IS_SEARCH_DIRECTORY (directory))
    {
        /* Special case.
         *
         * This may happen if switching view mode while searching. In that case,
         * we need to run the previous query again with the new view, because
         * `load_directory()` alone doesn't load the old results results for us.
         *
         * In this case we don't call `load_directory()` here because that's
         * going to be called internally by `nautilus_files_view_set_search_query()`
         */
        NautilusQuery *previous_query;

        previous_query = nautilus_search_directory_get_query (NAUTILUS_SEARCH_DIRECTORY (directory));
        nautilus_files_view_set_search_query (self, previous_query);
    }
    else
    {
        /* Regular case */
        load_directory (self, directory);

        /* Ensure we don't keep carrying old queries on. This must be called
         * after load_directory() is called for the new directory, otherwise it
         * could try to load the existing search query's base directory. */
        nautilus_files_view_set_search_query (self, NULL);
    }
}

static GtkWidget *
build_search_settings_button (void)
{
    GtkWidget *button = gtk_button_new_with_mnemonic (_("Search _Settings"));

    gtk_widget_set_halign (button, GTK_ALIGN_CENTER);
    gtk_widget_add_css_class (button, "pill");
    gtk_actionable_set_action_name (GTK_ACTIONABLE (button), "app.search-settings");

    return button;
}

static GtkWidget *
build_search_everywhere_button (void)
{
    GtkWidget *button = gtk_button_new_with_mnemonic (_("Search _Everywhere"));

    gtk_widget_set_halign (button, GTK_ALIGN_CENTER);
    gtk_widget_add_css_class (button, "pill");
    gtk_widget_add_css_class (button, "suggested-action");
    gtk_actionable_set_action_name (GTK_ACTIONABLE (button), "slot.search-global");

    return button;
}

static void
nautilus_files_view_update_status_overlay (NautilusFilesView *self)
{
    AdwStatusPage *status_page = ADW_STATUS_PAGE (self->empty_view_page);

    if (!self->loading &&
        nautilus_files_view_is_empty (self))
    {
        adw_status_page_set_child (status_page, NULL);

        if (NAUTILUS_IS_SEARCH_DIRECTORY (self->directory))
        {
            NautilusSearchDirectory *search = NAUTILUS_SEARCH_DIRECTORY (self->directory);
            NautilusQuery *query = nautilus_search_directory_get_query (search);
            gboolean global_search = nautilus_query_is_global (query);

            if (global_search)
            {
                adw_status_page_set_icon_name (status_page, "edit-find-symbolic");
                adw_status_page_set_description (status_page,
                                                 _("More locations can be added to search in the settings"));
                adw_status_page_set_child (status_page, build_search_settings_button ());
            }
            else
            {
                g_autoptr (GFile) location = nautilus_query_get_location (query);
                g_autoptr (NautilusFile) file = nautilus_file_get (location);
                /* Translators: %s is the name of the search location formatted for display */
                g_autofree gchar *local_description = g_strdup_printf (_("No matches in “%s”"),
                                                                       nautilus_file_get_display_name (file));

                adw_status_page_set_icon_name (status_page, "nautilus-folder-search-symbolic");
                adw_status_page_set_description (status_page, local_description);
                adw_status_page_set_child (status_page, (build_search_everywhere_button ()));
            }

            adw_status_page_set_title (status_page, _("No Results Found"));
        }
        else if (nautilus_is_root_for_scheme (self->location, SCHEME_TRASH))
        {
            adw_status_page_set_icon_name (status_page, "user-trash-symbolic");
            adw_status_page_set_title (status_page, _("Trash is Empty"));
            adw_status_page_set_description (status_page, NULL);
        }
        else if (g_file_has_uri_scheme (self->location, SCHEME_STARRED))
        {
            adw_status_page_set_icon_name (status_page, "starred-symbolic");
            adw_status_page_set_title (status_page, _("No Starred Files"));
            adw_status_page_set_description (status_page, NULL);
        }
        else if (g_file_has_uri_scheme (self->location, SCHEME_RECENT))
        {
            adw_status_page_set_icon_name (status_page, "document-open-recent-symbolic");
            adw_status_page_set_title (status_page, _("No Recent Files"));
            adw_status_page_set_description (status_page, NULL);
        }
        else if (g_file_has_uri_scheme (self->location, SCHEME_NETWORK_VIEW))
        {
            adw_status_page_set_icon_name (status_page, "network-computer-symbolic");
            adw_status_page_set_title (status_page, _("No Known Connections"));
            adw_status_page_set_description (status_page, _("Enter an address to connect to a network location."));
        }
        else
        {
            adw_status_page_set_icon_name (status_page, "folder-symbolic");
            adw_status_page_set_title (status_page, _("Folder is Empty"));
            adw_status_page_set_description (status_page, NULL);
        }

        gtk_widget_set_visible (self->empty_view_page, TRUE);
    }
    else
    {
        gtk_widget_set_visible (self->empty_view_page, FALSE);
    }
}

static void
done_loading (NautilusFilesView *self,
              gboolean           all_files_seen)
{
    if (!self->loading)
    {
        return;
    }

    if (!self->in_destruction)
    {
        g_autoptr (GtkBitset) selection = gtk_selection_model_get_selection (GTK_SELECTION_MODEL (self->model));
        gboolean no_selection = gtk_bitset_is_empty (selection);

        remove_loading_floating_bar (self);
        schedule_update_context_menus (self);
        schedule_update_status (self);
        nautilus_files_view_update_toolbar_menus (self);
        reset_update_interval (self);

        if (nautilus_files_view_is_searching (self) &&
            all_files_seen && no_selection && self->pending_selection == NULL)
        {
            nautilus_files_view_select_first (self);
        }
        else if (self->pending_selection != NULL && all_files_seen)
        {
            g_autolist (NautilusFile) pending_selection = NULL;
            pending_selection = g_steal_pointer (&self->pending_selection);

            nautilus_files_view_call_set_selection (self, pending_selection);
        }

        g_clear_pointer (&self->pending_selection, nautilus_file_list_free);

        nautilus_files_view_display_selection_info (self);
    }

    self->loading = FALSE;
    g_clear_handle_id (&self->search_transition_timeout_id, g_source_remove);
    g_signal_emit (self, signals[END_LOADING], 0, all_files_seen);
    g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_LOADING]);

    if (!self->in_destruction)
    {
        nautilus_files_view_update_status_overlay (self);
    }
}


typedef struct
{
    GHashTable *debuting_files;
    NautilusFileList *added_files;
} DebutingFilesData;

static void
debuting_files_data_free (DebutingFilesData *data)
{
    g_hash_table_unref (data->debuting_files);
    nautilus_file_list_free (data->added_files);
    g_free (data);
}

/* This signal handler watch for the arrival of the icons created
 * as the result of a file operation. Once the last one is detected
 * it selects and reveals them all.
 */
static void
debuting_files_add_files_callback (NautilusFilesView *view,
                                   GList             *new_files,
                                   DebutingFilesData *data)
{
    GFile *location;
    GList *l;

    for (l = new_files; l != NULL; l = l->next)
    {
        location = nautilus_file_get_location (NAUTILUS_FILE (l->data));

        if (g_hash_table_remove (data->debuting_files, location))
        {
            nautilus_file_ref (NAUTILUS_FILE (l->data));
            data->added_files = g_list_prepend (data->added_files, NAUTILUS_FILE (l->data));
        }
        g_object_unref (location);
    }

    if (g_hash_table_size (data->debuting_files) == 0)
    {
        nautilus_files_view_call_set_selection (view, data->added_files);
        g_signal_handlers_disconnect_by_func (view,
                                              G_CALLBACK (debuting_files_add_files_callback),
                                              data);
    }
}

static void
copy_move_done_data_free (CopyMoveDoneData *data)
{
    g_assert (data != NULL);

    if (data->directory_view != NULL)
    {
        g_object_remove_weak_pointer (G_OBJECT (data->directory_view),
                                      (gpointer *) &data->directory_view);
    }

    nautilus_file_list_free (data->added_files);
    g_free (data);
}

static void
pre_copy_move_add_files_callback (NautilusFilesView *view,
                                  GList             *new_files,
                                  CopyMoveDoneData  *data)
{
    GList *l;

    for (l = new_files; l != NULL; l = l->next)
    {
        nautilus_file_ref (NAUTILUS_FILE (l->data));
        data->added_files = g_list_prepend (data->added_files, l->data);
    }
}

/* This needs to be called prior to nautilus_file_operations_copy_move.
 * It hooks up a signal handler to catch any icons that get added before
 * the copy_done_callback is invoked. The return value should  be passed
 * as the data for uri_copy_move_done_callback.
 */
static CopyMoveDoneData *
pre_copy_move (NautilusFilesView *directory_view)
{
    CopyMoveDoneData *copy_move_done_data;

    copy_move_done_data = g_new0 (CopyMoveDoneData, 1);
    copy_move_done_data->directory_view = directory_view;

    g_object_add_weak_pointer (G_OBJECT (copy_move_done_data->directory_view),
                               (gpointer *) &copy_move_done_data->directory_view);

    /* We need to run after the default handler adds the folder we want to
     * operate on. The ADD_FILES signal is registered as G_SIGNAL_RUN_LAST, so we
     * must use connect_after.
     */
    g_signal_connect_after (directory_view, "add-files",
                            G_CALLBACK (pre_copy_move_add_files_callback), copy_move_done_data);

    return copy_move_done_data;
}

/* This function is used to pull out any debuting uris that were added
 * and (as a side effect) remove them from the debuting uri hash table.
 */
static gboolean
copy_move_done_partition_func (NautilusFile *file,
                               gpointer      callback_data)
{
    GFile *location;
    gboolean result;

    location = nautilus_file_get_location (file);
    result = g_hash_table_remove ((GHashTable *) callback_data, location);
    g_object_unref (location);

    return result;
}

static gboolean
remove_not_really_moved_files (gpointer key,
                               gpointer value,
                               gpointer callback_data)
{
    GList **added_files;
    GFile *loc;

    loc = key;

    if (GPOINTER_TO_INT (value))
    {
        return FALSE;
    }

    added_files = callback_data;
    *added_files = g_list_prepend (*added_files,
                                   nautilus_file_get (loc));
    return TRUE;
}

/* When this function is invoked, the file operation is over, but all
 * the icons may not have been added to the directory view yet, so
 * we can't select them yet.
 *
 * We're passed a hash table of the uri's to look out for, we hook
 * up a signal handler to await their arrival.
 */
static void
copy_move_done_callback (GHashTable *debuting_files,
                         gboolean    success,
                         gpointer    data)
{
    NautilusFilesView *directory_view;
    CopyMoveDoneData *copy_move_done_data;
    DebutingFilesData *debuting_files_data;
    GList *failed_files;

    copy_move_done_data = (CopyMoveDoneData *) data;
    directory_view = copy_move_done_data->directory_view;

    if (directory_view != NULL)
    {
        g_assert (NAUTILUS_IS_FILES_VIEW (directory_view));

        debuting_files_data = g_new (DebutingFilesData, 1);
        debuting_files_data->debuting_files = g_hash_table_ref (debuting_files);
        debuting_files_data->added_files = nautilus_file_list_filter (copy_move_done_data->added_files,
                                                                      &failed_files,
                                                                      copy_move_done_partition_func,
                                                                      debuting_files);
        nautilus_file_list_free (copy_move_done_data->added_files);
        copy_move_done_data->added_files = failed_files;

        /* We're passed the same data used by pre_copy_move_add_files_callback, so disconnecting
         * it will free data. We've already siphoned off the added_files we need, and stashed the
         * directory_view pointer.
         */
        g_signal_handlers_disconnect_by_func (directory_view,
                                              G_CALLBACK (pre_copy_move_add_files_callback),
                                              data);

        /* Any items in the debuting_files hash table that have
         * "FALSE" as their value aren't really being copied
         * or moved, so we can't wait for an add_files signal
         * to come in for those.
         */
        g_hash_table_foreach_remove (debuting_files,
                                     remove_not_really_moved_files,
                                     &debuting_files_data->added_files);

        if (g_hash_table_size (debuting_files) == 0)
        {
            /* on the off-chance that all the icons have already been added */
            if (debuting_files_data->added_files != NULL)
            {
                nautilus_files_view_call_set_selection (directory_view,
                                                        debuting_files_data->added_files);
            }
            debuting_files_data_free (debuting_files_data);
        }
        else
        {
            /* We need to run after the default handler adds the folder we want to
             * operate on. The ADD_FILES signal is registered as G_SIGNAL_RUN_LAST, so we
             * must use connect_after.
             */
            g_signal_connect_data (directory_view,
                                   "add-files",
                                   G_CALLBACK (debuting_files_add_files_callback),
                                   debuting_files_data,
                                   (GClosureNotify) debuting_files_data_free,
                                   G_CONNECT_AFTER);
        }
        /* Schedule menu update for undo items */
        schedule_update_context_menus (directory_view);
    }

    copy_move_done_data_free (copy_move_done_data);
}

static gboolean
view_file_still_belongs (NautilusFilesView *self,
                         FileAndDirectory  *fad)
{
    if (self->directory != fad->directory &&
        g_list_find (self->subdirectory_list, fad->directory) == NULL)
    {
        return FALSE;
    }

    return nautilus_directory_contains_file (fad->directory, fad->file);
}

static gboolean
still_should_show_file (NautilusFilesView *view,
                        FileAndDirectory  *fad)
{
    return nautilus_files_view_should_show_file (view, fad->file) &&
           view_file_still_belongs (view, fad);
}

static void
files_view_end_file_changes (NautilusFilesView *self)
{
    nautilus_view_model_sort (self->model);

    /* Addition and removal of files modify the empty state */
    nautilus_files_view_update_status_overlay (self);
    /* If the view is empty, zoom slider and sort menu are insensitive */
    nautilus_files_view_update_toolbar_menus (self);

    /* Reveal files that were pending to be revealed, only if all of them
     * were acknowledged by the view
     */
    if (g_hash_table_size (self->pending_reveal) > 0 &&
        g_hash_table_size (self->awaiting_acknowledge) == 0)
    {
        g_autoptr (NautilusFileList) selection = g_hash_table_get_keys (self->pending_reveal);

        nautilus_files_view_set_selection (self, selection);
        g_hash_table_remove_all (self->pending_reveal);
    }
}

static int
compare_pointers (gconstpointer pointer_1,
                  gconstpointer pointer_2)
{
    if (pointer_1 < pointer_2)
    {
        return -1;
    }
    else if (pointer_1 > pointer_2)
    {
        return +1;
    }

    return 0;
}

static gboolean
_g_lists_sort_and_check_for_intersection (GList **list_1,
                                          GList **list_2)
{
    GList *node_1;
    GList *node_2;
    int compare_result;

    *list_1 = g_list_sort (*list_1, compare_pointers);
    *list_2 = g_list_sort (*list_2, compare_pointers);

    node_1 = *list_1;
    node_2 = *list_2;

    while (node_1 != NULL && node_2 != NULL)
    {
        compare_result = compare_pointers (node_1->data, node_2->data);
        if (compare_result == 0)
        {
            return TRUE;
        }
        if (compare_result <= 0)
        {
            node_1 = node_1->next;
        }
        if (compare_result >= 0)
        {
            node_2 = node_2->next;
        }
    }

    return FALSE;
}

static void
remove_fad_duplicates (GList **files)
{
    g_autoptr (GHashTable) files_hash = g_hash_table_new (file_and_directory_hash,
                                                          file_and_directory_equal);

    for (GList *l = *files; l != NULL; l = l->next)
    {
        FileAndDirectory *fad = l->data;

        if (g_hash_table_contains (files_hash, fad))
        {
            /* Can't be null since we look for duplicates */
            GList *temp = l->prev;

            *files = g_list_remove_link (*files, l);
            g_list_free_full (l, file_and_directory_free);
            l = temp;
        }
        else
        {
            g_hash_table_add (files_hash, fad);
        }
    }
}

static void
nautilus_files_view_add_files (NautilusFilesView *self,
                               GList             *files)
{
    g_autolist (NautilusViewItem) items = NULL;

    items = g_list_copy_deep (files, (GCopyFunc) nautilus_view_item_new, NULL);
    nautilus_view_model_add_items (self->model, items);
}

static void
files_view_remove_files (NautilusFilesView *self,
                         GList             *files,
                         NautilusDirectory *directory)
{
    g_autoptr (GHashTable) items = g_hash_table_new (NULL, NULL);

    for (GList *l = files; l != NULL; l = l->next)
    {
        NautilusViewItem *item;

        item = nautilus_view_model_get_item_for_file (self->model, l->data);
        if (item != NULL)
        {
            g_hash_table_insert (items, item, l->data);
        }
    }

    if (g_hash_table_size (items) > 0)
    {
        nautilus_view_model_remove_items (self->model, items, directory);
    }
}

static void
files_view_file_changed (NautilusFilesView *self,
                         NautilusFile      *file,
                         NautilusDirectory *directory)
{
    g_autoptr (NautilusFile) directory_as_file = NULL;
    NautilusViewItem *item;

    directory_as_file = nautilus_directory_get_corresponding_file (directory);
    if (file == directory_as_file)
    {
        /* We don't care about changes to the current directory itself here, so
         * silently ignore it. This happens only with self-owned files.*/
        return;
    }

    item = nautilus_view_model_get_item_for_file (self->model, file);
    if (item != NULL)
    {
        nautilus_view_item_file_changed (item);
    }
    else
    {
        /* When a file that was hidden is not hidden anymore (e.g. undoing the
         * rename operation which made it hidden), we get a change notification
         * for a file that's not in our model. Let's add it then. */
        nautilus_files_view_add_files (self, &(GList){ .data = file });
    }
}

static void
process_pending_files (NautilusFilesView *self)
{
    g_autolist (FileAndDirectory) files_added = g_steal_pointer (&self->new_added_files);
    g_autolist (FileAndDirectory) files_changed = g_steal_pointer (&self->new_changed_files);

    if (files_added == NULL && files_changed == NULL)
    {
        return;
    }

    g_autoptr (GList) pending_additions = NULL;
    g_autoptr (GHashTable) files_removed = g_hash_table_new (NULL, NULL);

    remove_fad_duplicates (&files_changed);

    for (GList *node = files_added; node != NULL; node = node->next)
    {
        FileAndDirectory *pending = node->data;
        gboolean should_add_file = still_should_show_file (self, pending);

        if (should_add_file)
        {
            pending_additions = g_list_prepend (pending_additions, pending->file);
        }

        /* Acknowledge the files that were pending to be revealed */
        if (g_hash_table_contains (self->awaiting_acknowledge, pending->file))
        {
            if (should_add_file)
            {
                g_hash_table_add (self->pending_reveal, pending->file);
            }

            g_hash_table_remove (self->awaiting_acknowledge, pending->file);
        }
    }
    pending_additions = g_list_reverse (pending_additions);

    if (files_added != NULL)
    {
        g_signal_emit (self,
                       signals[ADD_FILES], 0, pending_additions);
    }

    for (GList *node = files_changed; node != NULL; node = node->next)
    {
        FileAndDirectory *pending = node->data;
        gboolean should_show_file = still_should_show_file (self, pending);

        if (should_show_file)
        {
            g_signal_emit (self,
                           signals[FILE_CHANGED], 0, pending->file, pending->directory);
        }
        else
        {
            GList *files = g_hash_table_lookup (files_removed, pending->directory);

            g_hash_table_insert (files_removed,
                                 pending->directory,
                                 g_list_prepend (files, pending->file));
        }

        /* Acknowledge the files that were pending to be revealed */
        if (g_hash_table_contains (self->awaiting_acknowledge, pending->file))
        {
            if (should_show_file)
            {
                g_hash_table_add (self->pending_reveal, pending->file);
            }

            g_hash_table_remove (self->awaiting_acknowledge, pending->file);
        }
    }

    if (g_hash_table_size (files_removed) > 0)
    {
        GHashTableIter iter;
        gpointer directory;
        GList *files;

        g_hash_table_iter_init (&iter, files_removed);
        while (g_hash_table_iter_next (&iter, &directory, (gpointer *) &files))
        {
            g_signal_emit (self, signals[REMOVE_FILES], 0, files, directory);
            g_list_free (files);
            g_hash_table_iter_steal (&iter);
        }
    }

    if (files_changed != NULL)
    {
        g_autolist (NautilusFile) selection = nautilus_files_view_get_selection (self);
        g_autoptr (GList) files = g_list_copy_deep (files_changed, (GCopyFunc) file_and_directory_get_file, NULL);

        if (_g_lists_sort_and_check_for_intersection (&files, &selection))
        {
            /* Send a selection change since some file names could
             * have changed.
             */
            nautilus_files_view_send_selection_change (self);
        }
    }

    g_signal_emit (self, signals[END_FILE_CHANGES], 0);
}

static void
display_pending_files (NautilusFilesView *view)
{
    search_transition_emit_delayed_signals_if_pending (view);

    /* Get selection after delayed signals are emitted. */
    g_autoptr (GtkBitset) selection = gtk_selection_model_get_selection (GTK_SELECTION_MODEL (view->model));
    gboolean no_selection = gtk_bitset_is_empty (selection);

    process_pending_files (view);

    if (no_selection &&
        !view->pending_selection &&
        nautilus_files_view_is_searching (view))
    {
        nautilus_files_view_select_first (view);
    }

    if (view->model != NULL
        && nautilus_directory_are_all_files_seen (view->directory))
    {
        done_loading (view, TRUE);
    }
}

static gboolean
display_selection_info_idle_callback (gpointer data)
{
    NautilusFilesView *view;

    view = NAUTILUS_FILES_VIEW (data);

    g_object_ref (G_OBJECT (view));

    view->display_selection_idle_id = 0;
    nautilus_files_view_display_selection_info (view);
    nautilus_files_view_send_selection_change (view);

    g_object_unref (G_OBJECT (view));

    return FALSE;
}

static void
remove_update_context_menus_timeout_callback (NautilusFilesView *self)
{
    if (self->update_context_menus_timeout_id != 0)
    {
        g_source_remove (self->update_context_menus_timeout_id);
        self->update_context_menus_timeout_id = 0;
    }
}

static void
update_context_menus_if_pending (NautilusFilesView *view)
{
    if (view->update_context_menus_timeout_id != 0)
    {
        remove_update_context_menus_timeout_callback (view);
        nautilus_files_view_update_context_menus (view);
    }
}

static gboolean
update_context_menus_timeout_callback (gpointer data)
{
    NautilusFilesView *view;

    view = NAUTILUS_FILES_VIEW (data);

    g_object_ref (G_OBJECT (view));

    view->update_context_menus_timeout_id = 0;
    nautilus_files_view_update_context_menus (view);

    g_object_unref (G_OBJECT (view));

    return FALSE;
}

static gboolean
display_pending_callback (gpointer data)
{
    NautilusFilesView *view = NAUTILUS_FILES_VIEW (data);

    g_object_ref (G_OBJECT (view));

    view->display_pending_source_id = 0;

    display_pending_files (view);

    g_object_unref (G_OBJECT (view));

    return FALSE;
}

static void
schedule_idle_display_of_pending_files (NautilusFilesView *view)
{
    /* Get rid of a pending source as it might be a timeout */
    unschedule_display_of_pending_files (view);

    /* We want higher priority than the idle that handles the relayout
     *  to avoid a resort on each add. But we still want to allow repaints
     *  and other hight prio events while we have pending files to show. */
    view->display_pending_source_id =
        g_idle_add_full (G_PRIORITY_DEFAULT_IDLE - 20,
                         display_pending_callback, view, NULL);
}

static void
schedule_timeout_display_of_pending_files (NautilusFilesView *self,
                                           guint              interval)
{
    /* No need to schedule an update if there's already one pending. */
    if (self->display_pending_source_id != 0)
    {
        return;
    }

    self->display_pending_source_id =
        g_timeout_add (interval, display_pending_callback, self);
}

static void
display_pending_files_with_tradeoff (NautilusFilesView *self)
{
    gboolean reversed;
    const char *sort_attribute = get_directory_sort_by (self->directory_as_file, &reversed);

    unschedule_display_of_pending_files (self);

    if (!nautilus_file_attribute_slow_sort (sort_attribute))
    {
        display_pending_files (self);
    }
    else
    {
        /* Schedule a pending update with the minimal update interval. This
         * gives the view a short chance at gathering the (cached) deep counts.
         */
        schedule_timeout_display_of_pending_files (self, UPDATE_INTERVAL_MIN);
    }
}

static void
unschedule_display_of_pending_files (NautilusFilesView *self)
{
    /* Get rid of source if it's active. */
    if (self->display_pending_source_id != 0)
    {
        g_source_remove (self->display_pending_source_id);
        self->display_pending_source_id = 0;
    }
}

static void
queue_pending_files (NautilusFilesView  *self,
                     NautilusDirectory  *directory,
                     GList              *files,
                     GList             **pending_list)
{
    GList *fad_list;

    if (files == NULL)
    {
        return;
    }

    fad_list = g_list_copy_deep (files, (GCopyFunc) file_and_directory_new, directory);
    *pending_list = g_list_concat (fad_list, *pending_list);
    /* Generally we don't want to show the files while the directory is loading
     * the files themselves, so we avoid jumping and oddities. However, for
     * search and for MTP devices, it can be a while, and we actually want to
     * show files as they are loading. */
    if ((!self->loading && self->subdirectories_loading == NULL) ||
        (nautilus_directory_are_all_files_seen (directory) ||
         nautilus_files_view_is_searching (self) ||
         (!g_file_is_native (self->location) &&
          g_file_has_uri_scheme (self->location, "mtp"))))
    {
        schedule_timeout_display_of_pending_files (self, self->update_interval);
    }
}

static void
remove_changes_timeout_callback (NautilusFilesView *self)
{
    if (self->changes_timeout_id != 0)
    {
        g_source_remove (self->changes_timeout_id);
        self->changes_timeout_id = 0;
    }
}

static void
reset_update_interval (NautilusFilesView *self)
{
    self->update_interval = UPDATE_INTERVAL_MIN;
    remove_changes_timeout_callback (self);
    /* Reschedule a pending timeout to idle */
    if (self->display_pending_source_id != 0)
    {
        schedule_idle_display_of_pending_files (self);
    }
}

static gboolean
changes_timeout_callback (gpointer data)
{
    gint64 now;
    gint64 time_delta;
    gboolean ret;
    NautilusFilesView *self;

    self = NAUTILUS_FILES_VIEW (data);

    g_object_ref (G_OBJECT (self));

    now = g_get_monotonic_time ();
    time_delta = now - self->last_queued;

    if (time_delta < UPDATE_INTERVAL_RESET * 1000)
    {
        if (self->update_interval < UPDATE_INTERVAL_MAX &&
            self->loading)
        {
            /* Increase */
            self->update_interval += UPDATE_INTERVAL_INC;
        }
        ret = TRUE;
    }
    else
    {
        /* Reset */
        reset_update_interval (self);
        ret = FALSE;
    }

    g_object_unref (G_OBJECT (self));

    return ret;
}

static void
schedule_changes (NautilusFilesView *self)
{
    /* Remember when the change was queued */
    self->last_queued = g_get_monotonic_time ();

    /* No need to schedule if there are already changes pending or during loading */
    if (self->changes_timeout_id != 0 ||
        self->loading)
    {
        return;
    }

    self->changes_timeout_id =
        g_timeout_add (UPDATE_INTERVAL_TIMEOUT_INTERVAL, changes_timeout_callback, self);
}

static void
files_added_callback (NautilusDirectory *directory,
                      GList             *files,
                      gpointer           callback_data)
{
    NautilusFilesView *view;
    GtkWindow *window;
    char *uri;

    view = NAUTILUS_FILES_VIEW (callback_data);

    window = nautilus_files_view_get_containing_window (view);
    uri = nautilus_files_view_get_uri (view);

    g_debug ("Files added in window %p: %s", window, uri ? uri : "(no directory)");
    nautilus_file_list_debug (files);

    g_free (uri);

    schedule_changes (view);

    queue_pending_files (view, directory, files, &view->new_added_files);

    /* The number of items could have changed */
    schedule_update_status (view);
}

static void
files_changed_callback (NautilusDirectory *directory,
                        GList             *files,
                        gpointer           callback_data)
{
    NautilusFilesView *view;
    GtkWindow *window;
    char *uri;

    view = NAUTILUS_FILES_VIEW (callback_data);

    window = nautilus_files_view_get_containing_window (view);
    uri = nautilus_files_view_get_uri (view);
    g_debug ("Files changed in window (%p) %s", window, uri ? uri : "(no directory)");
    nautilus_file_list_debug (files);

    g_free (uri);

    schedule_changes (view);

    queue_pending_files (view, directory, files, &view->new_changed_files);

    /* The free space or the number of items could have changed */
    schedule_update_status (view);
}

static void
done_loading_callback (NautilusDirectory *directory,
                       gpointer           callback_data)
{
    NautilusFilesView *view;

    view = NAUTILUS_FILES_VIEW (callback_data);

    display_pending_files_with_tradeoff (view);

    remove_loading_floating_bar (view);
}

static void
load_error_callback (NautilusDirectory *directory,
                     GError            *error,
                     gpointer           callback_data)
{
    NautilusFilesView *view = NAUTILUS_FILES_VIEW (callback_data);

    /* FIXME: By doing a stop, we discard some pending files. Is
     * that OK?
     */
    nautilus_files_view_stop_loading (view);

    nautilus_report_error_loading_directory (view->directory_as_file,
                                             error,
                                             nautilus_files_view_get_containing_window (view));
}

gboolean
nautilus_files_view_has_subdirectory (NautilusFilesView *self,
                                      NautilusDirectory *directory)
{
    return g_list_find (self->subdirectory_list, directory) != NULL;
}

static void
subdirectory_done_loading (NautilusDirectory *directory,
                           gpointer           user_data)
{
    NautilusFilesView *self = user_data;
    g_autoptr (NautilusFile) file = nautilus_directory_get_corresponding_file (directory);
    NautilusViewItem *item = nautilus_view_model_get_item_for_file (self->model, file);

    self->subdirectories_loading = g_list_remove (self->subdirectories_loading, directory);

    if (item != NULL)
    {
        nautilus_view_item_set_loading (item, FALSE);
    }
}

void
nautilus_files_view_add_subdirectory (NautilusFilesView *self,
                                      NautilusDirectory *directory)
{
    NautilusFileAttributes attributes;
    g_autoptr (NautilusFile) file = nautilus_directory_get_corresponding_file (directory);
    NautilusViewItem *item = nautilus_view_model_get_item_for_file (self->model, file);

    g_return_if_fail (!g_list_find (self->subdirectory_list, directory));

    nautilus_directory_ref (directory);

    attributes =
        NAUTILUS_FILE_ATTRIBUTES_FOR_ICON |
        NAUTILUS_FILE_ATTRIBUTE_DIRECTORY_ITEM_COUNT |
        NAUTILUS_FILE_ATTRIBUTE_INFO |
        NAUTILUS_FILE_ATTRIBUTE_MOUNT |
        NAUTILUS_FILE_ATTRIBUTE_EXTENSION_INFO;

    nautilus_directory_file_monitor_add (directory,
                                         &self->directory,
                                         self->show_hidden_files,
                                         attributes,
                                         files_added_callback, self);

    g_signal_connect
        (directory, "files-added",
        G_CALLBACK (files_added_callback), self);
    g_signal_connect
        (directory, "files-changed",
        G_CALLBACK (files_changed_callback), self);
    g_signal_connect_object (directory, "done-loading",
                             G_CALLBACK (subdirectory_done_loading),
                             self, 0);

    self->subdirectory_list = g_list_prepend (self->subdirectory_list, directory);
    self->subdirectories_loading = g_list_prepend (self->subdirectories_loading, directory);

    if (item != NULL &&
        !nautilus_directory_are_all_files_seen (directory))
    {
        nautilus_view_item_set_loading (item, TRUE);
    }
}

void
nautilus_files_view_remove_subdirectory (NautilusFilesView *self,
                                         NautilusDirectory *directory)
{
    g_autoptr (NautilusFile) file = nautilus_directory_get_corresponding_file (directory);
    NautilusViewItem *item = nautilus_view_model_get_item_for_file (self->model, file);

    g_return_if_fail (g_list_find (self->subdirectory_list, directory));

    self->subdirectory_list = g_list_remove (self->subdirectory_list, directory);
    self->subdirectories_loading = g_list_remove (self->subdirectories_loading, directory);

    if (item != NULL)
    {
        nautilus_view_item_set_loading (item, FALSE);

        /* The model holds a GListStore for every subdirectory. Empty it. */
        nautilus_view_model_clear_subdirectory (self->model, item);
    }

    g_signal_handlers_disconnect_by_func (directory,
                                          G_CALLBACK (files_added_callback),
                                          self);
    g_signal_handlers_disconnect_by_func (directory,
                                          G_CALLBACK (files_changed_callback),
                                          self);
    g_signal_handlers_disconnect_by_func (directory,
                                          G_CALLBACK (subdirectory_done_loading),
                                          self);

    nautilus_directory_file_monitor_remove (directory, &self->directory);

    nautilus_directory_unref (directory);
}

/**
 * nautilus_files_view_get_loading:
 * @view: an #NautilusFilesView.
 *
 * Return value: #gboolean inicating whether @view is currently loaded.
 *
 **/
gboolean
nautilus_files_view_get_loading (NautilusFilesView *self)
{
    g_return_val_if_fail (NAUTILUS_IS_FILES_VIEW (self), FALSE);

    return self->loading;
}

/* home_dir_in_selection()
 *
 * Return TRUE if the home directory is in the selection.
 */

static gboolean
home_dir_in_selection (NautilusFileList *selection)
{
    for (NautilusFileList *node = selection; node != NULL; node = node->next)
    {
        if (nautilus_file_is_home (NAUTILUS_FILE (node->data)))
        {
            return TRUE;
        }
    }

    return FALSE;
}

static GList *
get_extension_selection_menu_items (NautilusFilesView *view)
{
    GList *items;
    GList *providers;
    GList *l;
    g_autolist (NautilusFile) selection = NULL;

    selection = nautilus_files_view_get_selection (view);
    providers = nautilus_module_get_extensions_for_type (NAUTILUS_TYPE_MENU_PROVIDER);
    items = NULL;

    for (l = providers; l != NULL; l = l->next)
    {
        NautilusMenuProvider *provider;
        GList *file_items;

        provider = NAUTILUS_MENU_PROVIDER (l->data);
        file_items = nautilus_menu_provider_get_file_items (provider,
                                                            selection);
        items = g_list_concat (items, file_items);
    }

    nautilus_module_extension_list_free (providers);

    return items;
}

static GList *
get_extension_background_menu_items (NautilusFilesView *self)
{
    GList *items;
    GList *providers;
    GList *l;

    providers = nautilus_module_get_extensions_for_type (NAUTILUS_TYPE_MENU_PROVIDER);
    items = NULL;

    for (l = providers; l != NULL; l = l->next)
    {
        NautilusMenuProvider *provider;
        NautilusFileInfo *file_info;
        GList *file_items;

        provider = NAUTILUS_MENU_PROVIDER (l->data);
        file_info = NAUTILUS_FILE_INFO (self->directory_as_file);
        file_items = nautilus_menu_provider_get_background_items (provider,
                                                                  file_info);
        items = g_list_concat (items, file_items);
    }

    nautilus_module_extension_list_free (providers);

    return items;
}

static void
extension_action_callback (GSimpleAction *action,
                           GVariant      *state,
                           gpointer       user_data)
{
    NautilusMenuItem *item = user_data;
    nautilus_menu_item_activate (item);
}

static void
add_extension_action (NautilusFilesView *self,
                      NautilusMenuItem  *item,
                      const char        *action_name)
{
    gboolean sensitive;
    GSimpleAction *action;

    g_object_get (item,
                  "sensitive", &sensitive,
                  NULL);

    action = g_simple_action_new (action_name, NULL);
    g_signal_connect_data (action, "activate",
                           G_CALLBACK (extension_action_callback),
                           g_object_ref (item),
                           (GClosureNotify) g_object_unref, 0);

    g_action_map_add_action (G_ACTION_MAP (self->view_action_group),
                             G_ACTION (action));
    g_simple_action_set_enabled (action, sensitive);

    g_object_unref (action);
}

static GMenuModel *
build_menu_for_extension_menu_items (NautilusFilesView *view,
                                     const gchar       *extension_prefix,
                                     GList             *menu_items)
{
    GList *l;
    GMenu *gmenu;
    gint idx = 0;

    gmenu = g_menu_new ();

    for (l = menu_items; l; l = l->next)
    {
        NautilusMenuItem *item;
        NautilusMenu *menu;
        GMenuItem *menu_item;
        char *name, *label;
        g_autofree gchar *escaped_name = NULL;
        char *extension_id, *detailed_action_name;

        item = NAUTILUS_MENU_ITEM (l->data);

        g_object_get (item,
                      "label", &label,
                      "menu", &menu,
                      "name", &name,
                      NULL);

        escaped_name = g_uri_escape_string (name, NULL, TRUE);
        extension_id = g_strdup_printf ("extension_%s_%d_%s",
                                        extension_prefix, idx, escaped_name);
        add_extension_action (view, item, extension_id);

        detailed_action_name = g_strconcat ("view.", extension_id, NULL);
        menu_item = g_menu_item_new (label, detailed_action_name);

        if (menu != NULL)
        {
            GList *children;
            g_autoptr (GMenuModel) children_menu = NULL;

            children = nautilus_menu_get_items (menu);
            children_menu = build_menu_for_extension_menu_items (view, extension_id, children);
            g_menu_item_set_submenu (menu_item, children_menu);

            nautilus_menu_item_list_free (children);
        }

        g_menu_append_item (gmenu, menu_item);
        idx++;

        g_free (extension_id);
        g_free (detailed_action_name);
        g_free (name);
        g_free (label);
        g_object_unref (menu_item);
    }

    return G_MENU_MODEL (gmenu);
}

static void
update_extensions_menus (NautilusFilesView *view,
                         GtkBuilder        *builder)
{
    GList *selection_items, *background_items;
    GObject *object;
    g_autoptr (GMenuModel) background_menu = NULL;
    g_autoptr (GMenuModel) selection_menu = NULL;

    selection_items = get_extension_selection_menu_items (view);
    if (selection_items != NULL)
    {
        selection_menu = build_menu_for_extension_menu_items (view, "extensions",
                                                              selection_items);

        object = gtk_builder_get_object (builder, "selection-extensions-section");
        nautilus_gmenu_set_from_model (G_MENU (object), selection_menu);

        nautilus_menu_item_list_free (selection_items);
    }

    background_items = get_extension_background_menu_items (view);
    if (background_items != NULL)
    {
        background_menu = build_menu_for_extension_menu_items (view, "extensions",
                                                               background_items);

        object = gtk_builder_get_object (builder, "background-extensions-section");
        nautilus_gmenu_set_from_model (G_MENU (object), background_menu);

        nautilus_menu_item_list_free (background_items);
    }

    real_set_extensions_background_menu (view, background_menu);
}

static char *
change_to_view_directory (NautilusFilesView *view)
{
    char *path;
    char *old_path;

    old_path = g_get_current_dir ();

    path = get_view_directory (view);

    /* FIXME: What to do about non-local directories? */
    if (path != NULL)
    {
        g_chdir (path);
    }

    g_free (path);

    return old_path;
}

static char **
get_file_names_as_parameter_array (NautilusFileList  *selection,
                                   NautilusDirectory *directory)
{
    char **parameters;
    g_autoptr (GFile) directory_location = NULL;
    int i;

    if (directory == NULL)
    {
        return NULL;
    }

    parameters = g_new (char *, g_list_length (selection) + 1);

    directory_location = nautilus_directory_get_location (directory);

    i = 0;
    for (GList *node = selection; node != NULL; node = node->next, i++)
    {
        g_autoptr (GFile) file_location = NULL;
        NautilusFile *file = NAUTILUS_FILE (node->data);

        if (!nautilus_file_has_local_path (file))
        {
            parameters[i] = NULL;
            g_strfreev (parameters);
            return NULL;
        }

        file_location = nautilus_file_get_location (file);
        parameters[i] = g_file_get_relative_path (directory_location, file_location);
        if (parameters[i] == NULL)
        {
            parameters[i] = g_file_get_path (file_location);
        }
    }

    parameters[i] = NULL;
    return parameters;
}

static char *
get_file_paths_or_uris_as_newline_delimited_string (NautilusFileList *selection,
                                                    gboolean          get_paths)
{
    GString *expanding_string;

    expanding_string = g_string_new ("");
    for (GList *node = selection; node != NULL; node = node->next)
    {
        NautilusFile *file = NAUTILUS_FILE (node->data);
        g_autofree gchar *uri = NULL;

        uri = nautilus_file_get_uri (file);
        if (uri == NULL)
        {
            continue;
        }

        if (get_paths)
        {
            g_autofree gchar *path = NULL;

            if (!nautilus_file_has_local_path (file))
            {
                g_string_free (expanding_string, TRUE);
                return g_strdup ("");
            }

            path = g_filename_from_uri (uri, NULL, NULL);
            if (path != NULL)
            {
                g_string_append (expanding_string, path);
                g_string_append (expanding_string, "\n");
            }
        }
        else
        {
            g_string_append (expanding_string, uri);
            g_string_append (expanding_string, "\n");
        }
    }

    return g_string_free (expanding_string, FALSE);
}

static char *
get_file_paths_as_newline_delimited_string (NautilusFileList *selection)
{
    return get_file_paths_or_uris_as_newline_delimited_string (selection, TRUE);
}

static char *
get_file_uris_as_newline_delimited_string (NautilusFileList *selection)
{
    return get_file_paths_or_uris_as_newline_delimited_string (selection, FALSE);
}

/*
 * Set up some environment variables that scripts can use
 * to take advantage of the current Nautilus state.
 */
static void
set_script_environment_variables (NautilusFilesView *self,
                                  GList             *selected_files)
{
    g_autofree gchar *file_paths = NULL;
    g_autofree gchar *uris = NULL;
    g_autofree gchar *uri = NULL;

    file_paths = get_file_paths_as_newline_delimited_string (selected_files);
    g_setenv ("NAUTILUS_SCRIPT_SELECTED_FILE_PATHS", file_paths, TRUE);

    uris = get_file_uris_as_newline_delimited_string (selected_files);
    g_setenv ("NAUTILUS_SCRIPT_SELECTED_URIS", uris, TRUE);

    uri = nautilus_directory_get_uri (self->directory);
    g_setenv ("NAUTILUS_SCRIPT_CURRENT_URI", uri, TRUE);
}

/* Unset all the special script environment variables. */
static void
unset_script_environment_variables (void)
{
    g_unsetenv ("NAUTILUS_SCRIPT_SELECTED_FILE_PATHS");
    g_unsetenv ("NAUTILUS_SCRIPT_SELECTED_URIS");
    g_unsetenv ("NAUTILUS_SCRIPT_CURRENT_URI");
}

static void
run_script (GSimpleAction *action,
            GVariant      *state,
            gpointer       user_data)
{
    ScriptLaunchParameters *launch_parameters;
    g_autofree gchar *file_uri = NULL;
    g_autofree gchar *local_file_path = NULL;
    g_autofree gchar *quoted_path = NULL;
    g_autofree gchar *old_working_dir = NULL;
    g_autolist (NautilusFile) selection = NULL;
    g_auto (GStrv) parameters = NULL;
    GdkDisplay *display;

    launch_parameters = (ScriptLaunchParameters *) user_data;

    file_uri = nautilus_file_get_uri (launch_parameters->file);
    local_file_path = g_filename_from_uri (file_uri, NULL, NULL);
    g_assert (local_file_path != NULL);
    quoted_path = g_shell_quote (local_file_path);

    old_working_dir = change_to_view_directory (launch_parameters->directory_view);

    selection = nautilus_files_view_get_selection (launch_parameters->directory_view);
    set_script_environment_variables (launch_parameters->directory_view, selection);

    NautilusDirectory *directory = launch_parameters->directory_view->directory;
    parameters = get_file_names_as_parameter_array (selection, directory);

    display = gtk_widget_get_display (GTK_WIDGET (launch_parameters->directory_view));

    g_debug ("run_script, script_path=“%s” (omitting script parameters)", local_file_path);

    nautilus_launch_application_from_command_array (display, quoted_path, FALSE,
                                                    (const char * const *) parameters);

    unset_script_environment_variables ();
    g_chdir (old_working_dir);
}

static void
add_script_to_scripts_menus (NautilusFilesView *self,
                             NautilusFile      *file,
                             GMenu             *menu)
{
    const gchar *name;
    g_autofree gchar *uri = NULL;
    g_autofree gchar *escaped_uri = NULL;
    gchar *action_name, *detailed_action_name;
    ScriptLaunchParameters *launch_parameters;
    GAction *action;
    GMenuItem *menu_item;
    const gchar *shortcut;

    launch_parameters = script_launch_parameters_new (file, self);

    name = nautilus_file_get_display_name (file);

    uri = nautilus_file_get_uri (file);
    escaped_uri = g_uri_escape_string (uri, NULL, TRUE);
    action_name = g_strconcat ("script_", escaped_uri, NULL);

    action = G_ACTION (g_simple_action_new (action_name, NULL));

    g_signal_connect_data (action, "activate",
                           G_CALLBACK (run_script),
                           launch_parameters,
                           (GClosureNotify) script_launch_parameters_free, 0);

    g_action_map_add_action (G_ACTION_MAP (self->view_action_group), action);

    g_object_unref (action);

    detailed_action_name = g_strconcat ("view.", action_name, NULL);
    menu_item = g_menu_item_new (name, detailed_action_name);

    g_menu_append_item (menu, menu_item);

    if ((shortcut = g_hash_table_lookup (script_accels, name)))
    {
        gtk_shortcut_controller_add_shortcut (self->shortcuts,
                                              gtk_shortcut_new (gtk_shortcut_trigger_parse_string (shortcut),
                                                                gtk_named_action_new (detailed_action_name)));
    }

    g_free (action_name);
    g_free (detailed_action_name);
    g_object_unref (menu_item);
}

static gboolean
directory_belongs_in_scripts_menu (const char *uri)
{
    int num_levels;
    int i;

    if (!g_str_has_prefix (uri, scripts_directory_uri))
    {
        return FALSE;
    }

    num_levels = 0;
    for (i = scripts_directory_uri_length; uri[i] != '\0'; i++)
    {
        if (uri[i] == '/')
        {
            num_levels++;
        }
    }

    if (num_levels > MAX_MENU_LEVELS)
    {
        return FALSE;
    }

    return TRUE;
}

/* Expected format: accel script_name */
static void
nautilus_load_custom_accel_for_scripts (void)
{
    gchar *path, *contents;
    gchar **lines;
    GError *error = NULL;
    const int max_len = 100;

    path = g_build_filename (g_get_user_config_dir (), SHORTCUTS_PATH, NULL);

    if (g_file_get_contents (path, &contents, NULL, &error))
    {
        lines = g_strsplit (contents, "\n", -1);
        for (guint i = 0; lines[i] != NULL; i++)
        {
            g_auto (GStrv) result = g_strsplit (lines[i], " ", 2);

            if (result[0] == NULL || result[1] == NULL)
            {
                continue;
            }

            g_hash_table_insert (script_accels,
                                 g_strndup (result[1], max_len),
                                 g_strndup (result[0], max_len));
        }

        g_free (contents);
        g_strfreev (lines);
    }
    else
    {
        g_debug ("Unable to open '%s', error message: %s", path, error->message);
        g_clear_error (&error);
    }

    g_free (path);
}

static GMenu *
update_directory_in_scripts_menu (NautilusFilesView *view,
                                  NautilusDirectory *directory)
{
    GList *file_list, *filtered, *node;
    GMenu *menu, *children_menu;
    GMenuItem *menu_item;
    gboolean any_scripts;
    NautilusFile *file;
    NautilusDirectory *dir;
    char *uri;
    int num;

    g_return_val_if_fail (NAUTILUS_IS_FILES_VIEW (view), NULL);
    g_return_val_if_fail (NAUTILUS_IS_DIRECTORY (directory), NULL);

    if (script_accels == NULL)
    {
        script_accels = g_hash_table_new_full (g_str_hash, g_str_equal,
                                               g_free, g_free);
        nautilus_load_custom_accel_for_scripts ();
    }

    file_list = nautilus_directory_get_file_list (directory);
    filtered = nautilus_file_list_filter_hidden (file_list, FALSE);
    nautilus_file_list_free (file_list);
    menu = g_menu_new ();

    filtered = nautilus_file_list_sort_by_display_name (filtered);

    num = 0;
    any_scripts = FALSE;
    for (node = filtered; num < TEMPLATE_LIMIT && node != NULL; node = node->next, num++)
    {
        file = node->data;
        if (nautilus_file_is_directory (file))
        {
            uri = nautilus_file_get_uri (file);
            if (directory_belongs_in_scripts_menu (uri))
            {
                dir = nautilus_directory_get_by_uri (uri);
                add_directory_to_scripts_directory_list (view, dir);

                children_menu = update_directory_in_scripts_menu (view, dir);

                if (children_menu != NULL)
                {
                    const char *file_name = nautilus_file_get_display_name (file);
                    menu_item = g_menu_item_new_submenu (file_name,
                                                         G_MENU_MODEL (children_menu));
                    g_menu_append_item (menu, menu_item);
                    any_scripts = TRUE;
                    g_object_unref (menu_item);
                    g_object_unref (children_menu);
                }

                nautilus_directory_unref (dir);
            }
            g_free (uri);
        }
        else if (nautilus_file_is_launchable (file))
        {
            add_script_to_scripts_menus (view, file, menu);
            any_scripts = TRUE;
        }
    }

    nautilus_file_list_free (filtered);

    if (!any_scripts)
    {
        g_object_unref (menu);
        menu = NULL;
    }

    return menu;
}



static void
update_scripts_menu (NautilusFilesView *view,
                     GtkBuilder        *builder)
{
    g_autolist (NautilusDirectory) sorted_copy = NULL;
    g_autoptr (NautilusDirectory) directory = NULL;
    g_autoptr (GMenu) submenu = NULL;

    sorted_copy = nautilus_directory_list_sort_by_uri
                      (nautilus_directory_list_copy (view->scripts_directory_list));

    for (GList *dir_l = sorted_copy; dir_l != NULL; dir_l = dir_l->next)
    {
        g_autofree char *uri = nautilus_directory_get_uri (dir_l->data);
        if (!directory_belongs_in_scripts_menu (uri))
        {
            remove_directory_from_scripts_directory_list (view, dir_l->data);
        }
    }

    directory = nautilus_directory_get_by_uri (scripts_directory_uri);
    submenu = update_directory_in_scripts_menu (view, directory);
    g_set_object (&view->scripts_menu, G_MENU_MODEL (submenu));
}

static void
create_template (GSimpleAction *action,
                 GVariant      *state,
                 gpointer       user_data)
{
    CreateTemplateParameters *parameters;

    parameters = user_data;

    nautilus_files_view_new_file (parameters->directory_view, NULL, parameters->file);
}

static void
add_template_to_templates_menus (NautilusFilesView *self,
                                 NautilusFile      *file,
                                 GMenu             *menu)
{
    char *uri;
    const char *name;
    g_autofree gchar *escaped_uri = NULL;
    char *action_name, *detailed_action_name;
    CreateTemplateParameters *parameters;
    GAction *action;
    g_autofree char *label = NULL;
    GMenuItem *menu_item;

    name = nautilus_file_get_display_name (file);
    uri = nautilus_file_get_uri (file);
    escaped_uri = g_uri_escape_string (uri, NULL, TRUE);
    action_name = g_strconcat ("template_", escaped_uri, NULL);
    action = G_ACTION (g_simple_action_new (action_name, NULL));
    parameters = create_template_parameters_new (file, self);

    g_signal_connect_data (action, "activate",
                           G_CALLBACK (create_template),
                           parameters,
                           (GClosureNotify) create_templates_parameters_free, 0);

    g_action_map_add_action (G_ACTION_MAP (self->view_action_group), action);

    detailed_action_name = g_strconcat ("view.", action_name, NULL);
    label = escape_underscores (name);
    menu_item = g_menu_item_new (label, detailed_action_name);

    g_menu_append_item (menu, menu_item);

    g_free (uri);
    g_free (action_name);
    g_free (detailed_action_name);
    g_object_unref (action);
    g_object_unref (menu_item);
}

static void
update_templates_directory (NautilusFilesView *view)
{
    NautilusDirectory *templates_directory;
    GList *node, *next;
    char *templates_uri;

    for (node = view->templates_directory_list; node != NULL; node = next)
    {
        next = node->next;
        remove_directory_from_templates_directory_list (view, node->data);
    }

    if (nautilus_should_use_templates_directory ())
    {
        templates_uri = nautilus_get_templates_directory_uri ();
        templates_directory = nautilus_directory_get_by_uri (templates_uri);
        g_free (templates_uri);
        add_directory_to_templates_directory_list (view, templates_directory);
        nautilus_directory_unref (templates_directory);
    }
}

static gboolean
directory_belongs_in_templates_menu (const char *templates_directory_uri,
                                     const char *uri)
{
    int num_levels;
    int i;

    if (templates_directory_uri == NULL)
    {
        return FALSE;
    }

    if (!g_str_has_prefix (uri, templates_directory_uri))
    {
        return FALSE;
    }

    num_levels = 0;
    for (i = strlen (templates_directory_uri); uri[i] != '\0'; i++)
    {
        if (uri[i] == '/')
        {
            num_levels++;
        }
    }

    if (num_levels > MAX_MENU_LEVELS)
    {
        return FALSE;
    }

    return TRUE;
}

static gboolean
filter_templates_callback (NautilusFile *file,
                           gpointer      callback_data)
{
    gboolean show_hidden = GPOINTER_TO_INT (callback_data);

    if (nautilus_file_is_hidden_file (file))
    {
        if (!show_hidden)
        {
            return FALSE;
        }

        if (nautilus_file_is_directory (file))
        {
            return FALSE;
        }
    }

    return TRUE;
}

static GList *
filter_templates (GList    *files,
                  gboolean  show_hidden)
{
    GList *filtered_files;
    GList *removed_files;

    filtered_files = nautilus_file_list_filter (files,
                                                &removed_files,
                                                filter_templates_callback,
                                                GINT_TO_POINTER (show_hidden));
    nautilus_file_list_free (removed_files);

    return filtered_files;
}

static GMenuModel *
update_directory_in_templates_menu (NautilusFilesView *view,
                                    NautilusDirectory *directory)
{
    GList *file_list, *filtered, *node;
    GMenu *menu;
    GMenuItem *menu_item;
    gboolean any_templates;
    NautilusFile *file;
    NautilusDirectory *dir;
    char *uri;
    char *templates_directory_uri;
    int num;

    g_return_val_if_fail (NAUTILUS_IS_FILES_VIEW (view), NULL);
    g_return_val_if_fail (NAUTILUS_IS_DIRECTORY (directory), NULL);

    file_list = nautilus_directory_get_file_list (directory);

    /*
     * The nautilus_file_list_filter_hidden() function isn't used here, because
     * we want to show hidden files, but not directories. This is a compromise
     * to allow creating hidden files but to prevent content from .git directory
     * for example. See https://gitlab.gnome.org/GNOME/nautilus/issues/1413.
     */
    filtered = filter_templates (file_list, view->show_hidden_files);
    nautilus_file_list_free (file_list);
    templates_directory_uri = nautilus_get_templates_directory_uri ();
    menu = g_menu_new ();

    filtered = nautilus_file_list_sort_by_display_name (filtered);

    num = 0;
    any_templates = FALSE;
    for (node = filtered; num < TEMPLATE_LIMIT && node != NULL; node = node->next, num++)
    {
        file = node->data;
        if (nautilus_file_is_directory (file))
        {
            uri = nautilus_file_get_uri (file);
            if (directory_belongs_in_templates_menu (templates_directory_uri, uri))
            {
                g_autoptr (GMenuModel) children_menu = NULL;

                dir = nautilus_directory_get_by_uri (uri);
                add_directory_to_templates_directory_list (view, dir);

                children_menu = update_directory_in_templates_menu (view, dir);

                if (children_menu != NULL)
                {
                    const char *display_name = nautilus_file_get_display_name (file);
                    g_autofree char *label = NULL;

                    label = escape_underscores (display_name);
                    menu_item = g_menu_item_new_submenu (label, children_menu);
                    g_menu_append_item (menu, menu_item);
                    any_templates = TRUE;
                    g_object_unref (menu_item);
                }

                nautilus_directory_unref (dir);
            }
            g_free (uri);
        }
        else if (nautilus_file_can_read (file))
        {
            add_template_to_templates_menus (view, file, menu);
            any_templates = TRUE;
        }
    }

    nautilus_file_list_free (filtered);
    g_free (templates_directory_uri);

    if (!any_templates)
    {
        g_object_unref (menu);
        menu = NULL;
    }

    return G_MENU_MODEL (menu);
}



static void
update_templates_menu (NautilusFilesView *view,
                       GtkBuilder        *builder)
{
    g_autolist (NautilusDirectory) sorted_copy = NULL;
    g_autoptr (NautilusDirectory) directory = NULL;
    g_autoptr (GMenuModel) submenu = NULL;
    g_autofree char *templates_directory_uri = NULL;

    if (!nautilus_should_use_templates_directory ())
    {
        real_set_templates_menu (view, NULL);
        return;
    }

    templates_directory_uri = nautilus_get_templates_directory_uri ();
    sorted_copy = nautilus_directory_list_sort_by_uri
                      (nautilus_directory_list_copy (view->templates_directory_list));

    for (GList *dir_l = sorted_copy; dir_l != NULL; dir_l = dir_l->next)
    {
        g_autofree char *uri = nautilus_directory_get_uri (dir_l->data);
        if (!directory_belongs_in_templates_menu (templates_directory_uri, uri))
        {
            remove_directory_from_templates_directory_list (view, dir_l->data);
        }
    }

    directory = nautilus_directory_get_by_uri (templates_directory_uri);
    submenu = update_directory_in_templates_menu (view, directory);

    real_set_templates_menu (view, submenu);
}


static void
action_open_scripts_folder (GSimpleAction *action,
                            GVariant      *state,
                            gpointer       user_data)
{
    static GFile *location = NULL;

    if (location == NULL)
    {
        location = g_file_new_for_uri (scripts_directory_uri);
    }

    nautilus_application_open_location_full (NAUTILUS_APPLICATION (g_application_get_default ()),
                                             location, 0, NULL, NULL, NULL, NULL);
}

static GFile *
get_dialog_initial_location (NautilusFilesView *view,
                             GList             *files)
{
    GFile *location;
    NautilusFile *file = NAUTILUS_FILE (files->data);

    /* The file dialog will not be able to display the search directory,
     * so we need to get the base directory of the search if we are, in fact,
     * in search.
     */
    if (nautilus_files_view_is_searching (view))
    {
        NautilusSearchDirectory *search = NAUTILUS_SEARCH_DIRECTORY (view->directory);

        location = nautilus_search_directory_get_search_location (search);
    }
    else if (showing_starred_directory (view))
    {
        location = nautilus_file_get_parent_location (file);
    }
    else if (showing_recent_directory (view))
    {
        g_autoptr (GFile) child = nautilus_file_get_activation_location (file);

        location = g_file_get_parent (child);
    }
    else if (showing_trash_directory (view))
    {
        g_autoptr (NautilusFile) child = nautilus_file_get_trash_original_file (file);

        location = nautilus_file_get_parent_location (child);
    }
    else
    {
        location = nautilus_directory_get_location (view->directory);
        g_autofree gchar *path = g_file_get_path (location);

        if (path == NULL || *path == '\0')
        {
            /* Portals will not accept locations with no path, fall back to
             * null to use the default location. */
            g_clear_object (&location);
        }
    }

    return location;
}

typedef struct _CopyCallbackData
{
    NautilusFilesView *view;
    NautilusFileList *selection;
    gboolean is_move;
} CopyCallbackData;

static void
copy_data_free (CopyCallbackData *data)
{
    nautilus_file_list_free (data->selection);
    g_free (data);
}

static void
on_destination_dialog_response (GtkFileDialog *dialog,
                                GAsyncResult  *result,
                                gpointer       user_data)
{
    CopyCallbackData *copy_data = user_data;
    g_autoptr (GFile) target_location = NULL;
    g_autoptr (GError) error = NULL;

    target_location = gtk_file_dialog_select_folder_finish (dialog, result, &error);

    if (target_location != NULL)
    {
        char *target_uri;
        GList *uris, *l;

        target_uri = g_file_get_uri (target_location);
        uris = NULL;
        for (l = copy_data->selection; l != NULL; l = l->next)
        {
            uris = g_list_prepend (uris,
                                   nautilus_file_get_uri ((NautilusFile *) l->data));
        }
        uris = g_list_reverse (uris);

        nautilus_files_view_move_copy_items (copy_data->view, uris, target_uri,
                                             copy_data->is_move ? GDK_ACTION_MOVE : GDK_ACTION_COPY);

        g_list_free_full (uris, g_free);
        g_free (target_uri);
    }
    else if (error != NULL &&
             !g_error_matches (error, GTK_DIALOG_ERROR, GTK_DIALOG_ERROR_DISMISSED))
    {
        g_warning ("Error while choosing a destination folder: %s", error->message);
    }

    copy_data_free (copy_data);
}

static void
copy_or_move_selection (NautilusFilesView *view,
                        gboolean           is_move)
{
    g_autoptr (GtkFileDialog) dialog = gtk_file_dialog_new ();
    g_autoptr (GFile) location = NULL;
    CopyCallbackData *copy_data;
    NautilusFileList *selection;
    const gchar *title;

    if (is_move)
    {
        title = _("Select Move Destination");
    }
    else
    {
        title = _("Select Copy Destination");
    }

    selection = nautilus_files_view_get_selection_for_file_transfer (view);

    if (selection == NULL)
    {
        return;
    }

    gtk_file_dialog_set_title (dialog, title);
    gtk_file_dialog_set_accept_label (dialog, _("_Select"));

    copy_data = g_new0 (CopyCallbackData, 1);
    copy_data->view = view;
    copy_data->selection = selection;
    copy_data->is_move = is_move;

    location = get_dialog_initial_location (view, selection);

    gtk_file_dialog_set_initial_folder (dialog, location);

    gtk_file_dialog_select_folder (dialog,
                                   GTK_WINDOW (gtk_widget_get_root (GTK_WIDGET (view))),
                                   NULL,
                                   (GAsyncReadyCallback) on_destination_dialog_response,
                                   copy_data);
}

static void
action_copy (GSimpleAction *action,
             GVariant      *state,
             gpointer       user_data)
{
    NautilusFilesView *view;
    GdkClipboard *clipboard;
    g_autolist (NautilusFile) selection = NULL;

    view = NAUTILUS_FILES_VIEW (user_data);

    selection = nautilus_files_view_get_selection_for_file_transfer (view);
    clipboard = gtk_widget_get_clipboard (GTK_WIDGET (view));
    nautilus_clipboard_prepare_for_files (clipboard, selection, FALSE);
}

static void
action_cut (GSimpleAction *action,
            GVariant      *state,
            gpointer       user_data)
{
    NautilusFilesView *view;
    g_autolist (NautilusFile) selection = NULL;
    GdkClipboard *clipboard;

    view = NAUTILUS_FILES_VIEW (user_data);

    selection = nautilus_files_view_get_selection_for_file_transfer (view);
    clipboard = gtk_widget_get_clipboard (GTK_WIDGET (view));
    nautilus_clipboard_prepare_for_files (clipboard, selection, TRUE);
}

static void
action_copy_current_location (GSimpleAction *action,
                              GVariant      *state,
                              gpointer       user_data)
{
    NautilusFilesView *self = user_data;
    GdkClipboard *clipboard;
    GList *files;

    if (self->directory_as_file != NULL)
    {
        files = g_list_append (NULL, nautilus_file_ref (self->directory_as_file));

        clipboard = gtk_widget_get_clipboard (GTK_WIDGET (self));
        nautilus_clipboard_prepare_for_files (clipboard, files, FALSE);

        nautilus_file_list_free (files);
    }
}

static void
action_create_links_in_place (GSimpleAction *action,
                              GVariant      *state,
                              gpointer       user_data)
{
    NautilusFilesView *view;
    g_autolist (NautilusFile) selection = NULL;
    GList *item_uris;
    GList *l;
    char *destination_uri;

    view = NAUTILUS_FILES_VIEW (user_data);

    selection = nautilus_files_view_get_selection_for_file_transfer (view);

    item_uris = NULL;
    for (l = selection; l != NULL; l = l->next)
    {
        item_uris = g_list_prepend (item_uris, nautilus_file_get_uri (l->data));
    }
    item_uris = g_list_reverse (item_uris);

    destination_uri = nautilus_files_view_get_backing_uri (view);

    nautilus_files_view_move_copy_items (view, item_uris, destination_uri,
                                         GDK_ACTION_LINK);

    g_list_free_full (item_uris, g_free);
}

static void
action_copy_to (GSimpleAction *action,
                GVariant      *state,
                gpointer       user_data)
{
    NautilusFilesView *view;

    view = NAUTILUS_FILES_VIEW (user_data);
    copy_or_move_selection (view, FALSE);
}

static void
action_move_to (GSimpleAction *action,
                GVariant      *state,
                gpointer       user_data)
{
    NautilusFilesView *view;

    view = NAUTILUS_FILES_VIEW (user_data);
    copy_or_move_selection (view, TRUE);
}

static void
action_paste_files_into (GSimpleAction *action,
                         GVariant      *state,
                         gpointer       user_data)
{
    NautilusFilesView *view;
    g_autolist (NautilusFile) selection = NULL;

    view = NAUTILUS_FILES_VIEW (user_data);
    selection = nautilus_files_view_get_selection (view);
    if (selection != NULL)
    {
        paste_files (view, nautilus_file_get_activation_uri (selection->data), FALSE);
    }
}

static void
real_action_rename (NautilusFilesView *view)
{
    NautilusFile *file;
    g_autolist (NautilusFile) selection = NULL;
    GtkWidget *dialog;

    g_assert (NAUTILUS_IS_FILES_VIEW (view));

    selection = nautilus_files_view_get_selection (view);

    if (selection != NULL)
    {
        /* If there is more than one file selected, invoke a batch renamer */
        if (selection->next != NULL)
        {
            GtkRoot *window = gtk_widget_get_root (GTK_WIDGET (view));

            gtk_widget_set_cursor_from_name (GTK_WIDGET (window), "progress");

            dialog = nautilus_batch_rename_dialog_new (g_steal_pointer (&selection),
                                                       window);

            adw_dialog_present (ADW_DIALOG (dialog), GTK_WIDGET (window));
        }
        else
        {
            file = NAUTILUS_FILE (selection->data);

            nautilus_files_view_rename_file_popover_new (view, file);
        }
    }
}

static void
action_rename (GSimpleAction *action,
               GVariant      *state,
               gpointer       user_data)
{
    real_action_rename (NAUTILUS_FILES_VIEW (user_data));
}

typedef struct
{
    NautilusFilesView *view;
    GHashTable *added_locations;
} ExtractData;

static void
extract_done (GList    *outputs,
              gpointer  user_data)
{
    ExtractData *data;
    GList *l;
    gboolean all_files_acknowledged;

    data = user_data;

    if (data->view == NULL)
    {
        goto out;
    }

    NautilusFilesView *self = data->view;

    g_signal_handlers_disconnect_by_func (data->view,
                                          G_CALLBACK (track_newly_added_locations),
                                          data->added_locations);

    if (outputs == NULL)
    {
        goto out;
    }

    all_files_acknowledged = TRUE;
    for (l = outputs; l && all_files_acknowledged; l = l->next)
    {
        all_files_acknowledged = g_hash_table_contains (data->added_locations,
                                                        l->data);
    }

    if (all_files_acknowledged)
    {
        g_autolist (NautilusFile) selection = NULL;

        for (l = outputs; l != NULL; l = l->next)
        {
            selection = g_list_prepend (selection,
                                        nautilus_file_get (l->data));
        }

        nautilus_files_view_set_selection (data->view, selection);
    }
    else
    {
        for (l = outputs; l != NULL; l = l->next)
        {
            gboolean acknowledged;
            g_autoptr (NautilusFile) file = nautilus_file_get (l->data);

            acknowledged = g_hash_table_contains (data->added_locations,
                                                  l->data);

            if (acknowledged)
            {
                g_hash_table_add (self->pending_reveal, file);
            }
            else
            {
                g_hash_table_add (self->awaiting_acknowledge, file);
            }
        }
    }
out:
    g_hash_table_destroy (data->added_locations);

    if (data->view != NULL)
    {
        g_object_remove_weak_pointer (G_OBJECT (data->view),
                                      (gpointer *) &data->view);
    }

    g_free (data);
}

static void
extract_files (NautilusFilesView *view,
               GList             *files,
               GFile             *destination_directory)
{
    gboolean extracting_to_current_directory;

    if (files == NULL)
    {
        return;
    }

    g_autolist (GFile) locations = nautilus_location_list_from_file_list (files);
    extracting_to_current_directory = g_file_equal (destination_directory,
                                                    nautilus_files_view_get_location (view));

    if (extracting_to_current_directory)
    {
        ExtractData *data;

        data = g_new (ExtractData, 1);
        data->view = view;
        data->added_locations = g_hash_table_new_full (g_file_hash,
                                                       (GEqualFunc) g_file_equal,
                                                       g_object_unref, NULL);


        g_object_add_weak_pointer (G_OBJECT (data->view),
                                   (gpointer *) &data->view);

        g_signal_connect_data (view,
                               "add-files",
                               G_CALLBACK (track_newly_added_locations),
                               data->added_locations,
                               NULL,
                               G_CONNECT_AFTER);

        nautilus_file_operations_extract_files (locations,
                                                destination_directory,
                                                nautilus_files_view_get_containing_window (view),
                                                NULL,
                                                extract_done,
                                                data);
    }
    else
    {
        nautilus_file_operations_extract_files (locations,
                                                destination_directory,
                                                nautilus_files_view_get_containing_window (view),
                                                NULL,
                                                NULL,
                                                NULL);
    }
}

typedef struct
{
    NautilusFilesView *view;
    GList *files;
} ExtractToData;

static void
on_extract_destination_dialog_response (GtkFileDialog *dialog,
                                        GAsyncResult  *result,
                                        gpointer       user_data)
{
    ExtractToData *data;
    g_autoptr (GFile) destination_directory = NULL;
    g_autoptr (GError) error = NULL;

    data = user_data;
    destination_directory = gtk_file_dialog_select_folder_finish (dialog, result, &error);

    if (destination_directory != NULL)
    {
        extract_files (data->view, data->files, destination_directory);
    }
    else if (error != NULL &&
             !g_error_matches (error, GTK_DIALOG_ERROR, GTK_DIALOG_ERROR_DISMISSED))
    {
        g_warning ("Error while choosing a destination folder: %s", error->message);
    }

    nautilus_file_list_free (data->files);
    g_free (data);
}

static void
extract_files_to_chosen_location (NautilusFilesView *view,
                                  GList             *files)
{
    ExtractToData *data;
    g_autoptr (GtkFileDialog) dialog = NULL;
    g_autoptr (GFile) location = NULL;

    if (files == NULL)
    {
        return;
    }

    data = g_new (ExtractToData, 1);

    dialog = gtk_file_dialog_new ();
    gtk_file_dialog_set_title (dialog, _("Select Extract Destination"));
    gtk_file_dialog_set_accept_label (dialog, _("_Select"));

    location = get_dialog_initial_location (view, files);

    gtk_file_dialog_set_initial_folder (dialog, location);

    data->view = view;
    data->files = nautilus_file_list_copy (files);

    gtk_file_dialog_select_folder (dialog,
                                   GTK_WINDOW (gtk_widget_get_root (GTK_WIDGET (view))),
                                   NULL,
                                   (GAsyncReadyCallback) on_extract_destination_dialog_response,
                                   data);
}

static void
action_extract_here (GSimpleAction *action,
                     GVariant      *state,
                     gpointer       user_data)
{
    NautilusFilesView *view;
    g_autolist (NautilusFile) selection = NULL;
    g_autoptr (GFile) location = NULL;
    g_autoptr (GFile) parent = NULL;

    view = NAUTILUS_FILES_VIEW (user_data);

    selection = nautilus_files_view_get_selection (view);
    location = nautilus_file_get_location (NAUTILUS_FILE (g_list_first (selection)->data));
    /* Get a parent from a random file. We assume all files has a common parent.
     * But don't assume the parent is the view location, since that's not the
     * case in list view when expand-folder setting is set
     */
    parent = g_file_get_parent (location);

    extract_files (view, selection, parent);
}

static void
action_extract_to (GSimpleAction *action,
                   GVariant      *state,
                   gpointer       user_data)
{
    NautilusFilesView *view;
    g_autolist (NautilusFile) selection = NULL;

    view = NAUTILUS_FILES_VIEW (user_data);

    selection = nautilus_files_view_get_selection (view);

    extract_files_to_chosen_location (view, selection);
}

static void
action_compress (GSimpleAction *action,
                 GVariant      *state,
                 gpointer       user_data)
{
    NautilusFilesView *view = user_data;

    nautilus_files_view_compress_dialog_new (view);
}

static void
send_email_done (GObject      *source_object,
                 GAsyncResult *res,
                 gpointer      user_data)
{
    GtkWindow *window = user_data;
    g_autoptr (GError) error = NULL;

    xdp_portal_compose_email_finish (XDP_PORTAL (source_object), res, &error);
    if (error != NULL)
    {
        show_dialog (_("Error sending email."),
                     error->message,
                     window,
                     GTK_MESSAGE_ERROR);
    }
}

static void
real_send_email (GStrv              attachments,
                 NautilusFilesView *view)
{
    /* Although the documentation says that addresses can be NULL, it takes
     * no action when addresses is NULL. Since we don't know the address,
     * provide an empty list */
    const char * const addresses[] = {NULL};
    g_autoptr (XdpPortal) portal = NULL;
    g_autoptr (GError) error = NULL;
    XdpParent *parent;
    GtkWidget *toplevel;

    portal = xdp_portal_initable_new (&error);

    if (error != NULL)
    {
        g_critical ("Failed to create XdpPortal instance: %s", error->message);
        return;
    }

    toplevel = gtk_widget_get_ancestor (GTK_WIDGET (view), GTK_TYPE_WINDOW);
    parent = xdp_parent_new_gtk (GTK_WINDOW (toplevel));
    xdp_portal_compose_email (portal, parent, addresses,
                              NULL, NULL, NULL, NULL, (const char * const *) attachments,
                              XDP_EMAIL_FLAG_NONE, NULL, send_email_done, toplevel);
}

static void
email_archive_ready (GFile    *new_file,
                     gboolean  success,
                     gpointer  user_data)
{
    g_autoptr (GStrvBuilder) strv_builder = NULL;
    g_auto (GStrv) attachments = NULL;
    NautilusFilesView *view = user_data;

    if (success)
    {
        strv_builder = g_strv_builder_new ();
        g_strv_builder_add (strv_builder, g_file_get_path (new_file));
        attachments = g_strv_builder_end (strv_builder);
        real_send_email (attachments, view);
    }
}

static void
action_send_email (GSimpleAction *action,
                   GVariant      *state,
                   gpointer       user_data)
{
    NautilusFilesView *view = user_data;
    g_autolist (NautilusFile) selection = NULL;
    g_auto (GStrv) attachments = NULL;
    g_autoptr (GStrvBuilder) strv_builder = NULL;
    gboolean has_directory = FALSE;

    strv_builder = g_strv_builder_new ();
    selection = nautilus_files_view_get_selection (view);

    for (GList *l = selection; l != NULL; l = l->next)
    {
        if (nautilus_file_has_local_path (l->data))
        {
            g_autoptr (GFile) location = nautilus_file_get_activation_location (l->data);
            g_strv_builder_add (strv_builder, g_file_get_path (location));
        }
        /* If there's a directory in the list, we can't attach a folder,
         * so to keep things simple let's archive the whole selection */
        if (nautilus_file_is_directory (l->data))
        {
            has_directory = TRUE;
            break;
        }
    }

    if (has_directory)
    {
        g_autolist (GFile) source_locations = nautilus_location_list_from_file_list (selection);
        g_autofree gchar *archive_directory_name = NULL;
        g_autoptr (GFile) archive_directory = NULL;
        g_autoptr (GFile) archive_location = NULL;

        archive_directory_name = g_dir_make_tmp ("nautilus-sendto-XXXXXX", NULL);
        archive_directory = g_file_new_for_path (archive_directory_name);
        archive_location = g_file_get_child (archive_directory, "archive.zip");
        nautilus_file_operations_compress (source_locations, archive_location,
                                           AUTOAR_FORMAT_ZIP, AUTOAR_FILTER_NONE,
                                           NULL,
                                           nautilus_files_view_get_containing_window (view),
                                           NULL, email_archive_ready, view);
    }
    else
    {
        attachments = g_strv_builder_end (strv_builder);
        real_send_email (attachments, view);
    }
}

static gboolean
can_run_in_terminal (NautilusFileList *selection)
{
    NautilusFile *file;

    if (!list_len_is_one (selection))
    {
        return FALSE;
    }

    file = NAUTILUS_FILE (selection->data);

    if (nautilus_file_is_launchable (file) &&
        nautilus_file_contains_text (file))
    {
        g_autofree gchar *activation_uri = NULL;
        g_autofree gchar *executable_path = NULL;

        activation_uri = nautilus_file_get_activation_uri (file);
        executable_path = g_filename_from_uri (activation_uri, NULL, NULL);

        if (executable_path != NULL)
        {
            return TRUE;
        }
    }

    return FALSE;
}

static void
action_run_in_terminal (GSimpleAction *action,
                        GVariant      *state,
                        gpointer       user_data)
{
    g_assert (NAUTILUS_IS_FILES_VIEW (user_data));

    NautilusFilesView *view = user_data;
    g_autolist (NautilusFile) selection = nautilus_files_view_get_selection (view);

    if (!can_run_in_terminal (selection))
    {
        return;
    }

    GtkWindow *parent_window = nautilus_files_view_get_containing_window (view);
    GdkDisplay *display = gtk_widget_get_display (GTK_WIDGET (parent_window));

    g_autofree char *uri = nautilus_file_get_activation_uri (NAUTILUS_FILE (selection->data));
    g_autofree char *executable_path = g_filename_from_uri (uri, NULL, NULL);
    g_autofree char *executable_dir = g_path_get_dirname (executable_path);
    g_autofree char *quoted_path = g_shell_quote (executable_path);

    g_debug ("Launching in terminal %s", quoted_path);

    g_autofree char *old_working_dir = g_get_current_dir ();

    g_chdir (executable_dir);
    nautilus_launch_application_from_command (display, quoted_path, TRUE, NULL);
    g_chdir (old_working_dir);
}

static gboolean
can_set_wallpaper (NautilusFileList *selection)
{
    NautilusFile *file;

    if (!list_len_is_one (selection))
    {
        return FALSE;
    }

    file = NAUTILUS_FILE (selection->data);
    if (!nautilus_file_is_mime_type (file, "image/*"))
    {
        return FALSE;
    }

    /* FIXME: check file size? */

    return TRUE;
}

static void
set_wallpaper_with_portal_cb (GObject      *source,
                              GAsyncResult *result,
                              gpointer      user_data)
{
    XdpPortal *portal = XDP_PORTAL (source);
    g_autoptr (GError) error = NULL;

    if (!xdp_portal_set_wallpaper_finish (portal, result, &error)
        && !g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
    {
        g_warning ("Failed to set wallpaper via portal: %s", error->message);
    }
}

static void
set_wallpaper_with_portal (NautilusFile *file,
                           gpointer      user_data)
{
    g_autoptr (XdpPortal) portal = NULL;
    g_autoptr (GError) error = NULL;
    g_autofree gchar *uri = NULL;
    XdpParent *parent = NULL;
    GtkWidget *toplevel;

    portal = xdp_portal_initable_new (&error);

    if (error != NULL)
    {
        g_critical ("Failed to create XdpPortal instance: %s", error->message);
        return;
    }

    toplevel = gtk_widget_get_ancestor (GTK_WIDGET (user_data), GTK_TYPE_WINDOW);
    parent = xdp_parent_new_gtk (GTK_WINDOW (toplevel));
    uri = nautilus_file_get_uri (file);

    xdp_portal_set_wallpaper (portal,
                              parent,
                              uri,
                              XDP_WALLPAPER_FLAG_BACKGROUND | XDP_WALLPAPER_FLAG_PREVIEW,
                              NULL,
                              set_wallpaper_with_portal_cb,
                              NULL);
    xdp_parent_free (parent);
}

static void
action_set_as_wallpaper (GSimpleAction *action,
                         GVariant      *state,
                         gpointer       user_data)
{
    g_autolist (NautilusFile) selection = NULL;

    g_assert (NAUTILUS_IS_FILES_VIEW (user_data));

    selection = nautilus_files_view_get_selection (user_data);
    if (can_set_wallpaper (selection))
    {
        NautilusFile *file;

        file = NAUTILUS_FILE (selection->data);

        set_wallpaper_with_portal (file, user_data);
    }
}

static void
file_mount_callback (NautilusFile *file,
                     GFile        *result_location,
                     GError       *error,
                     gpointer      callback_data)
{
    g_autoptr (NautilusFilesView) self = NAUTILUS_FILES_VIEW (callback_data);
    NautilusViewItem *item = nautilus_view_model_get_item_for_file (self->model, file);

    nautilus_file_invalidate_attributes (file, NAUTILUS_FILE_ATTRIBUTE_MOUNT);
    nautilus_view_item_set_loading (item, FALSE);

    if (error != NULL &&
        (error->domain != G_IO_ERROR ||
         (error->code != G_IO_ERROR_CANCELLED &&
          error->code != G_IO_ERROR_FAILED_HANDLED &&
          error->code != G_IO_ERROR_ALREADY_MOUNTED)))
    {
        /* Translators: %s is a file name formatted for display */
        g_autofree char *text = g_strdup_printf (_("Unable to access “%s”"),
                                                 nautilus_file_get_display_name (file));
        show_dialog (text,
                     error->message,
                     GTK_WINDOW (gtk_widget_get_root (GTK_WIDGET (self))),
                     GTK_MESSAGE_ERROR);
    }
}

static void
file_unmount_callback (NautilusFile *file,
                       GFile        *result_location,
                       GError       *error,
                       gpointer      callback_data)
{
    g_autoptr (NautilusFilesView) self = NAUTILUS_FILES_VIEW (callback_data);
    NautilusViewItem *item = nautilus_view_model_get_item_for_file (self->model, file);

    nautilus_view_item_set_loading (item, FALSE);

    if (error != NULL &&
        (error->domain != G_IO_ERROR ||
         (error->code != G_IO_ERROR_CANCELLED &&
          error->code != G_IO_ERROR_FAILED_HANDLED)))
    {
        /* Translators: %s is a file name formatted for display */
        g_autofree char *text = g_strdup_printf (_("Unable to remove “%s”"),
                                                 nautilus_file_get_display_name (file));
        show_dialog (text,
                     error->message,
                     GTK_WINDOW (gtk_widget_get_root (GTK_WIDGET (self))),
                     GTK_MESSAGE_ERROR);
    }
}

static void
file_eject_callback (NautilusFile *file,
                     GFile        *result_location,
                     GError       *error,
                     gpointer      callback_data)
{
    g_autoptr (NautilusFilesView) self = NAUTILUS_FILES_VIEW (callback_data);

    if (error != NULL &&
        (error->domain != G_IO_ERROR ||
         (error->code != G_IO_ERROR_CANCELLED &&
          error->code != G_IO_ERROR_FAILED_HANDLED)))
    {
        /* Translators: %s is a file name formatted for display */
        g_autofree char *text = g_strdup_printf (_("Unable to eject “%s”"),
                                                 nautilus_file_get_display_name (file));
        show_dialog (text,
                     error->message,
                     GTK_WINDOW (gtk_widget_get_root (GTK_WIDGET (self))),
                     GTK_MESSAGE_ERROR);
    }
}

static void
file_stop_callback (NautilusFile *file,
                    GFile        *result_location,
                    GError       *error,
                    gpointer      callback_data)
{
    g_autoptr (NautilusFilesView) self = NAUTILUS_FILES_VIEW (callback_data);

    if (error != NULL &&
        (error->domain != G_IO_ERROR ||
         (error->code != G_IO_ERROR_CANCELLED &&
          error->code != G_IO_ERROR_FAILED_HANDLED)))
    {
        show_dialog (_("Unable to stop drive"),
                     error->message,
                     GTK_WINDOW (gtk_widget_get_root (GTK_WIDGET (self))),
                     GTK_MESSAGE_ERROR);
    }
}

static void
action_mount_volume (GSimpleAction *action,
                     GVariant      *state,
                     gpointer       user_data)
{
    NautilusFilesView *view = NAUTILUS_FILES_VIEW (user_data);
    NautilusFile *file;
    GMountOperation *mount_op;
    g_autolist (NautilusFile) selection = nautilus_files_view_get_selection (view);

    for (NautilusFileList *l = selection; l != NULL; l = l->next)
    {
        file = NAUTILUS_FILE (l->data);

        if (nautilus_file_can_mount (file))
        {
            NautilusViewItem *item = nautilus_view_model_get_item_for_file (view->model, file);

            nautilus_view_item_set_loading (item, TRUE);
            mount_op = gtk_mount_operation_new (nautilus_files_view_get_containing_window (view));
            g_mount_operation_set_password_save (mount_op, G_PASSWORD_SAVE_FOR_SESSION);
            nautilus_file_mount (file, mount_op, NULL,
                                 file_mount_callback,
                                 g_object_ref (view));
            g_object_unref (mount_op);
        }
    }
}

static void
action_unmount_volume (GSimpleAction *action,
                       GVariant      *state,
                       gpointer       user_data)
{
    NautilusFilesView *view = NAUTILUS_FILES_VIEW (user_data);
    NautilusFile *file;
    g_autolist (NautilusFile) selection = NULL;
    GList *l;

    selection = nautilus_files_view_get_selection (view);

    for (l = selection; l != NULL; l = l->next)
    {
        file = NAUTILUS_FILE (l->data);
        if (nautilus_file_can_unmount (file))
        {
            GMountOperation *mount_op;
            NautilusViewItem *item = nautilus_view_model_get_item_for_file (view->model, file);

            nautilus_view_item_set_loading (item, TRUE);
            mount_op = gtk_mount_operation_new (nautilus_files_view_get_containing_window (view));
            nautilus_file_unmount (file, mount_op, NULL,
                                   file_unmount_callback, g_object_ref (view));
            g_object_unref (mount_op);
        }
    }
}

static void
action_eject_volume (GSimpleAction *action,
                     GVariant      *state,
                     gpointer       user_data)
{
    NautilusFile *file;
    g_autolist (NautilusFile) selection = NULL;
    GList *l;
    NautilusFilesView *view;

    view = NAUTILUS_FILES_VIEW (user_data);

    selection = nautilus_files_view_get_selection (view);
    for (l = selection; l != NULL; l = l->next)
    {
        file = NAUTILUS_FILE (l->data);

        if (nautilus_file_can_eject (file))
        {
            GMountOperation *mount_op;
            mount_op = gtk_mount_operation_new (nautilus_files_view_get_containing_window (view));
            nautilus_file_eject (file, mount_op, NULL,
                                 file_eject_callback, g_object_ref (view));
            g_object_unref (mount_op);
        }
    }
}

static void
file_start_callback (NautilusFile *file,
                     GFile        *result_location,
                     GError       *error,
                     gpointer      callback_data)
{
    NautilusFilesView *view;

    view = NAUTILUS_FILES_VIEW (callback_data);

    if (error != NULL &&
        (error->domain != G_IO_ERROR ||
         (error->code != G_IO_ERROR_CANCELLED &&
          error->code != G_IO_ERROR_FAILED_HANDLED &&
          error->code != G_IO_ERROR_ALREADY_MOUNTED)))
    {
        const char *name = nautilus_file_get_display_name (file);
        /* Translators: %s is a file name formatted for display */
        g_autofree char *text = g_strdup_printf (_("Unable to start “%s”"), name);
        show_dialog (text,
                     error->message,
                     GTK_WINDOW (gtk_widget_get_root (GTK_WIDGET (view))),
                     GTK_MESSAGE_ERROR);
    }
}

static void
action_start_volume (GSimpleAction *action,
                     GVariant      *state,
                     gpointer       user_data)
{
    NautilusFile *file;
    g_autolist (NautilusFile) selection = NULL;
    GList *l;
    NautilusFilesView *view;
    GMountOperation *mount_op;

    view = NAUTILUS_FILES_VIEW (user_data);

    selection = nautilus_files_view_get_selection (view);
    for (l = selection; l != NULL; l = l->next)
    {
        file = NAUTILUS_FILE (l->data);

        if (nautilus_file_can_start (file) || nautilus_file_can_start_degraded (file))
        {
            mount_op = gtk_mount_operation_new (nautilus_files_view_get_containing_window (view));
            nautilus_file_start (file, mount_op, NULL,
                                 file_start_callback, view);
            g_object_unref (mount_op);
        }
    }
}

static void
action_stop_volume (GSimpleAction *action,
                    GVariant      *state,
                    gpointer       user_data)
{
    NautilusFile *file;
    g_autolist (NautilusFile) selection = NULL;
    GList *l;
    NautilusFilesView *view;

    view = NAUTILUS_FILES_VIEW (user_data);

    selection = nautilus_files_view_get_selection (view);
    for (l = selection; l != NULL; l = l->next)
    {
        file = NAUTILUS_FILE (l->data);

        if (nautilus_file_can_stop (file))
        {
            GMountOperation *mount_op;
            mount_op = gtk_mount_operation_new (nautilus_files_view_get_containing_window (view));
            nautilus_file_stop (file, mount_op, NULL,
                                file_stop_callback, g_object_ref (view));
            g_object_unref (mount_op);
        }
    }
}

static void
action_detect_media (GSimpleAction *action,
                     GVariant      *state,
                     gpointer       user_data)
{
    NautilusFile *file;
    g_autolist (NautilusFile) selection = NULL;
    GList *l;
    NautilusFilesView *self = user_data;

    selection = nautilus_files_view_get_selection (self);
    for (l = selection; l != NULL; l = l->next)
    {
        file = NAUTILUS_FILE (l->data);

        if (nautilus_file_can_poll_for_media (file) && !nautilus_file_is_media_check_automatic (file))
        {
            nautilus_file_poll_for_media (file);
        }
    }
}

static void
action_copy_network_address (GSimpleAction *action,
                             GVariant      *state,
                             gpointer       user_data)
{
    NautilusFilesView *self = NAUTILUS_FILES_VIEW (user_data);
    g_autolist (NautilusFile) selection = nautilus_files_view_get_selection (self);

    g_return_if_fail (list_len_is_one (selection));

    g_autofree char *address = nautilus_file_get_activation_uri (NAUTILUS_FILE (selection->data));

    gdk_clipboard_set_text (gtk_widget_get_clipboard (GTK_WIDGET (self)), address);
}

static void
action_remove_recent_server (GSimpleAction *action,
                             GVariant      *state,
                             gpointer       user_data)
{
    NautilusFilesView *self = NAUTILUS_FILES_VIEW (user_data);
    g_autolist (NautilusFile) selection = nautilus_files_view_get_selection (self);

    for (GList *l = selection; l != NULL; l = l->next)
    {
        g_autofree char *address = nautilus_file_get_activation_uri (NAUTILUS_FILE (selection->data));

        nautilus_remove_recent_server (address);
    }
}

const GActionEntry view_entries[] =
{
    /* Toolbar menu */
    { .name = "zoom-in", .activate = action_zoom_in },
    { .name = "zoom-out", .activate = action_zoom_out },
    { .name = "zoom-standard", .activate = action_zoom_standard },
    { .name = "sort", .parameter_type = "(sb)", .state = "('invalid',false)", .change_state = action_sort_order_changed },
    { .name = "show-hidden-files", .state = "true", .change_state = action_show_hidden_files },
    { .name = "visible-columns", .activate = action_visible_columns },
    /* Background menu */
    { .name = "empty-trash", .activate = action_empty_trash },
    { .name = "new-folder", .activate = action_new_folder },
    { .name = "select-all", .activate = action_select_all },
    { .name = "paste", .activate = action_paste_files },
    { .name = "copy-current-location", .activate = action_copy_current_location },
    { .name = "paste_accel", .activate = action_paste_files_accel },
    { .name = "create-link", .activate = action_create_links },
    { .name = "create-link-shortcut", .activate = action_create_links },
    /* Selection menu */
    { .name = "new-folder-with-selection", .activate = action_new_folder_with_selection },
    { .name = "open-scripts-folder", .activate = action_open_scripts_folder },
    { .name = "open-item-location", .activate = action_open_item_location },
    { .name = "open-with-default-application", .activate = action_open_with_default_application },
    { .name = "open-with-other-application", .activate = action_open_with_other_application },
    {
        .name = "open-current-directory-with-other-application",
        .activate = action_open_current_directory_with_other_application
    },
    { .name = "open-current-directory-with-files", .activate = action_open_current_directory_with_files },
    { .name = "open-item-new-window", .activate = action_open_item_new_window },
    { .name = "open-item-new-tab", .activate = action_open_item_new_tab },
    { .name = "cut", .activate = action_cut},
    { .name = "copy", .activate = action_copy},
    { .name = "create-link-in-place", .activate = action_create_links_in_place },
    { .name = "create-link-in-place-shortcut", .activate = action_create_links_in_place },
    { .name = "move-to", .activate = action_move_to},
    { .name = "copy-to", .activate = action_copy_to},
    { .name = "move-to-trash", .activate = action_move_to_trash},
    { .name = "delete-from-trash", .activate = action_delete },
    { .name = "star", .activate = action_star},
    { .name = "unstar", .activate = action_unstar},
    /* We separate the shortcut and the menu item since we want the shortcut
     * to always be available, but we don't want the menu item shown if not
     * completely necesary. Since the visibility of the menu item is based on
     * the action enability, we need to split the actions for the menu and the
     * shortcut. */
    { .name = "delete-permanently-shortcut", .activate = action_delete },
    { .name = "delete-permanently-menu-item", .activate = action_delete },
    /* This is only shown when the setting to show always delete permanently
     * is set and when the common use cases for delete permanently which uses
     * Delete as a shortcut are not needed. For instance this will be only
     * present when the setting is true and when it can trash files */
    { .name = "permanent-delete-permanently-menu-item", .activate = action_delete },
    { .name = "remove-from-recent", .activate = action_remove_from_recent },
    { .name = "restore-from-trash", .activate = action_restore_from_trash},
    { .name = "paste-into", .activate = action_paste_files_into },
    { .name = "rename", .activate = action_rename},
    { .name = "extract-here", .activate = action_extract_here },
    { .name = "extract-to", .activate = action_extract_to },
    { .name = "compress", .activate = action_compress },
    { .name = "send-email", .activate = action_send_email },
    { .name = "console", .activate = action_open_console },
    { .name = "current-directory-console", .activate = action_current_dir_open_console },
    { .name = "properties", .activate = action_properties},
    { .name = "current-directory-properties", .activate = action_current_dir_properties},
    { .name = "run-in-terminal", .activate = action_run_in_terminal },
    { .name = "set-as-wallpaper", .activate = action_set_as_wallpaper },
    { .name = "mount-volume", .activate = action_mount_volume },
    { .name = "unmount-volume", .activate = action_unmount_volume },
    { .name = "eject-volume", .activate = action_eject_volume },
    { .name = "start-volume", .activate = action_start_volume },
    { .name = "stop-volume", .activate = action_stop_volume },
    { .name = "detect-media", .activate = action_detect_media },
    /* Only in Network View */
    { .name = "copy-network-address", .activate = action_copy_network_address },
    { .name = "remove-recent-server", .activate = action_remove_recent_server },
    /* Only accesible by shorcuts */
    { .name = "select-pattern", .activate = action_select_pattern },
    { .name = "invert-selection", .activate = action_invert_selection },
    { .name = "preview-selection", .activate = action_preview_selection },
    { .name = "popup-menu", .activate = action_popup_menu },
};

static gboolean
can_paste_into_file (NautilusFile *file)
{
    if (nautilus_file_is_directory (file) &&
        nautilus_file_can_write (file))
    {
        return TRUE;
    }
    if (nautilus_file_has_activation_uri (file))
    {
        GFile *location;
        NautilusFile *activation_file;
        gboolean res;

        location = nautilus_file_get_activation_location (file);
        activation_file = nautilus_file_get (location);
        g_object_unref (location);

        /* The target location might not have data for it read yet,
         *  and we can't want to do sync I/O, so treat the unknown
         *  case as can-write */
        res = (nautilus_file_get_file_type (activation_file) == G_FILE_TYPE_UNKNOWN) ||
              (nautilus_file_get_file_type (activation_file) == G_FILE_TYPE_DIRECTORY &&
               nautilus_file_can_write (activation_file));

        nautilus_file_unref (activation_file);

        return res;
    }

    return FALSE;
}

static void
update_actions_clipboard_contents_received (GObject      *source_object,
                                            GAsyncResult *res,
                                            gpointer      user_data)
{
    NautilusFilesView *self = user_data;
    NautilusClipboard *clip = NULL;
    gboolean can_link_from_copied_files;
    gboolean settings_show_create_link;
    gboolean is_read_only;
    gboolean selection_contains_recent;
    gboolean selection_contains_starred;
    GAction *action;
    const GValue *value;
    NautilusMode mode;

    value = gdk_clipboard_read_value_finish (GDK_CLIPBOARD (source_object), res, NULL);
    if (value == NULL)
    {
        return;
    }

    if (G_VALUE_HOLDS (value, NAUTILUS_TYPE_CLIPBOARD))
    {
        clip = g_value_get_boxed (value);
    }

    if (self->in_destruction ||
        !self->active)
    {
        /* We've been destroyed or became inactive since call */
        return;
    }

    settings_show_create_link = g_settings_get_boolean (nautilus_preferences,
                                                        NAUTILUS_PREFERENCES_SHOW_CREATE_LINK);
    is_read_only = nautilus_files_view_is_read_only (self);
    selection_contains_recent = showing_recent_directory (self);
    selection_contains_starred = showing_starred_directory (self);
    can_link_from_copied_files = clip != NULL && !nautilus_clipboard_is_cut (clip) &&
                                 !selection_contains_recent && !selection_contains_starred &&
                                 !is_read_only;
    mode = nautilus_window_slot_get_mode (self->slot);

    action = g_action_map_lookup_action (G_ACTION_MAP (self->view_action_group),
                                         "create-link");
    g_simple_action_set_enabled (G_SIMPLE_ACTION (action),
                                 mode == NAUTILUS_MODE_BROWSE &&
                                 can_link_from_copied_files &&
                                 settings_show_create_link);
    action = g_action_map_lookup_action (G_ACTION_MAP (self->view_action_group),
                                         "create-link-shortcut");
    g_simple_action_set_enabled (G_SIMPLE_ACTION (action),
                                 mode == NAUTILUS_MODE_BROWSE &&
                                 can_link_from_copied_files &&
                                 !settings_show_create_link);
}

static void
update_actions_state_for_clipboard_targets (NautilusFilesView *self)
{
    GdkClipboard *clipboard;
    GdkContentFormats *formats;
    gboolean is_data_copied;
    GAction *action;

    clipboard = gtk_widget_get_clipboard (GTK_WIDGET (self));
    formats = gdk_clipboard_get_formats (clipboard);
    is_data_copied = gdk_content_formats_contain_gtype (formats, NAUTILUS_TYPE_CLIPBOARD) ||
                     gdk_content_formats_contain_gtype (formats, GDK_TYPE_FILE_LIST) ||
                     gdk_content_formats_contain_gtype (formats, G_TYPE_FILE) ||
                     gdk_content_formats_contain_gtype (formats, GDK_TYPE_TEXTURE);

    action = g_action_map_lookup_action (G_ACTION_MAP (self->view_action_group),
                                         "paste");
    /* Take into account if the action was previously disabled for other reasons,
     * like the directory not being writabble */
    g_simple_action_set_enabled (G_SIMPLE_ACTION (action),
                                 is_data_copied && g_action_get_enabled (action));

    action = g_action_map_lookup_action (G_ACTION_MAP (self->view_action_group),
                                         "paste-into");

    g_simple_action_set_enabled (G_SIMPLE_ACTION (action),
                                 is_data_copied && g_action_get_enabled (action));

    action = g_action_map_lookup_action (G_ACTION_MAP (self->view_action_group),
                                         "create-link");

    g_simple_action_set_enabled (G_SIMPLE_ACTION (action),
                                 is_data_copied && g_action_get_enabled (action));

    if (gdk_content_formats_contain_gtype (formats, NAUTILUS_TYPE_CLIPBOARD))
    {
        gdk_clipboard_read_value_async (clipboard, NAUTILUS_TYPE_CLIPBOARD,
                                        G_PRIORITY_DEFAULT,
                                        self->clipboard_cancellable,
                                        update_actions_clipboard_contents_received,
                                        self);
    }
}

static void
file_should_show_foreach (NautilusFile        *file,
                          gboolean            *show_mount,
                          gboolean            *show_unmount,
                          gboolean            *show_eject,
                          gboolean            *show_start,
                          gboolean            *show_stop,
                          gboolean            *show_poll,
                          GDriveStartStopType *start_stop_type)
{
    *show_mount = FALSE;
    *show_unmount = FALSE;
    *show_eject = FALSE;
    *show_start = FALSE;
    *show_stop = FALSE;
    *show_poll = FALSE;

    if (nautilus_file_can_eject (file))
    {
        *show_eject = TRUE;
    }

    if (nautilus_file_can_mount (file))
    {
        *show_mount = TRUE;
    }

    if (nautilus_file_can_start (file) || nautilus_file_can_start_degraded (file))
    {
        *show_start = TRUE;
    }

    if (nautilus_file_can_stop (file))
    {
        *show_stop = TRUE;
    }

    /* Dot not show both Unmount and Eject/Safe Removal; too confusing to
     * have too many menu entries */
    if (nautilus_file_can_unmount (file) && !*show_eject && !*show_stop)
    {
        *show_unmount = TRUE;
    }

    if (nautilus_file_can_poll_for_media (file) && !nautilus_file_is_media_check_automatic (file))
    {
        *show_poll = TRUE;
    }

    *start_stop_type = nautilus_file_get_start_stop_type (file);
}

static gboolean
can_restore_from_trash (NautilusFileList *files)
{
    NautilusFile *original_file;
    NautilusFile *original_dir;
    GHashTable *original_dirs_hash;
    GList *original_dirs;
    gboolean can_restore;

    original_file = NULL;
    original_dir = NULL;
    original_dirs = NULL;
    original_dirs_hash = NULL;

    if (files != NULL)
    {
        if (list_len_is_one (files))
        {
            original_file = nautilus_file_get_trash_original_file (files->data);
        }
        else
        {
            original_dirs_hash = nautilus_trashed_files_get_original_directories (files, NULL);
            if (original_dirs_hash != NULL)
            {
                original_dirs = g_hash_table_get_keys (original_dirs_hash);
                if (list_len_is_one (original_dirs))
                {
                    original_dir = nautilus_file_ref (NAUTILUS_FILE (original_dirs->data));
                }
            }
        }
    }

    can_restore = original_file != NULL || original_dirs != NULL;

    nautilus_file_unref (original_file);
    nautilus_file_unref (original_dir);
    g_list_free (original_dirs);

    if (original_dirs_hash != NULL)
    {
        g_hash_table_destroy (original_dirs_hash);
    }
    return can_restore;
}

static void
on_clipboard_owner_changed (GdkClipboard *clipboard,
                            gpointer      user_data)
{
    NautilusFilesView *self = NAUTILUS_FILES_VIEW (user_data);

    update_cut_status (self);

    /* We need to update paste and paste-like actions */
    nautilus_files_view_update_actions_state (self);
}

static gboolean
can_delete_all (GList *files)
{
    NautilusFile *file;
    GList *l;

    for (l = files; l != NULL; l = l->next)
    {
        file = l->data;
        if (!nautilus_file_can_delete (file))
        {
            return FALSE;
        }
    }
    return TRUE;
}

static gboolean
can_trash_all (GList *files)
{
    NautilusFile *file;
    GList *l;

    for (l = files; l != NULL; l = l->next)
    {
        file = l->data;
        if (!nautilus_file_can_trash (file))
        {
            return FALSE;
        }
    }
    return TRUE;
}

static gboolean
all_in_trash (GList *files)
{
    NautilusFile *file;
    GList *l;

    for (l = files; l != NULL; l = l->next)
    {
        file = l->data;
        if (!nautilus_file_is_in_trash (file))
        {
            return FALSE;
        }
    }
    return TRUE;
}

static gboolean
can_extract_all (GList *files)
{
    NautilusFile *file;
    GList *l;

    for (l = files; l != NULL; l = l->next)
    {
        file = l->data;
        if (!nautilus_file_is_archive (file))
        {
            return FALSE;
        }
    }
    return TRUE;
}

static gboolean
nautilus_handles_all_files_to_extract (GList *files)
{
    NautilusFile *file;
    GList *l;

    for (l = files; l != NULL; l = l->next)
    {
        file = l->data;
        if (!nautilus_mime_file_extracts (file))
        {
            return FALSE;
        }
    }
    return TRUE;
}

void
nautilus_files_view_update_actions_state (NautilusFilesView *self)
{
    NautilusMode mode = nautilus_window_slot_get_mode (self->slot);
    g_autolist (NautilusFile) selection = NULL;
    GList *l;
    gboolean is_network_view = NAUTILUS_IS_NETWORK_VIEW (self->list_base);
    gboolean selection_contains_home_dir;
    gboolean selection_contains_recent;
    gboolean selection_contains_search;
    gboolean selection_contains_starred;
    gboolean selection_all_in_trash;
    gboolean can_create_files;
    gboolean can_delete_files;
    gboolean can_move_files;
    gboolean can_trash_files;
    gboolean can_copy_files;
    gboolean can_paste_files_into;
    gboolean can_extract_files;
    gboolean handles_all_files_to_extract;
    gboolean can_extract_here;
    gboolean item_opens_in_view;
    gboolean is_read_only;
    gboolean is_in_trash;
    GAction *action;
    GActionGroup *view_action_group;
    gboolean show_mount;
    gboolean show_unmount;
    gboolean show_eject;
    gboolean show_start;
    gboolean show_stop;
    gboolean show_detect_media;
    gboolean settings_show_delete_permanently;
    gboolean settings_show_create_link;
    GDriveStartStopType start_stop_type;
    g_autoptr (GFile) current_location = NULL;
    gboolean can_star_current_directory;
    gboolean show_star;
    gboolean show_unstar;
    g_autoptr (GAppInfo) app_info_mailto = NULL;

    view_action_group = self->view_action_group;

    selection = nautilus_files_view_get_selection (self);
    selection_contains_home_dir = home_dir_in_selection (selection);
    selection_contains_recent = showing_recent_directory (self);
    selection_contains_starred = showing_starred_directory (self);
    selection_contains_search = nautilus_files_view_is_searching (self);
    selection_all_in_trash = all_in_trash (selection);

    is_read_only = nautilus_files_view_is_read_only (self);
    is_in_trash = showing_trash_directory (self);
    can_create_files = nautilus_files_view_supports_creating_files (self);
    can_delete_files =
        can_delete_all (selection) &&
        selection != NULL &&
        !selection_contains_home_dir;
    can_trash_files =
        can_trash_all (selection) &&
        selection != NULL &&
        !selection_contains_home_dir;
    can_copy_files = selection != NULL && !is_network_view;
    can_move_files = can_delete_files && !selection_contains_recent &&
                     !selection_contains_starred;
    can_paste_files_into = (list_len_is_one (selection) &&
                            can_paste_into_file (NAUTILUS_FILE (selection->data)));
    can_extract_files = selection != NULL &&
                        can_extract_all (selection);
    can_extract_here = nautilus_files_view_supports_extract_here (self);
    handles_all_files_to_extract = nautilus_handles_all_files_to_extract (selection);
    settings_show_delete_permanently = g_settings_get_boolean (nautilus_preferences,
                                                               NAUTILUS_PREFERENCES_SHOW_DELETE_PERMANENTLY);
    settings_show_create_link = g_settings_get_boolean (nautilus_preferences,
                                                        NAUTILUS_PREFERENCES_SHOW_CREATE_LINK);

    app_info_mailto = g_app_info_get_default_for_uri_scheme ("mailto");

    /* Right click actions
     * Selection menu actions
     */
    action = g_action_map_lookup_action (G_ACTION_MAP (view_action_group),
                                         "new-folder-with-selection");
    g_simple_action_set_enabled (G_SIMPLE_ACTION (action),
                                 mode == NAUTILUS_MODE_BROWSE &&
                                 can_create_files && can_delete_files &&
                                 (selection != NULL && selection->next != NULL) &&
                                 !selection_contains_recent
                                 && !selection_contains_starred);

    action = g_action_map_lookup_action (G_ACTION_MAP (view_action_group),
                                         "rename");
    if (selection != NULL && selection->next != NULL)
    {
        g_simple_action_set_enabled (G_SIMPLE_ACTION (action),
                                     nautilus_file_can_rename_files (selection));
    }
    else
    {
        g_simple_action_set_enabled (G_SIMPLE_ACTION (action),
                                     list_len_is_one (selection) &&
                                     nautilus_file_can_rename (selection->data));
    }

    action = g_action_map_lookup_action (G_ACTION_MAP (view_action_group),
                                         "extract-here");
    g_simple_action_set_enabled (G_SIMPLE_ACTION (action),
                                 mode == NAUTILUS_MODE_BROWSE &&
                                 can_extract_files &&
                                 !handles_all_files_to_extract &&
                                 can_extract_here);

    action = g_action_map_lookup_action (G_ACTION_MAP (view_action_group),
                                         "extract-to");
    g_simple_action_set_enabled (G_SIMPLE_ACTION (action),
                                 mode == NAUTILUS_MODE_BROWSE &&
                                 can_extract_files &&
                                 (!handles_all_files_to_extract ||
                                  can_extract_here));

    action = g_action_map_lookup_action (G_ACTION_MAP (view_action_group),
                                         "compress");
    g_simple_action_set_enabled (G_SIMPLE_ACTION (action),
                                 mode == NAUTILUS_MODE_BROWSE &&
                                 can_create_files && can_copy_files);

    action = g_action_map_lookup_action (G_ACTION_MAP (view_action_group),
                                         "open-item-location");

    g_simple_action_set_enabled (G_SIMPLE_ACTION (action),
                                 list_len_is_one (selection) &&
                                 (selection_contains_recent || selection_contains_search ||
                                  selection_contains_starred));

    item_opens_in_view = selection != NULL;

    for (l = selection; l != NULL; l = l->next)
    {
        NautilusFile *file;

        file = NAUTILUS_FILE (selection->data);

        if (!nautilus_file_opens_in_view (file))
        {
            item_opens_in_view = FALSE;
        }

        if (!item_opens_in_view)
        {
            break;
        }
    }

    action = g_action_map_lookup_action (G_ACTION_MAP (view_action_group),
                                         "open-with-default-application");
    g_simple_action_set_enabled (G_SIMPLE_ACTION (action),
                                 mode == NAUTILUS_MODE_BROWSE &&
                                 selection != NULL);

    /* Allow to select a different application to open the item */
    action = g_action_map_lookup_action (G_ACTION_MAP (view_action_group),
                                         "open-with-other-application");
    g_simple_action_set_enabled (G_SIMPLE_ACTION (action),
                                 mode == NAUTILUS_MODE_BROWSE &&
                                 selection != NULL);

    action = g_action_map_lookup_action (G_ACTION_MAP (view_action_group),
                                         "open-item-new-tab");
    g_simple_action_set_enabled (G_SIMPLE_ACTION (action),
                                 mode == NAUTILUS_MODE_BROWSE &&
                                 item_opens_in_view);
    action = g_action_map_lookup_action (G_ACTION_MAP (view_action_group),
                                         "open-item-new-window");
    g_simple_action_set_enabled (G_SIMPLE_ACTION (action),
                                 mode == NAUTILUS_MODE_BROWSE &&
                                 item_opens_in_view);
    action = g_action_map_lookup_action (G_ACTION_MAP (view_action_group),
                                         "run-in-terminal");
    g_simple_action_set_enabled (G_SIMPLE_ACTION (action),
                                 mode == NAUTILUS_MODE_BROWSE &&
                                 can_run_in_terminal (selection));
    action = g_action_map_lookup_action (G_ACTION_MAP (view_action_group),
                                         "set-as-wallpaper");
    g_simple_action_set_enabled (G_SIMPLE_ACTION (action),
                                 mode == NAUTILUS_MODE_BROWSE &&
                                 can_set_wallpaper (selection));
    action = g_action_map_lookup_action (G_ACTION_MAP (view_action_group),
                                         "restore-from-trash");
    g_simple_action_set_enabled (G_SIMPLE_ACTION (action),
                                 mode == NAUTILUS_MODE_BROWSE &&
                                 can_restore_from_trash (selection));

    action = g_action_map_lookup_action (G_ACTION_MAP (view_action_group),
                                         "move-to-trash");
    g_simple_action_set_enabled (G_SIMPLE_ACTION (action), can_trash_files);

    action = g_action_map_lookup_action (G_ACTION_MAP (view_action_group),
                                         "delete-from-trash");
    g_simple_action_set_enabled (G_SIMPLE_ACTION (action),
                                 mode == NAUTILUS_MODE_BROWSE &&
                                 can_delete_files && selection_all_in_trash);

    action = g_action_map_lookup_action (G_ACTION_MAP (view_action_group),
                                         "delete-permanently-shortcut");
    g_simple_action_set_enabled (G_SIMPLE_ACTION (action),
                                 mode == NAUTILUS_MODE_BROWSE &&
                                 can_delete_files);

    action = g_action_map_lookup_action (G_ACTION_MAP (view_action_group),
                                         "delete-permanently-menu-item");
    g_simple_action_set_enabled (G_SIMPLE_ACTION (action),
                                 mode == NAUTILUS_MODE_BROWSE &&
                                 can_delete_files && !can_trash_files &&
                                 !selection_all_in_trash && !selection_contains_recent &&
                                 !selection_contains_starred);

    action = g_action_map_lookup_action (G_ACTION_MAP (view_action_group),
                                         "permanent-delete-permanently-menu-item");
    g_simple_action_set_enabled (G_SIMPLE_ACTION (action),
                                 mode == NAUTILUS_MODE_BROWSE &&
                                 can_delete_files && can_trash_files &&
                                 settings_show_delete_permanently &&
                                 !selection_all_in_trash && !selection_contains_recent &&
                                 !selection_contains_starred);

    action = g_action_map_lookup_action (G_ACTION_MAP (view_action_group),
                                         "remove-from-recent");
    g_simple_action_set_enabled (G_SIMPLE_ACTION (action),
                                 mode == NAUTILUS_MODE_BROWSE &&
                                 selection_contains_recent && selection != NULL);

    action = g_action_map_lookup_action (G_ACTION_MAP (view_action_group),
                                         "cut");
    g_simple_action_set_enabled (G_SIMPLE_ACTION (action),
                                 mode == NAUTILUS_MODE_BROWSE &&
                                 can_move_files && !selection_contains_recent &&
                                 !selection_contains_starred);
    action = g_action_map_lookup_action (G_ACTION_MAP (view_action_group),
                                         "copy");
    g_simple_action_set_enabled (G_SIMPLE_ACTION (action),
                                 mode == NAUTILUS_MODE_BROWSE &&
                                 can_copy_files);
    action = g_action_map_lookup_action (G_ACTION_MAP (view_action_group),
                                         "create-link-in-place");
    g_simple_action_set_enabled (G_SIMPLE_ACTION (action),
                                 mode == NAUTILUS_MODE_BROWSE &&
                                 can_copy_files &&
                                 can_create_files &&
                                 settings_show_create_link);
    action = g_action_map_lookup_action (G_ACTION_MAP (view_action_group),
                                         "create-link-in-place-shortcut");
    g_simple_action_set_enabled (G_SIMPLE_ACTION (action),
                                 mode == NAUTILUS_MODE_BROWSE &&
                                 can_copy_files &&
                                 can_create_files &&
                                 !settings_show_create_link);
    action = g_action_map_lookup_action (G_ACTION_MAP (view_action_group),
                                         "send-email");
    g_simple_action_set_enabled (G_SIMPLE_ACTION (action),
                                 mode == NAUTILUS_MODE_BROWSE &&
                                 app_info_mailto != NULL);
    action = g_action_map_lookup_action (G_ACTION_MAP (view_action_group),
                                         "copy-to");
    g_simple_action_set_enabled (G_SIMPLE_ACTION (action),
                                 mode == NAUTILUS_MODE_BROWSE &&
                                 can_copy_files);
    action = g_action_map_lookup_action (G_ACTION_MAP (view_action_group),
                                         "move-to");
    g_simple_action_set_enabled (G_SIMPLE_ACTION (action),
                                 mode == NAUTILUS_MODE_BROWSE &&
                                 can_move_files && !selection_contains_recent &&
                                 !selection_contains_starred);
    action = g_action_map_lookup_action (G_ACTION_MAP (view_action_group),
                                         "preview-selection");
    g_simple_action_set_enabled (G_SIMPLE_ACTION (action), selection != NULL);
    action = g_action_map_lookup_action (G_ACTION_MAP (view_action_group),
                                         "copy-current-location");
    g_simple_action_set_enabled (G_SIMPLE_ACTION (action),
                                 !selection_contains_recent &&
                                 !selection_contains_search &&
                                 !selection_contains_starred &&
                                 !is_network_view);

    /* Drive menu */
    show_mount = (selection != NULL);
    show_unmount = (selection != NULL);
    show_eject = (selection != NULL);
    show_start = list_len_is_one (selection);
    show_stop = list_len_is_one (selection);
    show_detect_media = list_len_is_one (selection);
    for (l = selection; l != NULL && (show_mount || show_unmount
                                      || show_eject
                                      || show_start || show_stop
                                      || show_detect_media);
         l = l->next)
    {
        NautilusFile *file;
        gboolean show_mount_one;
        gboolean show_unmount_one;
        gboolean show_eject_one;
        gboolean show_start_one;
        gboolean show_stop_one;
        gboolean show_detect_media_one;

        file = NAUTILUS_FILE (l->data);
        file_should_show_foreach (file,
                                  &show_mount_one,
                                  &show_unmount_one,
                                  &show_eject_one,
                                  &show_start_one,
                                  &show_stop_one,
                                  &show_detect_media_one,
                                  &start_stop_type);

        show_mount &= show_mount_one;
        show_unmount &= show_unmount_one;
        show_eject &= show_eject_one;
        show_start &= show_start_one;
        show_stop &= show_stop_one;
        show_detect_media &= show_detect_media_one;
    }

    action = g_action_map_lookup_action (G_ACTION_MAP (view_action_group),
                                         "mount-volume");
    g_simple_action_set_enabled (G_SIMPLE_ACTION (action),
                                 show_mount);
    action = g_action_map_lookup_action (G_ACTION_MAP (view_action_group),
                                         "unmount-volume");
    g_simple_action_set_enabled (G_SIMPLE_ACTION (action),
                                 show_unmount);
    action = g_action_map_lookup_action (G_ACTION_MAP (view_action_group),
                                         "eject-volume");
    g_simple_action_set_enabled (G_SIMPLE_ACTION (action),
                                 show_eject);
    action = g_action_map_lookup_action (G_ACTION_MAP (view_action_group),
                                         "start-volume");
    g_simple_action_set_enabled (G_SIMPLE_ACTION (action),
                                 show_start);
    action = g_action_map_lookup_action (G_ACTION_MAP (view_action_group),
                                         "stop-volume");
    g_simple_action_set_enabled (G_SIMPLE_ACTION (action),
                                 show_stop);
    action = g_action_map_lookup_action (G_ACTION_MAP (view_action_group),
                                         "detect-media");
    g_simple_action_set_enabled (G_SIMPLE_ACTION (action),
                                 show_detect_media);

    /* Background menu actions */
    action = g_action_map_lookup_action (G_ACTION_MAP (view_action_group),
                                         "open-current-directory-with-other-application");
    g_simple_action_set_enabled (G_SIMPLE_ACTION (action),
                                 mode == NAUTILUS_MODE_BROWSE &&
                                 !selection_contains_recent &&
                                 !selection_contains_search &&
                                 !selection_contains_starred &&
                                 !is_network_view);
    action = g_action_map_lookup_action (G_ACTION_MAP (view_action_group),
                                         "open-current-directory-with-files");
    g_simple_action_set_enabled (G_SIMPLE_ACTION (action),
                                 mode != NAUTILUS_MODE_BROWSE &&
                                 !selection_contains_search);
    action = g_action_map_lookup_action (G_ACTION_MAP (view_action_group),
                                         "new-folder");
    g_simple_action_set_enabled (G_SIMPLE_ACTION (action),
                                 (mode == NAUTILUS_MODE_BROWSE ||
                                  mode == NAUTILUS_MODE_SAVE_FILE ||
                                  mode == NAUTILUS_MODE_SAVE_FILES ||
                                  mode == NAUTILUS_MODE_OPEN_FOLDER ||
                                  mode == NAUTILUS_MODE_OPEN_FOLDERS) &&
                                 can_create_files);

    action = g_action_map_lookup_action (G_ACTION_MAP (view_action_group),
                                         "empty-trash");

    g_simple_action_set_enabled (G_SIMPLE_ACTION (action),
                                 !nautilus_trash_monitor_is_empty () &&
                                 is_in_trash);

    action = g_action_map_lookup_action (G_ACTION_MAP (view_action_group),
                                         "paste");
    g_simple_action_set_enabled (G_SIMPLE_ACTION (action),
                                 mode == NAUTILUS_MODE_BROWSE &&
                                 !is_read_only &&
                                 !selection_contains_recent &&
                                 !is_in_trash &&
                                 !selection_contains_starred);

    action = g_action_map_lookup_action (G_ACTION_MAP (view_action_group),
                                         "paste_accel");
    g_simple_action_set_enabled (G_SIMPLE_ACTION (action),
                                 mode == NAUTILUS_MODE_BROWSE);

    action = g_action_map_lookup_action (G_ACTION_MAP (view_action_group),
                                         "paste-into");
    g_simple_action_set_enabled (G_SIMPLE_ACTION (action),
                                 mode == NAUTILUS_MODE_BROWSE &&
                                 !selection_contains_recent &&
                                 !is_in_trash &&
                                 !selection_contains_starred &&
                                 can_paste_files_into);

    action = g_action_map_lookup_action (G_ACTION_MAP (view_action_group),
                                         "console");
    g_simple_action_set_enabled (G_SIMPLE_ACTION (action),
                                 mode == NAUTILUS_MODE_BROWSE &&
                                 list_len_is_one (selection) && nautilus_file_is_directory (selection->data) &&
                                 nautilus_dbus_launcher_is_available (nautilus_dbus_launcher_get (),
                                                                      NAUTILUS_DBUS_LAUNCHER_CONSOLE));
    action = g_action_map_lookup_action (G_ACTION_MAP (view_action_group),
                                         "current-directory-console");
    g_simple_action_set_enabled (G_SIMPLE_ACTION (action),
                                 mode == NAUTILUS_MODE_BROWSE &&
                                 nautilus_dbus_launcher_is_available (nautilus_dbus_launcher_get (),
                                                                      NAUTILUS_DBUS_LAUNCHER_CONSOLE));
    action = g_action_map_lookup_action (G_ACTION_MAP (view_action_group),
                                         "properties");
    g_simple_action_set_enabled (G_SIMPLE_ACTION (action),
                                 mode == NAUTILUS_MODE_BROWSE &&
                                 (is_network_view ?
                                  (list_len_is_one (selection) &&
                                   nautilus_file_can_unmount (selection->data)) :
                                  (selection != NULL ||
                                   (!selection_contains_recent &&
                                    !selection_contains_search &&
                                    !selection_contains_starred))));
    action = g_action_map_lookup_action (G_ACTION_MAP (view_action_group),
                                         "current-directory-properties");
    g_simple_action_set_enabled (G_SIMPLE_ACTION (action),
                                 mode == NAUTILUS_MODE_BROWSE &&
                                 !selection_contains_recent &&
                                 !selection_contains_search &&
                                 !selection_contains_starred &&
                                 !is_network_view);

    /* Actions that are related to the clipboard need request, request the data
     * and update them once we have the data */
    update_actions_state_for_clipboard_targets (self);

    action = g_action_map_lookup_action (G_ACTION_MAP (view_action_group),
                                         "select-all");
    g_simple_action_set_enabled (G_SIMPLE_ACTION (action),
                                 !nautilus_files_view_is_empty (self) &&
                                 !self->loading &&
                                 !nautilus_view_model_get_single_selection (self->model));

    /* Toolbar menu actions */
    g_action_group_change_action_state (view_action_group,
                                        "show-hidden-files",
                                        g_variant_new_boolean (self->show_hidden_files));

    action = g_action_map_lookup_action (G_ACTION_MAP (view_action_group),
                                         "visible-columns");
    g_simple_action_set_enabled (G_SIMPLE_ACTION (action),
                                 NAUTILUS_IS_LIST_VIEW (self->list_base));

    update_zoom_actions_state (self);

    current_location = nautilus_file_get_location (self->directory_as_file);
    can_star_current_directory = nautilus_tag_manager_can_star_contents (nautilus_tag_manager_get (), current_location);

    show_star = (selection != NULL) &&
                (can_star_current_directory || selection_contains_starred);
    show_unstar = (selection != NULL) &&
                  (can_star_current_directory || selection_contains_starred);
    for (l = selection; l != NULL; l = l->next)
    {
        NautilusFile *file;
        g_autofree gchar *uri = NULL;

        file = NAUTILUS_FILE (l->data);
        uri = nautilus_file_get_uri (file);

        if (!show_star && !show_unstar)
        {
            break;
        }

        if (nautilus_tag_manager_file_is_starred (nautilus_tag_manager_get (), uri))
        {
            show_star = FALSE;
        }
        else
        {
            show_unstar = FALSE;
        }
    }

    action = g_action_map_lookup_action (G_ACTION_MAP (view_action_group),
                                         "star");
    g_simple_action_set_enabled (G_SIMPLE_ACTION (action),
                                 mode == NAUTILUS_MODE_BROWSE &&
                                 show_star);

    action = g_action_map_lookup_action (G_ACTION_MAP (view_action_group),
                                         "unstar");
    g_simple_action_set_enabled (G_SIMPLE_ACTION (action),
                                 mode == NAUTILUS_MODE_BROWSE &&
                                 show_unstar && selection_contains_starred);

    /* Network view actions */
    gboolean can_remove_recent_server = is_network_view;

    for (l = selection; l != NULL && can_remove_recent_server; l = l->next)
    {
        NautilusFile *file = NAUTILUS_FILE (l->data);
        g_autoptr (GFile) location = nautilus_file_get_location (NAUTILUS_FILE (l->data));

        /* Only recent servers have x-network-view: scheme */
        if (!g_file_has_uri_scheme (location, SCHEME_NETWORK_VIEW) || nautilus_file_can_unmount (file))
        {
            can_remove_recent_server = FALSE;
        }
    }

    action = g_action_map_lookup_action (G_ACTION_MAP (view_action_group),
                                         "copy-network-address");
    g_simple_action_set_enabled (G_SIMPLE_ACTION (action),
                                 (is_network_view &&
                                  list_len_is_one (selection) &&
                                  nautilus_file_has_activation_uri (selection->data)));

    action = g_action_map_lookup_action (G_ACTION_MAP (view_action_group),
                                         "remove-recent-server");
    g_simple_action_set_enabled (G_SIMPLE_ACTION (action), can_remove_recent_server);
}

static void
update_selection_menu (NautilusFilesView *self,
                       GtkBuilder        *builder)
{
    NautilusMode mode = nautilus_window_slot_get_mode (self->slot);
    g_autolist (NautilusFile) selection = NULL;
    GList *l;
    gboolean show_app;
    gboolean show_run;
    gboolean show_extract;
    gboolean item_opens_in_view;
    gchar *item_label;
    GAppInfo *app;
    g_autoptr (GIcon) app_icon = NULL;
    GMenuItem *menu_item;
    GObject *object;
    gboolean show_mount;
    gboolean show_unmount;
    gboolean show_eject;
    gboolean show_start;
    gboolean show_stop;
    gboolean show_detect_media;
    gboolean show_scripts = FALSE;
    gint i;
    GDriveStartStopType start_stop_type;

    selection = nautilus_files_view_get_selection (self);

    show_mount = (selection != NULL);
    show_unmount = (selection != NULL);
    show_eject = (selection != NULL);
    show_start = list_len_is_one (selection);
    show_stop = list_len_is_one (selection);
    show_detect_media = list_len_is_one (selection);
    item_opens_in_view = (selection != NULL);
    start_stop_type = G_DRIVE_START_STOP_TYPE_UNKNOWN;

    if (mode == NAUTILUS_MODE_BROWSE)
    {
        guint selection_count = g_list_length (selection);
        item_label = g_strdup_printf (ngettext ("New Folder with Selection (%'d Item)",
                                                "New Folder with Selection (%'d Items)",
                                                selection_count),
                                      selection_count);
        menu_item = g_menu_item_new (item_label, "view.new-folder-with-selection");
        g_menu_item_set_attribute (menu_item, "hidden-when", "s", "action-disabled");
        object = gtk_builder_get_object (builder, "new-folder-with-selection-section");
        g_menu_append_item (G_MENU (object), menu_item);
        g_object_unref (menu_item);
        g_free (item_label);

        /* Open With <App> menu item */
        show_extract = show_app = show_run = item_opens_in_view;
        for (l = selection; l != NULL; l = l->next)
        {
            NautilusFile *file;

            file = NAUTILUS_FILE (l->data);

            if (show_extract && !nautilus_mime_file_extracts (file))
            {
                show_extract = FALSE;
            }

            if (show_app && !nautilus_mime_file_opens_in_external_app (file))
            {
                show_app = FALSE;
            }

            if (show_run && !nautilus_mime_file_launches (file))
            {
                show_run = FALSE;
            }

            if (item_opens_in_view && !nautilus_file_opens_in_view (file))
            {
                item_opens_in_view = FALSE;
            }

            if (!show_extract && !show_app && !show_run && !item_opens_in_view)
            {
                break;
            }
        }

        item_label = NULL;
        app = NULL;
        if (show_app)
        {
            app = nautilus_mime_get_default_application_for_files (selection);
        }

        if (app != NULL)
        {
            g_autofree char *escaped_app = escape_underscores (g_app_info_get_name (app));
            item_label = g_strdup_printf (_("Open With %s"), escaped_app);

            app_icon = g_app_info_get_icon (app);
            if (app_icon != NULL)
            {
                g_object_ref (app_icon);
            }
            g_object_unref (app);
        }
        else if (show_run)
        {
            item_label = g_strdup (_("Run"));
        }
        else if (show_extract)
        {
            item_label = nautilus_files_view_supports_extract_here (self) ?
                         g_strdup (_("Extract")) :
                         g_strdup (_("Extract to…"));
        }
        else
        {
            item_label = g_strdup (_("Open"));
        }

        /* The action already exists in the submenu if item opens in view */
        if (!item_opens_in_view)
        {
            menu_item = g_menu_item_new (item_label, "view.open-with-default-application");
            if (app_icon != NULL)
            {
                g_menu_item_set_icon (menu_item, app_icon);
            }

            object = gtk_builder_get_object (builder, "open-with-application-section");
            g_menu_prepend_item (G_MENU (object), menu_item);

            g_object_unref (menu_item);
        }
        else
        {
            object = gtk_builder_get_object (builder, "open-with-application-section");
            i = nautilus_g_menu_model_find_by_string (G_MENU_MODEL (object),
                                                      "nautilus-menu-item",
                                                      "open_with_in_main_menu");
            g_menu_remove (G_MENU (object), i);
        }

        g_free (item_label);
    }

    /* The "Open" submenu should be hidden if the item doesn't open in the view. */
    object = gtk_builder_get_object (builder, "open-with-application-section");
    i = nautilus_g_menu_model_find_by_string (G_MENU_MODEL (object),
                                              "nautilus-menu-item",
                                              "open_in_view_submenu");
    nautilus_g_menu_replace_string_in_item (G_MENU (object), i,
                                            "hidden-when",
                                            !item_opens_in_view ? "action-missing" : NULL);

    /* Drives */
    for (l = selection; l != NULL && (show_mount || show_unmount
                                      || show_eject
                                      || show_start || show_stop
                                      || show_detect_media);
         l = l->next)
    {
        NautilusFile *file;
        gboolean show_mount_one;
        gboolean show_unmount_one;
        gboolean show_eject_one;
        gboolean show_start_one;
        gboolean show_stop_one;
        gboolean show_detect_media_one;

        file = NAUTILUS_FILE (l->data);
        file_should_show_foreach (file,
                                  &show_mount_one,
                                  &show_unmount_one,
                                  &show_eject_one,
                                  &show_start_one,
                                  &show_stop_one,
                                  &show_detect_media_one,
                                  &start_stop_type);

        show_mount &= show_mount_one;
        show_unmount &= show_unmount_one;
        show_eject &= show_eject_one;
        show_start &= show_start_one;
        show_stop &= show_stop_one;
        show_detect_media &= show_detect_media_one;
    }

    if (show_start)
    {
        switch (start_stop_type)
        {
            default:
            case G_DRIVE_START_STOP_TYPE_UNKNOWN:
            case G_DRIVE_START_STOP_TYPE_SHUTDOWN:
            {
                item_label = _("_Start");
            }
            break;

            case G_DRIVE_START_STOP_TYPE_NETWORK:
            {
                item_label = _("_Connect");
            }
            break;

            case G_DRIVE_START_STOP_TYPE_MULTIDISK:
            {
                item_label = _("_Start Multi-disk Drive");
            }
            break;

            case G_DRIVE_START_STOP_TYPE_PASSWORD:
            {
                item_label = _("U_nlock Drive");
            }
            break;
        }

        menu_item = g_menu_item_new (item_label, "view.start-volume");
        object = gtk_builder_get_object (builder, "drive-section");
        g_menu_append_item (G_MENU (object), menu_item);
        g_object_unref (menu_item);
    }

    if (show_stop)
    {
        switch (start_stop_type)
        {
            default:
            case G_DRIVE_START_STOP_TYPE_UNKNOWN:
            {
                item_label = _("Stop Drive");
            }
            break;

            case G_DRIVE_START_STOP_TYPE_SHUTDOWN:
            {
                item_label = _("_Safely Remove Drive");
            }
            break;

            case G_DRIVE_START_STOP_TYPE_NETWORK:
            {
                item_label = _("_Disconnect");
            }
            break;

            case G_DRIVE_START_STOP_TYPE_MULTIDISK:
            {
                item_label = _("_Stop Multi-disk Drive");
            }
            break;

            case G_DRIVE_START_STOP_TYPE_PASSWORD:
            {
                item_label = _("_Lock Drive");
            }
            break;
        }

        menu_item = g_menu_item_new (item_label, "view.stop-volume");
        object = gtk_builder_get_object (builder, "drive-section");
        g_menu_append_item (G_MENU (object), menu_item);
        g_object_unref (menu_item);
    }

    if (!self->scripts_menu_updated && mode == NAUTILUS_MODE_BROWSE)
    {
        update_scripts_menu (self, builder);
        self->scripts_menu_updated = TRUE;
    }

    if (self->scripts_menu != NULL)
    {
        show_scripts = TRUE;
        object = gtk_builder_get_object (builder, "scripts-submenu-section");
        nautilus_gmenu_set_from_model (G_MENU (object), self->scripts_menu);
    }

    object = gtk_builder_get_object (builder, "open-with-application-section");
    i = nautilus_g_menu_model_find_by_string (G_MENU_MODEL (object),
                                              "nautilus-menu-item",
                                              "scripts-submenu");
    nautilus_g_menu_replace_string_in_item (G_MENU (object), i,
                                            "hidden-when",
                                            (!show_scripts) ? "action-missing" : NULL);

    const char *view_name = NAUTILUS_IS_NETWORK_VIEW (self->list_base) ? "network" : "normal";

    /* Filter  the menus at the end to not interfere with other checks */
    nautilus_g_menu_model_set_for_view (G_MENU_MODEL (self->selection_menu_model), view_name);
    nautilus_g_menu_model_set_for_mode (G_MENU_MODEL (self->selection_menu_model), mode);
}

static void
update_background_menu (NautilusFilesView *self,
                        GtkBuilder        *builder)
{
    NautilusMode mode = nautilus_window_slot_get_mode (self->slot);
    GObject *object;
    gboolean remove_submenu = TRUE;
    gint i;

    if (nautilus_files_view_supports_creating_files (self) &&
        !showing_recent_directory (self) &&
        !showing_starred_directory (self))
    {
        if (!self->templates_menu_updated)
        {
            update_templates_menu (self, builder);
            self->templates_menu_updated = TRUE;
        }

        object = gtk_builder_get_object (builder, "templates-submenu");
        nautilus_gmenu_set_from_model (G_MENU (object), self->templates_menu);

        if (self->templates_menu != NULL)
        {
            remove_submenu = FALSE;
        }
    }
    else
    {
        /* This is necessary because the pathbar menu relies on it being NULL
         * to hide the submenu. */
        real_set_templates_menu (self, NULL);

        /* And this is necessary to regenerate the templates menu when we go
         * back to a normal folder. */
        self->templates_menu_updated = FALSE;
    }

    i = nautilus_g_menu_model_find_by_string (G_MENU_MODEL (self->background_menu_model),
                                              "nautilus-menu-item",
                                              "templates-submenu");
    nautilus_g_menu_replace_string_in_item (self->background_menu_model, i,
                                            "hidden-when",
                                            remove_submenu ? "action-missing" : NULL);

    const char *view_name = NAUTILUS_IS_NETWORK_VIEW (self->list_base) ? "network" : "normal";

    /* Filter  the menus at the end to not interfere with other checks */
    nautilus_g_menu_model_set_for_view (G_MENU_MODEL (self->background_menu_model), view_name);
    nautilus_g_menu_model_set_for_mode (G_MENU_MODEL (self->background_menu_model), mode);
}

void
nautilus_files_view_update_context_menus (NautilusFilesView *self)
{
    NautilusMode mode = nautilus_window_slot_get_mode (self->slot);
    g_autoptr (GtkBuilder) builder = NULL;
    GObject *object;

    builder = gtk_builder_new_from_resource ("/org/gnome/nautilus/ui/nautilus-files-view-context-menus.ui");

    g_clear_object (&self->background_menu_model);
    g_clear_object (&self->selection_menu_model);

    object = gtk_builder_get_object (builder, "background-menu");
    self->background_menu_model = g_object_ref (G_MENU (object));

    object = gtk_builder_get_object (builder, "selection-menu");
    self->selection_menu_model = g_object_ref (G_MENU (object));

    update_selection_menu (self, builder);
    update_background_menu (self, builder);

    if (mode == NAUTILUS_MODE_BROWSE)
    {
        update_extensions_menus (self, builder);
    }

    nautilus_files_view_update_actions_state (self);
}

static void
nautilus_files_view_reset_view_menu (NautilusFilesView *self)
{
    NautilusFile *file = self->directory_as_file;
    GMenuModel *sort_section = self->toolbar_menu_sections->sort_section;
    const gchar *action;
    gint i;

    /* When not in the special location, set an inexistant action to hide the
     * menu item. This works under the assumptiont that the menu item has its
     * "hidden-when" attribute set to "action-disabled", and that an inexistant
     * action is treated as a disabled action. */
    action = nautilus_file_is_in_trash (file) ? "view.sort" : "doesnt-exist";
    i = nautilus_g_menu_model_find_by_string (sort_section, "nautilus-menu-item", "last_trashed");
    nautilus_g_menu_replace_string_in_item (G_MENU (sort_section), i, "action", action);

    action = nautilus_file_is_in_recent (file) ? "view.sort" : "doesnt-exist";
    i = nautilus_g_menu_model_find_by_string (sort_section, "nautilus-menu-item", "recency");
    nautilus_g_menu_replace_string_in_item (G_MENU (sort_section), i, "action", action);

    action = nautilus_file_is_in_search (file) ? "view.sort" : "doesnt-exist";
    i = nautilus_g_menu_model_find_by_string (sort_section, "nautilus-menu-item", "relevance");
    nautilus_g_menu_replace_string_in_item (G_MENU (sort_section), i, "action", action);
}

/* Convenience function to reset the menus owned by the view but managed on
 * the toolbar, and update them with the current state.
 * It will also update the actions state, which will also update children
 * actions state if the children subclass nautilus_files_view_update_actions_state
 */
void
nautilus_files_view_update_toolbar_menus (NautilusFilesView *self)
{
    g_assert (NAUTILUS_IS_FILES_VIEW (self));

    /* Don't update after destroy (#349551),
     * or if we are not active.
     */
    if (self->in_destruction ||
        !self->active)
    {
        return;
    }

    nautilus_files_view_update_actions_state (self);
    nautilus_files_view_reset_view_menu (self);
}

static void
nautilus_files_view_pop_up_selection_context_menu (NautilusFilesView *self,
                                                   graphene_point_t  *point)
{
    g_assert (NAUTILUS_IS_FILES_VIEW (self));

    /* Make the context menu items not flash as they update to proper disabled,
     * etc. states by forcing menus to update now.
     */
    update_context_menus_if_pending (self);

    /* Destroy old popover and create a new one, to avoid duplicate submenu bugs
     * and showing old model temporarily. We don't do this when popover is
     * closed because it wouldn't activate the actions then. */
    g_clear_pointer (&self->selection_menu, gtk_widget_unparent);
    self->selection_menu = gtk_popover_menu_new_from_model (NULL);

    /* There's something related to NautilusFilesView that isn't grabbing the
     * focus back when the popover is closed. Let's force it as a workaround. */
    g_signal_connect_object (self->selection_menu, "closed",
                             G_CALLBACK (gtk_widget_grab_focus), self,
                             G_CONNECT_SWAPPED);
    gtk_widget_set_parent (self->selection_menu, GTK_WIDGET (self));
    gtk_popover_set_has_arrow (GTK_POPOVER (self->selection_menu), FALSE);
    gtk_widget_set_halign (self->selection_menu, GTK_ALIGN_START);

    gtk_popover_menu_set_menu_model (GTK_POPOVER_MENU (self->selection_menu),
                                     G_MENU_MODEL (self->selection_menu_model));

    if (point == NULL)
    {
        /* If triggered from the keyboard, popup at selection, not pointer */
        GdkRectangle rectangle;

        if (!get_selected_rectangle (self, &rectangle))
        {
            g_return_if_reached ();
        }
        gtk_popover_set_pointing_to (GTK_POPOVER (self->selection_menu),
                                     &rectangle);
    }
    else
    {
        gtk_popover_set_pointing_to (GTK_POPOVER (self->selection_menu),
                                     &(GdkRectangle){ point->x, point->y, 0, 0 });
    }
    gtk_popover_popup (GTK_POPOVER (self->selection_menu));
}

static void
nautilus_files_view_pop_up_background_context_menu (NautilusFilesView *self,
                                                    graphene_point_t  *point)
{
    g_assert (NAUTILUS_IS_FILES_VIEW (self));

    /* Make the context menu items not flash as they update to proper disabled,
     * etc. states by forcing menus to update now.
     */
    update_context_menus_if_pending (self);

    /* Destroy old popover and create a new one, to avoid duplicate submenu bugs
     * and showing old model temporarily. We don't do this when popover is
     * closed because it wouldn't activate the actions then. */
    g_clear_pointer (&self->background_menu, gtk_widget_unparent);
    self->background_menu = gtk_popover_menu_new_from_model (NULL);

    /* There's something related to NautilusFilesView that isn't grabbing the
     * focus back when the popover is closed. Let's force it as a workaround. */
    g_signal_connect_object (self->background_menu, "closed",
                             G_CALLBACK (gtk_widget_grab_focus), self,
                             G_CONNECT_SWAPPED);
    gtk_widget_set_parent (self->background_menu, GTK_WIDGET (self));
    gtk_popover_set_has_arrow (GTK_POPOVER (self->background_menu), FALSE);
    gtk_widget_set_halign (self->background_menu, GTK_ALIGN_START);
    gtk_popover_menu_set_menu_model (GTK_POPOVER_MENU (self->background_menu),
                                     G_MENU_MODEL (self->background_menu_model));

    GdkRectangle rect = { 0 };

    if (point == NULL &&
        gtk_widget_get_direction (GTK_WIDGET (self)) == GTK_TEXT_DIR_RTL)
    {
        rect.x = (float) gtk_widget_get_width (GTK_WIDGET (self));
    }
    else if (point != NULL)
    {
        rect.x = point->x;
        rect.y = point->y;
    }

    gtk_popover_set_pointing_to (GTK_POPOVER (self->background_menu), &rect);

    gtk_popover_popup (GTK_POPOVER (self->background_menu));
}

static void
schedule_update_context_menus (NautilusFilesView *self)
{
    g_assert (NAUTILUS_IS_FILES_VIEW (self));

    /* Don't schedule updates after destroy (#349551),
     * or if we are not active.
     */
    if (self->in_destruction ||
        !self->active)
    {
        return;
    }

    /* Schedule a menu update with the current update interval */
    if (self->update_context_menus_timeout_id == 0)
    {
        self->update_context_menus_timeout_id
            = g_timeout_add (self->update_interval, update_context_menus_timeout_callback, self);
    }
}

static void
remove_update_status_idle_callback (NautilusFilesView *self)
{
    if (self->update_status_idle_id != 0)
    {
        g_source_remove (self->update_status_idle_id);
        self->update_status_idle_id = 0;
    }
}

static gboolean
update_status_idle_callback (gpointer data)
{
    NautilusFilesView *self = NAUTILUS_FILES_VIEW (data);

    nautilus_files_view_display_selection_info (self);
    self->update_status_idle_id = 0;

    return FALSE;
}

static void
schedule_update_status (NautilusFilesView *self)
{
    g_assert (NAUTILUS_IS_FILES_VIEW (self));

    /* Make sure we haven't already destroyed it */
    if (self->in_destruction)
    {
        return;
    }

    if (self->loading)
    {
        /* Don't update status bar while loading the dir */
        return;
    }

    if (self->update_status_idle_id == 0)
    {
        self->update_status_idle_id =
            g_idle_add_full (G_PRIORITY_DEFAULT_IDLE - 20,
                             update_status_idle_callback, self, NULL);
    }
}

static void
nautilus_files_view_notify_selection_changed (NautilusFilesView *view)
{
    GtkWindow *window;

    g_return_if_fail (NAUTILUS_IS_FILES_VIEW (view));

    window = nautilus_files_view_get_containing_window (view);
    g_debug ("Selection changed in window %p", window);

    if (g_getenv ("G_MESSAGES_DEBUG") != NULL)
    {
        g_autolist (NautilusFile) selection = nautilus_files_view_get_selection (view);
        nautilus_file_list_debug (selection);
    }

    view->selection_was_removed = FALSE;

    /* Schedule a display of the new selection. */
    if (view->display_selection_idle_id == 0)
    {
        view->display_selection_idle_id
            = g_idle_add (display_selection_info_idle_callback,
                          view);
    }

    nautilus_files_view_update_actions_state (view);
    schedule_update_context_menus (view);
}

static void
view_directory_changed_callback (NautilusFile *file,
                                 gpointer      callback_data)
{
    NautilusFilesView *view = NAUTILUS_FILES_VIEW (callback_data);

    schedule_changes (view);

    schedule_update_context_menus (view);
    schedule_update_status (view);
}

static void
files_view_clear (NautilusFilesView *self)
{
    nautilus_view_model_remove_all_items (self->model);
}

static void
emit_clear (NautilusFilesView *self)
{
    if (self->search_transition_timeout_id != 0)
    {
        /* Scheduled to be emitted later. */
        return;
    }

    g_signal_emit (self, signals[CLEAR], 0);
}

static void
emit_begin_loading (NautilusFilesView *self)
{
    if (self->search_transition_timeout_id != 0)
    {
        /* Mark it to be emitted later, as we haven't cleared old contents yet. */
        self->begin_loading_delayed = TRUE;
        return;
    }
    self->begin_loading_delayed = FALSE;

    /* Tell interested parties that we've begun loading this directory now.
     * Subclasses use this to know that the new metadata is now available.
     */
    g_signal_emit (self, signals[BEGIN_LOADING], 0);
}

static void
search_transition_emit_delayed_signals (gpointer user_data)
{
    NautilusFilesView *self = NAUTILUS_FILES_VIEW (user_data);
    self->search_transition_timeout_id = 0;

    emit_clear (self);

    if (self->begin_loading_delayed)
    {
        emit_begin_loading (self);
    }
}

static void
search_transition_emit_delayed_signals_if_pending (NautilusFilesView *self)
{
    if (self->search_transition_timeout_id != 0)
    {
        g_clear_handle_id (&self->search_transition_timeout_id, g_source_remove);
        search_transition_emit_delayed_signals (self);
    }
}

static void
search_transition_schedule_delayed_signals (NautilusFilesView *self)
{
    if (self->search_transition_timeout_id == 0)
    {
        guint id = g_timeout_add_once (SEARCH_TRANSITION_TIMEOUT,
                                       search_transition_emit_delayed_signals,
                                       self);

        self->search_transition_timeout_id = id;
    }
}

/**
 * load_directory:
 *
 * Switch the displayed location to a new uri. If the uri is not valid,
 * the location will not be switched; user feedback will be provided instead.
 * @view: NautilusFilesView whose location will be changed.
 * @uri: A string representing the uri to switch to.
 *
 **/
static void
load_directory (NautilusFilesView *self,
                NautilusDirectory *directory)
{
    NautilusFileAttributes attributes;

    g_assert (NAUTILUS_IS_FILES_VIEW (self));
    g_assert (NAUTILUS_IS_DIRECTORY (directory));

    nautilus_files_view_stop_loading (self);

    /* To make search feel fast and smooth as if it were filtering the current
     * view, avoid blanking the view temporarily in the following cases:
     * 1- Going from a search to a search
     * 2- Going from a location to local search
     */
    if (NAUTILUS_IS_SEARCH_DIRECTORY (directory) &&
        (NAUTILUS_IS_SEARCH_DIRECTORY (self->directory) ||
         (self->search_query != NULL && !nautilus_query_is_global (self->search_query))))
    {
        search_transition_schedule_delayed_signals (self);
    }

    emit_clear (self);

    self->loading = TRUE;

    setup_loading_floating_bar (self);

    g_clear_pointer (&self->subdirectories_loading, g_list_free);
    while (self->subdirectory_list != NULL)
    {
        nautilus_files_view_remove_subdirectory (self,
                                                 self->subdirectory_list->data);
    }

    /* Avoid freeing it and won't be able to ref it */
    if (self->directory != directory)
    {
        nautilus_directory_unref (self->directory);
        self->directory = nautilus_directory_ref (directory);
    }

    nautilus_file_unref (self->directory_as_file);
    self->directory_as_file = nautilus_directory_get_corresponding_file (directory);

    g_clear_object (&self->location);
    self->location = nautilus_directory_get_location (directory);

    g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_LOCATION]);
    g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_LOADING]);
    g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_SEARCHING]);

    /* FIXME bugzilla.gnome.org 45062: In theory, we also need to monitor metadata here (as
     * well as doing a call when ready), in case external forces
     * change the directory's file metadata.
     */
    attributes =
        NAUTILUS_FILE_ATTRIBUTE_INFO |
        NAUTILUS_FILE_ATTRIBUTE_MOUNT |
        NAUTILUS_FILE_ATTRIBUTE_FILESYSTEM_INFO;
    self->metadata_for_directory_as_file_pending = TRUE;
    self->metadata_for_files_in_directory_pending = TRUE;
    nautilus_file_call_when_ready
        (self->directory_as_file,
        attributes,
        metadata_for_directory_as_file_ready_callback, self);
    nautilus_directory_call_when_ready
        (self->directory,
        attributes,
        FALSE,
        metadata_for_files_in_directory_ready_callback, self);

    /* If capabilities change, then we need to update the menus
     * because of New Folder, and relative emblems.
     */
    attributes =
        NAUTILUS_FILE_ATTRIBUTE_INFO |
        NAUTILUS_FILE_ATTRIBUTE_THUMBNAIL_INFO |
        NAUTILUS_FILE_ATTRIBUTE_FILESYSTEM_INFO;
    nautilus_file_monitor_add (self->directory_as_file,
                               &self->directory_as_file,
                               attributes);

    self->file_changed_handler_id = g_signal_connect
                                        (self->directory_as_file, "changed",
                                        G_CALLBACK (view_directory_changed_callback), self);
}

static void
finish_loading (NautilusFilesView *self)
{
    NautilusFileAttributes attributes;

    emit_begin_loading (self);

    nautilus_files_view_update_status_overlay (self);

    /* Start loading. */

    /* Connect handlers to learn about loading progress. */
    self->done_loading_handler_id = g_signal_connect (self->directory, "done-loading",
                                                      G_CALLBACK (done_loading_callback), self);
    self->load_error_handler_id = g_signal_connect (self->directory, "load-error",
                                                    G_CALLBACK (load_error_callback), self);

    /* Monitor the things needed to get the right icon. Also
     * monitor a directory's item count because the "size"
     * attribute is based on that, and the file's metadata
     * and possible custom name.
     */
    attributes =
        NAUTILUS_FILE_ATTRIBUTES_FOR_ICON |
        NAUTILUS_FILE_ATTRIBUTE_DIRECTORY_ITEM_COUNT |
        NAUTILUS_FILE_ATTRIBUTE_INFO |
        NAUTILUS_FILE_ATTRIBUTE_MOUNT |
        NAUTILUS_FILE_ATTRIBUTE_EXTENSION_INFO;

    self->files_added_handler_id = g_signal_connect
                                       (self->directory, "files-added",
                                       G_CALLBACK (files_added_callback), self);
    self->files_changed_handler_id = g_signal_connect
                                         (self->directory, "files-changed",
                                         G_CALLBACK (files_changed_callback), self);

    nautilus_directory_file_monitor_add (self->directory,
                                         &self->directory,
                                         self->show_hidden_files,
                                         attributes,
                                         files_added_callback, self);

    /* If escaping search we can release the search directory now that the view
     * is now monitoring the base directory directly. */
    g_clear_object (&self->outgoing_search);

    if (nautilus_directory_are_all_files_seen (self->directory))
    {
        display_pending_files_with_tradeoff (self);
    }
}

static void
finish_loading_if_all_metadata_loaded (NautilusFilesView *self)
{
    if (!self->metadata_for_directory_as_file_pending &&
        !self->metadata_for_files_in_directory_pending)
    {
        finish_loading (self);
    }
}

static void
metadata_for_directory_as_file_ready_callback (NautilusFile *file,
                                               gpointer      callback_data)
{
    NautilusFilesView *self = callback_data;

    g_assert (NAUTILUS_IS_FILES_VIEW (self));
    g_assert (self->directory_as_file == file);
    g_assert (self->metadata_for_directory_as_file_pending);

    self->metadata_for_directory_as_file_pending = FALSE;

    finish_loading_if_all_metadata_loaded (self);
}

static void
metadata_for_files_in_directory_ready_callback (NautilusDirectory *directory,
                                                GList             *files,
                                                gpointer           callback_data)
{
    NautilusFilesView *self = callback_data;

    g_assert (NAUTILUS_IS_FILES_VIEW (self));
    g_assert (self->directory == directory);
    g_assert (self->metadata_for_files_in_directory_pending);

    self->metadata_for_files_in_directory_pending = FALSE;

    finish_loading_if_all_metadata_loaded (self);
}

static void
disconnect_directory_handlers (NautilusFilesView *self)
{
    if (self->directory == NULL)
    {
        return;
    }
    g_clear_signal_handler (&self->files_added_handler_id, self->directory);
    g_clear_signal_handler (&self->files_changed_handler_id, self->directory);
    g_clear_signal_handler (&self->done_loading_handler_id, self->directory);
    g_clear_signal_handler (&self->load_error_handler_id, self->directory);
    g_clear_signal_handler (&self->file_changed_handler_id, self->directory_as_file);
    nautilus_file_cancel_call_when_ready (self->directory_as_file,
                                          metadata_for_directory_as_file_ready_callback,
                                          self);
    nautilus_directory_cancel_callback (self->directory,
                                        metadata_for_files_in_directory_ready_callback,
                                        self);
    nautilus_directory_file_monitor_remove (self->directory,
                                            &self->directory);
    nautilus_file_monitor_remove (self->directory_as_file,
                                  &self->directory_as_file);
}

/**
 * nautilus_files_view_stop_loading:
 *
 * Stop the current ongoing process, such as switching to a new uri.
 * @view: NautilusFilesView in question.
 *
 **/
void
nautilus_files_view_stop_loading (NautilusFilesView *self)
{
    g_return_if_fail (NAUTILUS_IS_FILES_VIEW (self));

    unschedule_display_of_pending_files (self);
    reset_update_interval (self);

    /* Free extra undisplayed files */
    g_list_free_full (self->new_added_files, file_and_directory_free);
    self->new_added_files = NULL;

    g_list_free_full (self->new_changed_files, file_and_directory_free);
    self->new_changed_files = NULL;

    g_list_free_full (self->pending_selection, g_object_unref);
    self->pending_selection = NULL;

    done_loading (self, FALSE);

    disconnect_directory_handlers (self);
}

static gboolean
nautilus_files_view_is_editable (NautilusFilesView *self)
{
    if (self->directory != NULL)
    {
        return nautilus_directory_is_editable (self->directory);
    }

    return TRUE;
}

static gboolean
nautilus_files_view_is_read_only (NautilusFilesView *self)
{
    if (!nautilus_files_view_is_editable (self))
    {
        return TRUE;
    }

    if (self->directory_as_file != NULL)
    {
        return !nautilus_file_can_write (self->directory_as_file);
    }
    return FALSE;
}

/**
 * nautilus_files_view_should_show_file
 *
 * Returns whether or not this file should be displayed based on
 * current filtering options.
 */
static gboolean
nautilus_files_view_should_show_file (NautilusFilesView *self,
                                      NautilusFile      *file)
{
    return nautilus_file_should_show (file,
                                      self->show_hidden_files);
}

static char *
nautilus_files_view_get_uri (NautilusFilesView *self)
{
    g_return_val_if_fail (NAUTILUS_IS_FILES_VIEW (self), NULL);

    if (self->directory == NULL)
    {
        return NULL;
    }
    return nautilus_directory_get_uri (self->directory);
}

void nautilus_file_view_save_image_from_texture (NautilusFilesView *view,
                                                 GdkTexture        *texture,
                                                 const char        *dest_uri,
                                                 const char        *base_name)
{
    nautilus_file_operations_save_image_from_texture (GTK_WIDGET (view), NULL,
                                                      dest_uri,
                                                      base_name,
                                                      texture,
                                                      copy_move_done_callback,
                                                      pre_copy_move (view));
}

void
nautilus_files_view_move_copy_items (NautilusFilesView *view,
                                     const GList       *item_uris,
                                     const char        *target_uri,
                                     int                copy_action)
{
    NautilusFile *target_file;

    target_file = nautilus_file_get_existing_by_uri (target_uri);
    if (copy_action == GDK_ACTION_COPY &&
        nautilus_is_file_roller_installed () &&
        target_file != NULL &&
        nautilus_file_is_archive (target_file))
    {
        char *command, *quoted_uri, *tmp;
        const GList *l;
        GdkDisplay *display;

        /* Handle dropping onto a file-roller archiver file, instead of starting a move/copy */

        nautilus_file_unref (target_file);

        quoted_uri = g_shell_quote (target_uri);
        command = g_strconcat ("file-roller -a ", quoted_uri, NULL);
        g_free (quoted_uri);

        for (l = item_uris; l != NULL; l = l->next)
        {
            quoted_uri = g_shell_quote ((char *) l->data);

            tmp = g_strconcat (command, " ", quoted_uri, NULL);
            g_free (command);
            command = tmp;

            g_free (quoted_uri);
        }

        display = gtk_widget_get_display (GTK_WIDGET (view));
        if (display == NULL)
        {
            display = gdk_display_get_default ();
        }

        nautilus_launch_application_from_command (display, command, FALSE, NULL);
        g_free (command);

        return;
    }
    else if (copy_action == GDK_ACTION_MOVE)
    {
        nautilus_clipboard_clear_if_colliding_uris (GTK_WIDGET (view),
                                                    item_uris);
    }
    nautilus_file_unref (target_file);

    nautilus_file_operations_copy_move
        (item_uris,
        target_uri, copy_action, GTK_WIDGET (view),
        NULL,
        copy_move_done_callback, pre_copy_move (view));
}

static void
nautilus_files_view_trash_state_changed_callback (NautilusTrashMonitor *trash_monitor,
                                                  gboolean              state,
                                                  gpointer              callback_data)
{
    NautilusFilesView *view;

    view = (NautilusFilesView *) callback_data;
    g_assert (NAUTILUS_IS_FILES_VIEW (view));

    schedule_update_context_menus (view);
}

static void
nautilus_files_view_get_property (GObject    *object,
                                  guint       prop_id,
                                  GValue     *value,
                                  GParamSpec *pspec)
{
    NautilusFilesView *view = NAUTILUS_FILES_VIEW (object);

    switch (prop_id)
    {
        case PROP_LOADING:
        {
            g_value_set_boolean (value, nautilus_files_view_is_loading (view));
        }
        break;

        case PROP_SEARCHING:
        {
            g_value_set_boolean (value, nautilus_files_view_is_searching (view));
        }
        break;

        case PROP_LOCATION:
        {
            g_value_set_object (value, nautilus_files_view_get_location (view));
        }
        break;

        case PROP_SELECTION:
        {
            g_value_set_pointer (value, nautilus_files_view_get_selection (view));
        }
        break;

        case PROP_EXTENSIONS_BACKGROUND_MENU:
        {
            g_value_set_object (value, view->extensions_background_menu);
        }
        break;

        case PROP_TEMPLATES_MENU:
        {
            g_value_set_object (value, view->templates_menu);
        }
        break;

        default:
        {
            g_assert_not_reached ();
        }
        break;
    }
}

static void
nautilus_files_view_set_property (GObject      *object,
                                  guint         prop_id,
                                  const GValue *value,
                                  GParamSpec   *pspec)
{
    NautilusFilesView *self = NAUTILUS_FILES_VIEW (object);

    switch (prop_id)
    {
        case PROP_WINDOW_SLOT:
        {
            g_assert (self->slot == NULL);

            self->slot = NAUTILUS_WINDOW_SLOT (g_value_get_object (value));
        }
        break;

        case PROP_LOCATION:
        {
            nautilus_files_view_set_location (self, g_value_get_object (value));
        }
        break;

        case PROP_SELECTION:
        {
            nautilus_files_view_set_selection (self, g_value_get_pointer (value));
        }
        break;

        case PROP_EXTENSIONS_BACKGROUND_MENU:
        {
            real_set_extensions_background_menu (self,
                                                 g_value_get_object (value));
        }
        break;

        case PROP_TEMPLATES_MENU:
        {
            real_set_templates_menu (self,
                                     g_value_get_object (value));
        }
        break;

        default:
        {
            G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
        }
        break;
    }
}

static void
on_parent_changed (GObject    *object,
                   GParamSpec *pspec,
                   gpointer    user_data)
{
    GtkWidget *widget;
    NautilusFilesView *self = NAUTILUS_FILES_VIEW (object);
    GtkWidget *parent;

    widget = GTK_WIDGET (object);

    parent = gtk_widget_get_parent (widget);

    if (parent != NULL)
    {
        if (nautilus_window_slot_get_active (self->slot))
        {
            self->active = TRUE;
            gtk_widget_insert_action_group (GTK_WIDGET (gtk_widget_get_root (widget)),
                                            "view",
                                            G_ACTION_GROUP (self->view_action_group));
        }
    }
    else
    {
        remove_update_context_menus_timeout_callback (self);
        /* Only remove the action group if this is still the active view.
         * Otherwise we might be removing an action group set by a different
         * view i.e. if slot_active_changed() is called before this one.
         */
        if (self->active)
        {
            gtk_widget_insert_action_group (GTK_WIDGET (gtk_widget_get_root (GTK_WIDGET (self->slot))),
                                            "view",
                                            NULL);
        }
    }
}

/**
 * nautilus_files_view_get_search_query:
 * @self: a #NautilusFilesView
 *
 * Retrieves the current search query displayed by @self.
 *
 * Returns: (transfer none): a #NautilusQuery
 */
NautilusQuery *
nautilus_files_view_get_search_query (NautilusFilesView *self)
{
    return self->search_query;
}

/**
 * nautilus_files_view_set_search_query:
 * @self: a #NautilusFilesView
 * @query: the search query to be performed, or %NULL
 *
 * Sets the current search query performed by @self.
 */
void
nautilus_files_view_set_search_query (NautilusFilesView *self,
                                      NautilusQuery     *query)
{
    g_autoptr (GFile) location = NULL;

    g_set_object (&self->search_query, query);

    if (!nautilus_query_is_empty (query))
    {
        if (nautilus_files_view_is_searching (self))
        {
            /*
             * Reuse the search directory and reload it.
             */
            nautilus_search_directory_set_query (NAUTILUS_SEARCH_DIRECTORY (self->directory), query);
            load_directory (self, self->directory);
        }
        else
        {
            NautilusDirectory *directory;
            gchar *uri;

            uri = nautilus_search_directory_generate_new_uri ();
            location = g_file_new_for_uri (uri);

            directory = nautilus_directory_get (location);
            g_assert (NAUTILUS_IS_SEARCH_DIRECTORY (directory));
            nautilus_search_directory_set_query (NAUTILUS_SEARCH_DIRECTORY (directory), query);

            g_set_object (&self->location_before_search, self->location);
            if (self->location_before_search == NULL)
            {
                /* This may happen if switching view mode while searching, as
                 * the new view doesn't have a location. In such cases, we can
                 * assume the location before search from the query, if not NULL.
                 */
                g_autoptr (GFile) queried_location = nautilus_query_get_location (query);
                g_set_object (&self->location_before_search, queried_location);
            }

            load_directory (self, directory);

            g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_SEARCHING]);

            nautilus_directory_unref (directory);
            g_free (uri);
        }
    }
    else
    {
        if (nautilus_files_view_is_searching (self))
        {
            location = g_steal_pointer (&self->location_before_search);

            if (G_UNLIKELY (location == NULL))
            {
                g_warn_if_reached ();
                location = g_file_new_for_path (g_get_home_dir ());
                nautilus_window_slot_open_location_full (self->slot, location, NULL);
                return;
            }

            /* OPTIMIZATION: Fast and cheap loading coming back from search.
             *
             * The search directory has been monitoring its base directory. This
             * means the later's file list is already loaded and up-to-date. By
             * keeping it continuously monitored, we can avoid reloading the
             * directory from scratch, which saves time and disk/network usage.
             *
             * So, we need to keep the search directory alive until after the
             * view sets up its own monitor. This can be achieved by holding
             * adding a reference to the search directory, preventing its
             * destruction during the location change.
             */
            self->outgoing_search = g_object_ref (self->directory);

            nautilus_files_view_set_location (self, location);
        }
    }
}

/**
 * nautilus_files_view_get_location:
 * @self: a #NautilusFilesView
 *
 * Retrieves the current current location of @self.
 *
 * Returns: (transfer none): a #GFile
 */
GFile *
nautilus_files_view_get_location (NautilusFilesView *self)
{
    return self->location;
}

/**
 * nautilus_files_view_is_loading:
 * @self: a #NautilusFilesView
 *
 * Whether @self is loading the current location.
 *
 * Returns: %TRUE if @self is loading, %FALSE otherwise.
 */
gboolean
nautilus_files_view_is_loading (NautilusFilesView *self)
{
    return self->loading;
}

static void
nautilus_files_view_constructed (GObject *object)
{
    G_OBJECT_CLASS (nautilus_files_view_parent_class)->constructed (object);

    NautilusFilesView *self = NAUTILUS_FILES_VIEW (object);
    NautilusMode mode = nautilus_window_slot_get_mode (self->slot);
    gboolean use_single_selection = (mode == NAUTILUS_MODE_OPEN_FILE ||
                                     mode == NAUTILUS_MODE_OPEN_FOLDER ||
                                     mode == NAUTILUS_MODE_SAVE_FILE ||
                                     mode == NAUTILUS_MODE_SAVE_FILES);

    g_signal_connect_object (self->slot, "notify::active",
                             G_CALLBACK (slot_active_changed), self,
                             G_CONNECT_DEFAULT);

    self->model = nautilus_view_model_new (use_single_selection);
    g_object_bind_property (self->slot, "filter",
                            self->model, "filter",
                            G_BINDING_SYNC_CREATE);

    /* GtkSelectionModel::selection-changed only notifies about individual item
     * selection state changes. Changes to the selection set require listening
     * to the GListModel::items-changed signal as well. Instead of reinventing
     * the wheel, rely on GtkSelectionFilterModel to do the right thing for us.
     */
    self->selection = gtk_selection_filter_model_new (GTK_SELECTION_MODEL (self->model));
    g_signal_connect_object (self->selection, "items-changed",
                             G_CALLBACK (nautilus_files_view_notify_selection_changed), self,
                             G_CONNECT_SWAPPED);
}

static void
nautilus_files_view_class_init (NautilusFilesViewClass *klass)
{
    GObjectClass *oclass;
    GtkWidgetClass *widget_class;

    widget_class = GTK_WIDGET_CLASS (klass);
    oclass = G_OBJECT_CLASS (klass);

    oclass->constructed = nautilus_files_view_constructed;
    oclass->dispose = nautilus_files_view_dispose;
    oclass->finalize = nautilus_files_view_finalize;
    oclass->get_property = nautilus_files_view_get_property;
    oclass->set_property = nautilus_files_view_set_property;

    widget_class->grab_focus = nautilus_files_view_grab_focus;

    /**
     * NautilusFilesView:loading:
     *
     * %TRUE if the view is loading the location, %FALSE otherwise.
     */
    properties[PROP_LOADING] =
        g_param_spec_boolean ("loading",
                              "Current view is loading",
                              "Whether the current view is loading the location or not",
                              FALSE,
                              G_PARAM_READABLE);
    /**
     * NautilusFilesView:location:
     *
     * The current location of the view.
     */
    properties[PROP_LOCATION] =
        g_param_spec_object ("location",
                             "Location displayed by the view",
                             "The current location displayed by the view",
                             G_TYPE_FILE,
                             G_PARAM_READWRITE);
    /**
     * NautilusFilesView:selection:
     *
     * The current selection of the view.
     */
    properties[PROP_SELECTION] =
        g_param_spec_pointer ("selection",
                              "Selection of the view",
                              "The current selection of the view",
                              G_PARAM_READWRITE);
    /**
     * NautilusFilesView:searching:
     *
     * %TRUE if the view is searching, %FALSE otherwise.
     */
    properties[PROP_SEARCHING] =
        g_param_spec_boolean ("searching",
                              "Current view is searching",
                              "Whether the current view is searching or not",
                              FALSE,
                              G_PARAM_READABLE);

    /**
     * NautilusFilesView:extensions-background-menu:
     *
     * Menu for the background click of extensions
     */
    properties[PROP_EXTENSIONS_BACKGROUND_MENU] =
        g_param_spec_object ("extensions-background-menu",
                             "Menu for the background click of extensions",
                             "Menu for the background click of extensions",
                             G_TYPE_MENU_MODEL,
                             G_PARAM_READWRITE);
    /**
     * NautilusFilesView:templates-menu:
     *
     * Menu of templates
     */
    properties[PROP_TEMPLATES_MENU] =
        g_param_spec_object ("templates-menu",
                             "Menu of templates",
                             "Menu of templates",
                             G_TYPE_MENU_MODEL,
                             G_PARAM_READWRITE);

    properties[PROP_WINDOW_SLOT] =
        g_param_spec_object ("window-slot",
                             "Window Slot",
                             "The parent window slot reference",
                             NAUTILUS_TYPE_WINDOW_SLOT,
                             G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY);

    g_object_class_install_properties (oclass, NUM_PROPERTIES, properties);

    /*
     * NautilusFilesView::add-files:
     *
     * @view: The view which emitted the signal.
     * @files: (element-type NautilusFile) (transfer none): A list of files
     *   that were added.
     *
     * Emitted to add a set of files to the view.
     *
     */
    signals[ADD_FILES] =
        g_signal_new_class_handler ("add-files",
                                    G_TYPE_FROM_CLASS (klass),
                                    G_SIGNAL_RUN_LAST,
                                    G_CALLBACK (nautilus_files_view_add_files),
                                    NULL, NULL,
                                    g_cclosure_marshal_generic,
                                    G_TYPE_NONE, 1, G_TYPE_POINTER);
    /*
     * NautilusFilesView::begin-loading:
     *
     * @view: The view which emitted the signal.
     *
     * Emitted before any of the contents of a directory are added to the view.
     *
     */
    signals[BEGIN_LOADING] =
        g_signal_new_class_handler ("begin-loading",
                                    G_TYPE_FROM_CLASS (klass),
                                    G_SIGNAL_RUN_LAST,
                                    G_CALLBACK (files_view_begin_loading),
                                    NULL, NULL,
                                    g_cclosure_marshal_VOID__VOID,
                                    G_TYPE_NONE, 0);
    /*
     * NautilusFilesView::clear:
     *
     * @view: The view which emitted the signal.
     *
     * Emitted to empty the view of its contents.
     *
     */
    signals[CLEAR] =
        g_signal_new_class_handler ("clear",
                                    G_TYPE_FROM_CLASS (klass),
                                    G_SIGNAL_RUN_LAST,
                                    G_CALLBACK (files_view_clear),
                                    NULL, NULL,
                                    g_cclosure_marshal_VOID__VOID,
                                    G_TYPE_NONE, 0);
    /*
     * NautilusFilesView::end-file-changes:
     *
     * @view: The view which emitted the signal.
     *
     * Emitted after a set of files are added to the view.
     *
     */
    signals[END_FILE_CHANGES] =
        g_signal_new_class_handler ("end-file-changes",
                                    G_TYPE_FROM_CLASS (klass),
                                    G_SIGNAL_RUN_LAST,
                                    G_CALLBACK (files_view_end_file_changes),
                                    NULL, NULL,
                                    g_cclosure_marshal_VOID__VOID,
                                    G_TYPE_NONE, 0);
    /*
     * NautilusFilesView::end-loading:
     *
     * @view: The view which emitted the signal.
     * @all_files_seen: If all files were loaded.
     *
     * Emitted after all of the contents of a directory are added to the view.
     *
     * If all_files_seen is true, the handler may assume that no load error
     * occurred, and all files of the underlying directory were loaded.
     * Otherwise, end_loading was emitted due to cancellation, which usually
     * means that not all files are available.
     *
     */
    signals[END_LOADING] =
        g_signal_new_class_handler ("end-loading",
                                    G_TYPE_FROM_CLASS (klass),
                                    G_SIGNAL_RUN_LAST,
                                    G_CALLBACK (files_view_end_loading),
                                    NULL, NULL,
                                    g_cclosure_marshal_VOID__BOOLEAN,
                                    G_TYPE_NONE, 1, G_TYPE_BOOLEAN);
    /*
     * NautilusFilesView::file-changed:
     *
     * @view: The view which emitted the signal.
     * @file: (type NautilusFile) (transfer none): The file that was changed.
     * @directory: (type NautilusDirectory) (transfer none): The directory the
     *   file is in.
     *
     * Emitted to signal a change in a file, including the file being removed.
     *
     */
    signals[FILE_CHANGED] =
        g_signal_new_class_handler ("file-changed",
                                    G_TYPE_FROM_CLASS (klass),
                                    G_SIGNAL_RUN_LAST,
                                    G_CALLBACK (files_view_file_changed),
                                    NULL, NULL,
                                    g_cclosure_marshal_generic,
                                    G_TYPE_NONE, 2, NAUTILUS_TYPE_FILE, NAUTILUS_TYPE_DIRECTORY);
    /*
     * NautilusFilesView::remove-files:
     *
     * @view: The view which emitted the signal.
     * @files: (element-type NautilusFile) (transfer none): A list of files
     *   that were removed.
     * @directory: (type NautilusDirectory) (transfer none): The directory the
     *   files were in.
     *
     * Emitted to remove a set of files from the view.
     *
     */
    signals[REMOVE_FILES] =
        g_signal_new_class_handler ("remove-files",
                                    G_TYPE_FROM_CLASS (klass),
                                    G_SIGNAL_RUN_LAST,
                                    G_CALLBACK (files_view_remove_files),
                                    NULL, NULL,
                                    g_cclosure_marshal_generic,
                                    G_TYPE_NONE, 2, G_TYPE_POINTER /* GList<NautilusFile> */, NAUTILUS_TYPE_DIRECTORY);
    /*
     * NautilusFilesView::selection-changed:
     *
     * @view: The view which emitted the signal.
     *
     * Emitted when changes happen to files that were in selection.
     *
     */
    signals[SELECTION_CHANGED] =
        g_signal_new_class_handler ("selection-changed",
                                    G_TYPE_FROM_CLASS (klass),
                                    G_SIGNAL_RUN_LAST,
                                    0,
                                    NULL, NULL,
                                    g_cclosure_marshal_VOID__VOID,
                                    G_TYPE_NONE, 0);

    gtk_widget_class_set_template_from_resource (widget_class,
                                                 "/org/gnome/nautilus/ui/nautilus-files-view.ui");

    gtk_widget_class_bind_template_child (widget_class, NautilusFilesView, overlay);
    gtk_widget_class_bind_template_child (widget_class, NautilusFilesView, floating_bar);

    /* See also the global accelerators in init() in addition to all the local
     * ones defined below.
     */

    /* Only one delete action is enabled at a time, so we can just activate several
     * delete or trash actions with the same shortcut without worrying: only the
     * enabled one will be activated.
     */
    gtk_widget_class_add_binding_action (widget_class, GDK_KEY_KP_Delete, GDK_SHIFT_MASK, "view.delete-permanently-shortcut", NULL);
    gtk_widget_class_add_binding_action (widget_class, GDK_KEY_Delete, GDK_SHIFT_MASK, "view.delete-permanently-shortcut", NULL);
    gtk_widget_class_add_binding_action (widget_class, GDK_KEY_KP_Delete, GDK_SHIFT_MASK, "view.permanent-delete-permanently-menu-item", NULL);
    gtk_widget_class_add_binding_action (widget_class, GDK_KEY_Delete, GDK_SHIFT_MASK, "view.permanent-delete-permanently-menu-item", NULL);
    gtk_widget_class_add_binding_action (widget_class, GDK_KEY_KP_Delete, 0, "view.move-to-trash", NULL);
    gtk_widget_class_add_binding_action (widget_class, GDK_KEY_Delete, 0, "view.move-to-trash", NULL);
    gtk_widget_class_add_binding_action (widget_class, GDK_KEY_KP_Delete, 0, "view.delete-from-trash", NULL);
    gtk_widget_class_add_binding_action (widget_class, GDK_KEY_Delete, 0, "view.delete-from-trash", NULL);
    /* When trash is not available, allow the "Delete" keys to delete permanently, that is, when
     * the menu item is available, since we never make both the trash and delete-permanently-menu-item
     * actions active.
     */
    gtk_widget_class_add_binding_action (widget_class, GDK_KEY_KP_Delete, 0, "view.delete-permanently-menu-item", NULL);
    gtk_widget_class_add_binding_action (widget_class, GDK_KEY_Delete, 0, "view.delete-permanently-menu-item", NULL);

    gtk_widget_class_add_binding_action (widget_class, GDK_KEY_F2, 0, "view.rename", NULL);
    gtk_widget_class_add_binding_action (widget_class, GDK_KEY_Menu, 0, "view.popup-menu", NULL);
    gtk_widget_class_add_binding_action (widget_class, GDK_KEY_F10, GDK_SHIFT_MASK, "view.popup-menu", NULL);
    gtk_widget_class_add_binding_action (widget_class, GDK_KEY_o, GDK_CONTROL_MASK, "view.open-with-default-application", NULL);
    /* This is not necessary per-se, because it's the default activation
     * keybinding. But in order for it to appear in the context menu as a
     * keyboard shortcut, we need to bind it to the menu item action here. */
    gtk_widget_class_add_binding_action (widget_class, GDK_KEY_Return, 0, "view.open-with-default-application", NULL);
    gtk_widget_class_add_binding_action (widget_class, GDK_KEY_i, GDK_CONTROL_MASK, "view.properties", NULL);
    gtk_widget_class_add_binding_action (widget_class, GDK_KEY_Return, GDK_ALT_MASK, "view.properties", NULL);
    gtk_widget_class_add_binding_action (widget_class, GDK_KEY_a, GDK_CONTROL_MASK, "view.select-all", NULL);
    gtk_widget_class_add_binding_action (widget_class, GDK_KEY_i, GDK_CONTROL_MASK | GDK_SHIFT_MASK, "view.invert-selection", NULL);
    gtk_widget_class_add_binding_action (widget_class, GDK_KEY_m, GDK_CONTROL_MASK, "view.create-link", NULL);
    gtk_widget_class_add_binding_action (widget_class, GDK_KEY_m, GDK_CONTROL_MASK, "view.create-link-shortcut", NULL);
    gtk_widget_class_add_binding_action (widget_class, GDK_KEY_m, GDK_CONTROL_MASK | GDK_SHIFT_MASK, "view.create-link-in-place", NULL);
    gtk_widget_class_add_binding_action (widget_class, GDK_KEY_m, GDK_CONTROL_MASK | GDK_SHIFT_MASK, "view.create-link-in-place-shortcut", NULL);
    gtk_widget_class_add_binding_action (widget_class, GDK_KEY_Return, GDK_CONTROL_MASK, "view.open-item-new-tab", NULL);
    gtk_widget_class_add_binding_action (widget_class, GDK_KEY_Return, GDK_SHIFT_MASK, "view.open-item-new-window", NULL);
    gtk_widget_class_add_binding_action (widget_class, GDK_KEY_o, GDK_CONTROL_MASK | GDK_ALT_MASK, "view.open-item-location", NULL);
    gtk_widget_class_add_binding_action (widget_class, GDK_KEY_c, GDK_CONTROL_MASK, "view.copy", NULL);
    gtk_widget_class_add_binding_action (widget_class, GDK_KEY_x, GDK_CONTROL_MASK, "view.cut", NULL);
    gtk_widget_class_add_binding_action (widget_class, GDK_KEY_c, GDK_CONTROL_MASK, "view.copy-network-address", NULL);
}

static void
nautilus_files_view_init (NautilusFilesView *self)
{
    GtkBuilder *builder;
    NautilusDirectory *scripts_directory;
    NautilusDirectory *templates_directory;
    GtkEventController *controller;
    GtkShortcut *shortcut;
    gchar *templates_uri;
    GdkClipboard *clipboard;

    /* Own a reference to keep the status page alive even if reparented while
     * switching view modes. */
    self->empty_view_page = g_object_ref_sink (adw_status_page_new ());
    /* Ensure opaque background, to hide the view underneath it. */
    gtk_widget_add_css_class (self->empty_view_page, "view");

    /* Toolbar menu */
    builder = gtk_builder_new_from_resource ("/org/gnome/nautilus/ui/nautilus-toolbar-view-menu.ui");
    self->toolbar_menu_sections = g_new0 (NautilusToolbarMenuSections, 1);
    self->toolbar_menu_sections->sort_section = G_MENU_MODEL (g_object_ref (gtk_builder_get_object (builder, "sort_section")));

    g_signal_connect (self,
                      "notify::selection",
                      G_CALLBACK (nautilus_files_view_preview_update),
                      self);
    g_signal_connect (self,
                      "notify::parent",
                      G_CALLBACK (on_parent_changed),
                      NULL);

    g_object_unref (builder);

    g_type_ensure (NAUTILUS_TYPE_FLOATING_BAR);
    gtk_widget_init_template (GTK_WIDGET (self));

    g_signal_connect (self->floating_bar,
                      "stop",
                      G_CALLBACK (floating_bar_stop_cb),
                      self);

    self->pending_reveal = g_hash_table_new (NULL, NULL);
    self->awaiting_acknowledge = g_hash_table_new (NULL, NULL);

    if (set_up_scripts_directory_global ())
    {
        scripts_directory = nautilus_directory_get_by_uri (scripts_directory_uri);
        add_directory_to_scripts_directory_list (self, scripts_directory);
        nautilus_directory_unref (scripts_directory);
    }
    else
    {
        g_warning ("Ignoring scripts directory, it may be a broken link\n");
    }

    if (nautilus_should_use_templates_directory ())
    {
        templates_uri = nautilus_get_templates_directory_uri ();
        templates_directory = nautilus_directory_get_by_uri (templates_uri);
        g_free (templates_uri);
        add_directory_to_templates_directory_list (self, templates_directory);
        nautilus_directory_unref (templates_directory);
    }
    update_templates_directory (self);

    self->show_hidden_files =
        g_settings_get_boolean (gtk_filechooser_preferences, NAUTILUS_PREFERENCES_SHOW_HIDDEN_FILES);

    g_signal_connect_object (nautilus_trash_monitor_get (), "trash-state-changed",
                             G_CALLBACK (nautilus_files_view_trash_state_changed_callback), self, 0);

    /* React to clipboard changes */
    clipboard = gdk_display_get_clipboard (gdk_display_get_default ());
    g_signal_connect (clipboard, "changed",
                      G_CALLBACK (on_clipboard_owner_changed), self);

    /* Register to menu provider extension signal managing menu updates */
    g_signal_connect_object (nautilus_signaller_get_current (), "popup-menu-changed",
                             G_CALLBACK (schedule_update_context_menus), self, G_CONNECT_SWAPPED);

    g_signal_connect_object (nautilus_preferences,
                             "changed::" NAUTILUS_PREFERENCES_DEFAULT_SORT_ORDER,
                             G_CALLBACK (update_sort_order_from_metadata_and_preferences),
                             self,
                             G_CONNECT_SWAPPED);
    g_signal_connect_object (nautilus_preferences,
                             "changed::" NAUTILUS_PREFERENCES_DEFAULT_SORT_IN_REVERSE_ORDER,
                             G_CALLBACK (update_sort_order_from_metadata_and_preferences),
                             self,
                             G_CONNECT_SWAPPED);
    g_signal_connect_swapped (gtk_filechooser_preferences,
                              "changed::" NAUTILUS_PREFERENCES_SHOW_HIDDEN_FILES,
                              G_CALLBACK (show_hidden_files_changed_callback), self);
    g_signal_connect_swapped (gnome_lockdown_preferences,
                              "changed::" NAUTILUS_PREFERENCES_LOCKDOWN_COMMAND_LINE,
                              G_CALLBACK (schedule_update_context_menus), self);

    self->in_destruction = FALSE;

    self->view_action_group = G_ACTION_GROUP (g_simple_action_group_new ());
    g_action_map_add_action_entries (G_ACTION_MAP (self->view_action_group),
                                     view_entries,
                                     G_N_ELEMENTS (view_entries),
                                     self);
    gtk_widget_insert_action_group (GTK_WIDGET (self),
                                    "view",
                                    G_ACTION_GROUP (self->view_action_group));
    g_signal_connect_object (self->view_action_group, "action-state-changed::sort",
                             G_CALLBACK (on_sort_action_state_changed), self, 0);

    /* NOTE: Please do not add any key here that could interfere with
     * the rest of the app's use of those keys. Some example of keys set here
     * that broke keynav include Enter/Return, Menu, F2 and Delete keys.
     * The accelerators below are set on the window level for the sole purpose
     * of making it more convenient when you don't have the focus exactly on the
     * files view, but some keys are used in a contextual way, and those should
     * should be added in nautilus_files_view_class_init() above instead of a
     * managed accelerator, unless it really makes sense to have them managed
     * (e.g. Zoom in/out 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)))))

    self->shortcuts = GTK_SHORTCUT_CONTROLLER (gtk_shortcut_controller_new ());

    gtk_shortcut_controller_set_scope (self->shortcuts, GTK_SHORTCUT_SCOPE_MANAGED);
    gtk_widget_add_controller (GTK_WIDGET (self), GTK_EVENT_CONTROLLER (self->shortcuts));

    ADD_SHORTCUT_FOR_ACTION (self->shortcuts, "view.zoom-in", "<control>equal|<control>plus|<control>KP_Add|ZoomIn");
    ADD_SHORTCUT_FOR_ACTION (self->shortcuts, "view.zoom-out", "<control>minus|<control>KP_Subtract|ZoomOut");
    ADD_SHORTCUT_FOR_ACTION (self->shortcuts, "view.show-hidden-files", "<control>h");
    /* Despite putting copy/cut at the widget scope instead of the global one,
     * we're putting paste "managed" so that it's easy to switch between apps
     * with e.g. Alt+Tab and paste directly the copied file without having to
     * make sure the focus is on the files view.
     */
    ADD_SHORTCUT_FOR_ACTION (self->shortcuts, "view.paste_accel", "<control>v");
    ADD_SHORTCUT_FOR_ACTION (self->shortcuts, "view.new-folder", "<control><shift>n");
    ADD_SHORTCUT_FOR_ACTION (self->shortcuts, "view.select-pattern", "<control>s");
    ADD_SHORTCUT_FOR_ACTION (self->shortcuts, "view.zoom-standard", "<control>0|<control>KP_0");
    ADD_SHORTCUT_FOR_ACTION (self->shortcuts, "view.current-directory-console", "<control>period");
#undef ADD_SHORTCUT_FOR_ACTION

    /* This one should have been a keybinding, because it should trigger only
     * when the view is focused. Unfortunately, children can override bindings,
     * and such is the case of GtkListItemWidget which binds the spacebar to its
     * `|listitem.select` action.
     *
     * So, we make it a local shortcut (like keybindings are), but using the
     * capture phase instead, to trigger it first (keybindings use bubble phase).
     */
    shortcut = gtk_shortcut_new (gtk_keyval_trigger_new (GDK_KEY_space, 0),
                                 gtk_named_action_new ("view.preview-selection"));

    controller = gtk_shortcut_controller_new ();
    gtk_widget_add_controller (GTK_WIDGET (self), controller);
    /* By default, :scope is GTK_SHORTCUT_SCOPE_LOCAL, so no need to set it. */
    gtk_event_controller_set_propagation_phase (controller, GTK_PHASE_CAPTURE);
    gtk_shortcut_controller_add_shortcut (GTK_SHORTCUT_CONTROLLER (controller), shortcut);

    self->starred_cancellable = g_cancellable_new ();
    self->clipboard_cancellable = g_cancellable_new ();

    self->rename_file_popover = nautilus_rename_file_popover_new ();
    gtk_widget_set_parent (self->rename_file_popover, GTK_WIDGET (self));
}

static void
create_inner_view (NautilusFilesView *self,
                   guint              id)
{
    NautilusMode mode = nautilus_window_slot_get_mode (self->slot);

    switch (id)
    {
        case NAUTILUS_VIEW_GRID_ID:
        {
            self->list_base = NAUTILUS_LIST_BASE (nautilus_grid_view_new ());
        }
        break;

        case NAUTILUS_VIEW_LIST_ID:
        {
            self->list_base = NAUTILUS_LIST_BASE (nautilus_list_view_new ());
        }
        break;

        case NAUTILUS_VIEW_NETWORK_ID:
        {
            self->list_base = NAUTILUS_LIST_BASE (nautilus_network_view_new ());
        }
        break;

        default:
        {
            g_critical ("Unknown view type ID: %d. Falling back to list.", id);
            self->list_base = NAUTILUS_LIST_BASE (nautilus_list_view_new ());
        }
    }

    if (mode != NAUTILUS_MODE_BROWSE)
    {
        nautilus_list_base_disable_dnd (self->list_base);
    }

    gtk_overlay_set_child (GTK_OVERLAY (self->overlay), GTK_WIDGET (self->list_base));
}

/**
 * nautilus_files_view_get_toggle_icon_name:
 * @self: a #NautilusFilesView
 *
 * Returns: (transfer none): icon name to toggle @self
 */
const char *
nautilus_files_view_get_toggle_icon_name (NautilusFilesView *self)
{
    guint view_id = nautilus_list_base_get_view_info (self->list_base).view_id;

    switch (view_id)
    {
        case NAUTILUS_VIEW_LIST_ID:
        {
            return "view-grid-symbolic";
        }
        break;

        case NAUTILUS_VIEW_NETWORK_ID:
        case NAUTILUS_VIEW_GRID_ID:
        {
            return "view-list-symbolic";
        }
        break;

        default:
        {
            g_assert_not_reached ();
        }
    }
}

/**
 * nautilus_files_view_get_toggle_tooltip:
 * @self: a #NautilusFilesView
 *
 * Returns: (transfer none): a translated tooltip explanation to toggle @self
 */
const char *
nautilus_files_view_get_toggle_tooltip (NautilusFilesView *self)
{
    guint view_id = nautilus_list_base_get_view_info (self->list_base).view_id;

    switch (view_id)
    {
        case NAUTILUS_VIEW_LIST_ID:
        {
            return _("Grid View");
        }
        break;

        case NAUTILUS_VIEW_NETWORK_ID:
        case NAUTILUS_VIEW_GRID_ID:
        {
            return _("List View");
        }
        break;

        default:
        {
            g_assert_not_reached ();
        }
    }
}

void
nautilus_files_view_change (NautilusFilesView *self,
                            guint              id)
{
    /* Prepare empty page for reuse. It's not destroyed because we own it. */
    gtk_widget_unparent (self->empty_view_page);

    /* Avoid subfolder items showing up in grid view. */
    nautilus_view_model_expand_as_a_tree (self->model, FALSE);

    /* Destroy existing inner view (which is owned by the overlay) */
    gtk_overlay_set_child (GTK_OVERLAY (self->overlay), NULL);
    g_clear_pointer (&self->subdirectories_loading, g_list_free);
    while (self->subdirectory_list != NULL)
    {
        nautilus_files_view_remove_subdirectory (self,
                                                 self->subdirectory_list->data);
    }

    /* Create a new one */
    create_inner_view (self, id);

    /* Prepare directory settings before connecting the model. */
    nautilus_list_base_setup_directory (self->list_base, self->directory);

    connect_inner_view (self);

    /* Update actions, because some of them may depend on the view (e.g. zoom
     * actions and visible-columns). At the time of writing, there is no reason
     * to also update context menus. */
    nautilus_files_view_update_actions_state (self);
}

NautilusFilesView *
nautilus_files_view_new (guint               id,
                         NautilusWindowSlot *slot)
{
    NautilusFilesView *view = NULL;

    view = NAUTILUS_FILES_VIEW (g_object_new (NAUTILUS_TYPE_FILES_VIEW,
                                              "window-slot", slot,
                                              NULL));

    if (view == NULL)
    {
        g_assert_not_reached ();
    }
    else if (g_object_is_floating (view))
    {
        g_object_ref_sink (view);
    }

    create_inner_view (view, id);
    connect_inner_view (view);

    return view;
}

NautilusViewModel *
nautilus_files_view_get_private_model (NautilusFilesView *self)
{
    return self->model;
}
