summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rwxr-xr-xsrc/check-nautilus2
-rw-r--r--src/gtk/.editorconfig30
-rw-r--r--src/gtk/nautilusgtkbookmarksmanager.c643
-rw-r--r--src/gtk/nautilusgtkbookmarksmanagerprivate.h89
-rw-r--r--src/gtk/nautilusgtkplacessidebar.c5142
-rw-r--r--src/gtk/nautilusgtkplacessidebarprivate.h148
-rw-r--r--src/gtk/nautilusgtkplacesview.c2635
-rw-r--r--src/gtk/nautilusgtkplacesview.ui261
-rw-r--r--src/gtk/nautilusgtkplacesviewprivate.h55
-rw-r--r--src/gtk/nautilusgtkplacesviewrow.c508
-rw-r--r--src/gtk/nautilusgtkplacesviewrow.ui83
-rw-r--r--src/gtk/nautilusgtkplacesviewrowprivate.h59
-rw-r--r--src/gtk/nautilusgtksidebarrow.c692
-rw-r--r--src/gtk/nautilusgtksidebarrow.ui69
-rw-r--r--src/gtk/nautilusgtksidebarrowprivate.h60
-rw-r--r--src/meson.build345
-rw-r--r--src/nautilus-app-chooser.c320
-rw-r--r--src/nautilus-app-chooser.h22
-rw-r--r--src/nautilus-application.c1542
-rw-r--r--src/nautilus-application.h87
-rw-r--r--src/nautilus-autorun-software.c285
-rw-r--r--src/nautilus-batch-rename-dialog.c2040
-rw-r--r--src/nautilus-batch-rename-dialog.h232
-rw-r--r--src/nautilus-batch-rename-utilities.c1186
-rw-r--r--src/nautilus-batch-rename-utilities.h70
-rw-r--r--src/nautilus-bookmark-list.c670
-rw-r--r--src/nautilus-bookmark-list.h47
-rw-r--r--src/nautilus-bookmark.c788
-rw-r--r--src/nautilus-bookmark.h54
-rw-r--r--src/nautilus-clipboard.c349
-rw-r--r--src/nautilus-clipboard.h47
-rw-r--r--src/nautilus-column-chooser.c597
-rw-r--r--src/nautilus-column-chooser.h38
-rw-r--r--src/nautilus-column-utilities.c416
-rw-r--r--src/nautilus-column-utilities.h34
-rw-r--r--src/nautilus-compress-dialog-controller.c612
-rw-r--r--src/nautilus-compress-dialog-controller.h34
-rw-r--r--src/nautilus-dbus-launcher.c231
-rw-r--r--src/nautilus-dbus-launcher.h39
-rw-r--r--src/nautilus-dbus-manager.c660
-rw-r--r--src/nautilus-dbus-manager.h36
-rw-r--r--src/nautilus-debug.c180
-rw-r--r--src/nautilus-debug.h78
-rw-r--r--src/nautilus-directory-async.c4755
-rw-r--r--src/nautilus-directory-notify.h57
-rw-r--r--src/nautilus-directory-private.h230
-rw-r--r--src/nautilus-directory.c2085
-rw-r--r--src/nautilus-directory.h252
-rw-r--r--src/nautilus-dnd.c317
-rw-r--r--src/nautilus-dnd.h26
-rw-r--r--src/nautilus-enum-types.c.template37
-rw-r--r--src/nautilus-enum-types.h.template25
-rw-r--r--src/nautilus-enums.h81
-rw-r--r--src/nautilus-error-reporting.c446
-rw-r--r--src/nautilus-error-reporting.h53
-rw-r--r--src/nautilus-file-changes-queue.c344
-rw-r--r--src/nautilus-file-changes-queue.h31
-rw-r--r--src/nautilus-file-conflict-dialog.c307
-rw-r--r--src/nautilus-file-conflict-dialog.h62
-rw-r--r--src/nautilus-file-name-widget-controller.c522
-rw-r--r--src/nautilus-file-name-widget-controller.h52
-rw-r--r--src/nautilus-file-operations-dbus-data.c75
-rw-r--r--src/nautilus-file-operations-dbus-data.h34
-rw-r--r--src/nautilus-file-operations.c9183
-rw-r--r--src/nautilus-file-operations.h166
-rw-r--r--src/nautilus-file-private.h283
-rw-r--r--src/nautilus-file-queue.c130
-rw-r--r--src/nautilus-file-queue.h46
-rw-r--r--src/nautilus-file-undo-manager.c270
-rw-r--r--src/nautilus-file-undo-manager.h55
-rw-r--r--src/nautilus-file-undo-operations.c2639
-rw-r--r--src/nautilus-file-undo-operations.h230
-rw-r--r--src/nautilus-file-utilities.c1515
-rw-r--r--src/nautilus-file-utilities.h145
-rw-r--r--src/nautilus-file.c9586
-rw-r--r--src/nautilus-file.h590
-rw-r--r--src/nautilus-files-view-dnd.c362
-rw-r--r--src/nautilus-files-view-dnd.h51
-rw-r--r--src/nautilus-files-view.c9880
-rw-r--r--src/nautilus-files-view.h315
-rw-r--r--src/nautilus-floating-bar.c549
-rw-r--r--src/nautilus-floating-bar.h48
-rw-r--r--src/nautilus-freedesktop-dbus.c329
-rw-r--r--src/nautilus-freedesktop-dbus.h37
-rw-r--r--src/nautilus-global-preferences.c67
-rw-r--r--src/nautilus-global-preferences.h145
-rw-r--r--src/nautilus-grid-cell.c282
-rw-r--r--src/nautilus-grid-cell.h29
-rw-r--r--src/nautilus-grid-view.c604
-rw-r--r--src/nautilus-grid-view.h20
-rw-r--r--src/nautilus-history-controls.c313
-rw-r--r--src/nautilus-history-controls.h20
-rw-r--r--src/nautilus-icon-info.c505
-rw-r--r--src/nautilus-icon-info.h29
-rw-r--r--src/nautilus-icon-names.h26
-rw-r--r--src/nautilus-keyfile-metadata.c343
-rw-r--r--src/nautilus-keyfile-metadata.h43
-rw-r--r--src/nautilus-label-cell.c158
-rw-r--r--src/nautilus-label-cell.h23
-rw-r--r--src/nautilus-lib-self-check-functions.c35
-rw-r--r--src/nautilus-lib-self-check-functions.h48
-rw-r--r--src/nautilus-list-base-private.h33
-rw-r--r--src/nautilus-list-base.c1892
-rw-r--r--src/nautilus-list-base.h27
-rw-r--r--src/nautilus-list-view.c1258
-rw-r--r--src/nautilus-list-view.h22
-rw-r--r--src/nautilus-location-entry.c845
-rw-r--r--src/nautilus-location-entry.h51
-rw-r--r--src/nautilus-main.c89
-rw-r--r--src/nautilus-metadata.c55
-rw-r--r--src/nautilus-metadata.h45
-rw-r--r--src/nautilus-mime-actions.c2325
-rw-r--r--src/nautilus-mime-actions.h54
-rw-r--r--src/nautilus-module.c332
-rw-r--r--src/nautilus-module.h39
-rw-r--r--src/nautilus-monitor.c182
-rw-r--r--src/nautilus-monitor.h33
-rw-r--r--src/nautilus-name-cell.c340
-rw-r--r--src/nautilus-name-cell.h23
-rw-r--r--src/nautilus-new-folder-dialog-controller.c191
-rw-r--r--src/nautilus-new-folder-dialog-controller.h36
-rw-r--r--src/nautilus-operations-ui-manager.c688
-rw-r--r--src/nautilus-operations-ui-manager.h34
-rw-r--r--src/nautilus-pathbar.c1216
-rw-r--r--src/nautilus-pathbar.h34
-rw-r--r--src/nautilus-places-view.c413
-rw-r--r--src/nautilus-places-view.h32
-rw-r--r--src/nautilus-preferences-window.c411
-rw-r--r--src/nautilus-preferences-window.h34
-rw-r--r--src/nautilus-previewer.c207
-rw-r--r--src/nautilus-previewer.h42
-rw-r--r--src/nautilus-profile.c69
-rw-r--r--src/nautilus-profile.h53
-rw-r--r--src/nautilus-program-choosing.c611
-rw-r--r--src/nautilus-program-choosing.h59
-rw-r--r--src/nautilus-progress-indicator.c548
-rw-r--r--src/nautilus-progress-indicator.h20
-rw-r--r--src/nautilus-progress-info-manager.c239
-rw-r--r--src/nautilus-progress-info-manager.h44
-rw-r--r--src/nautilus-progress-info-widget.c223
-rw-r--r--src/nautilus-progress-info-widget.h57
-rw-r--r--src/nautilus-progress-info.c760
-rw-r--r--src/nautilus-progress-info.h82
-rw-r--r--src/nautilus-progress-persistence-handler.c384
-rw-r--r--src/nautilus-progress-persistence-handler.h37
-rw-r--r--src/nautilus-properties-window.c4457
-rw-r--r--src/nautilus-properties-window.h41
-rw-r--r--src/nautilus-query-editor.c844
-rw-r--r--src/nautilus-query-editor.h79
-rw-r--r--src/nautilus-query.c692
-rw-r--r--src/nautilus-query.h89
-rw-r--r--src/nautilus-rename-file-popover-controller.c439
-rw-r--r--src/nautilus-rename-file-popover-controller.h37
-rw-r--r--src/nautilus-search-directory-file.c313
-rw-r--r--src/nautilus-search-directory-file.h32
-rw-r--r--src/nautilus-search-directory.c1080
-rw-r--r--src/nautilus-search-directory.h43
-rw-r--r--src/nautilus-search-engine-model.c391
-rw-r--r--src/nautilus-search-engine-model.h32
-rw-r--r--src/nautilus-search-engine-private.h31
-rw-r--r--src/nautilus-search-engine-recent.c465
-rw-r--r--src/nautilus-search-engine-recent.h33
-rw-r--r--src/nautilus-search-engine-simple.c614
-rw-r--r--src/nautilus-search-engine-simple.h34
-rw-r--r--src/nautilus-search-engine-tracker.c627
-rw-r--r--src/nautilus-search-engine-tracker.h29
-rw-r--r--src/nautilus-search-engine.c588
-rw-r--r--src/nautilus-search-engine.h48
-rw-r--r--src/nautilus-search-hit.c482
-rw-r--r--src/nautilus-search-hit.h50
-rw-r--r--src/nautilus-search-popover.c1092
-rw-r--r--src/nautilus-search-popover.h52
-rw-r--r--src/nautilus-search-provider.c145
-rw-r--r--src/nautilus-search-provider.h101
-rw-r--r--src/nautilus-self-check-functions.c37
-rw-r--r--src/nautilus-self-check-functions.h46
-rw-r--r--src/nautilus-shell-search-provider.c875
-rw-r--r--src/nautilus-shell-search-provider.h39
-rw-r--r--src/nautilus-signaller.c93
-rw-r--r--src/nautilus-signaller.h41
-rw-r--r--src/nautilus-special-location-bar.c324
-rw-r--r--src/nautilus-special-location-bar.h39
-rw-r--r--src/nautilus-star-cell.c173
-rw-r--r--src/nautilus-star-cell.h19
-rw-r--r--src/nautilus-starred-directory.c573
-rw-r--r--src/nautilus-starred-directory.h33
-rw-r--r--src/nautilus-tag-manager.c1019
-rw-r--r--src/nautilus-tag-manager.h61
-rw-r--r--src/nautilus-thumbnails.c598
-rw-r--r--src/nautilus-thumbnails.h35
-rw-r--r--src/nautilus-toolbar-menu-sections.h30
-rw-r--r--src/nautilus-toolbar.c644
-rw-r--r--src/nautilus-toolbar.h54
-rw-r--r--src/nautilus-tracker-utilities.c148
-rw-r--r--src/nautilus-tracker-utilities.h31
-rw-r--r--src/nautilus-trash-monitor.c262
-rw-r--r--src/nautilus-trash-monitor.h35
-rw-r--r--src/nautilus-types.h47
-rw-r--r--src/nautilus-ui-utilities.c420
-rw-r--r--src/nautilus-ui-utilities.h59
-rw-r--r--src/nautilus-undo-private.h30
-rw-r--r--src/nautilus-vfs-directory.c121
-rw-r--r--src/nautilus-vfs-directory.h49
-rw-r--r--src/nautilus-vfs-file.c734
-rw-r--r--src/nautilus-vfs-file.h49
-rw-r--r--src/nautilus-video-mime-types.h65
-rw-r--r--src/nautilus-view-cell.c190
-rw-r--r--src/nautilus-view-cell.h31
-rw-r--r--src/nautilus-view-controls.c187
-rw-r--r--src/nautilus-view-controls.h20
-rw-r--r--src/nautilus-view-item.c280
-rw-r--r--src/nautilus-view-item.h40
-rw-r--r--src/nautilus-view-model.c422
-rw-r--r--src/nautilus-view-model.h34
-rw-r--r--src/nautilus-view.c369
-rw-r--r--src/nautilus-view.h121
-rw-r--r--src/nautilus-window-slot-dnd.c346
-rw-r--r--src/nautilus-window-slot-dnd.h37
-rw-r--r--src/nautilus-window-slot.c3417
-rw-r--r--src/nautilus-window-slot.h120
-rw-r--r--src/nautilus-window.c2328
-rw-r--r--src/nautilus-window.h121
-rw-r--r--src/nautilus-x-content-bar.c357
-rw-r--r--src/nautilus-x-content-bar.h38
-rw-r--r--src/resources/Checkerboard.pngbin0 -> 184 bytes
-rw-r--r--src/resources/gtk/help-overlay.ui406
-rw-r--r--src/resources/icons/external-link-symbolic.svg2
-rw-r--r--src/resources/icons/funnel-symbolic.svg2
-rw-r--r--src/resources/icons/quotation-symbolic.svg2
-rw-r--r--src/resources/nautilus.gresource.xml45
-rw-r--r--src/resources/style-hc.css16
-rw-r--r--src/resources/style.css301
-rw-r--r--src/resources/text-x-preview.pngbin0 -> 923 bytes
-rw-r--r--src/resources/ui/nautilus-app-chooser.ui120
-rw-r--r--src/resources/ui/nautilus-batch-rename-dialog.ui395
-rw-r--r--src/resources/ui/nautilus-column-chooser.ui105
-rw-r--r--src/resources/ui/nautilus-compress-dialog.ui98
-rw-r--r--src/resources/ui/nautilus-create-folder-dialog.ui58
-rw-r--r--src/resources/ui/nautilus-file-conflict-dialog.ui147
-rw-r--r--src/resources/ui/nautilus-file-properties-change-permissions.ui157
-rw-r--r--src/resources/ui/nautilus-files-view-context-menus.ui260
-rw-r--r--src/resources/ui/nautilus-files-view-select-items.ui54
-rw-r--r--src/resources/ui/nautilus-files-view.ui42
-rw-r--r--src/resources/ui/nautilus-grid-cell.ui109
-rw-r--r--src/resources/ui/nautilus-history-controls.ui28
-rw-r--r--src/resources/ui/nautilus-list-view-column-editor.ui41
-rw-r--r--src/resources/ui/nautilus-name-cell.ui112
-rw-r--r--src/resources/ui/nautilus-operations-ui-manager-request-passphrase.ui49
-rw-r--r--src/resources/ui/nautilus-pathbar-context-menu.ui73
-rw-r--r--src/resources/ui/nautilus-preferences-window.ui164
-rw-r--r--src/resources/ui/nautilus-progress-indicator.ui70
-rw-r--r--src/resources/ui/nautilus-progress-info-widget.ui75
-rw-r--r--src/resources/ui/nautilus-properties-window.ui913
-rw-r--r--src/resources/ui/nautilus-rename-file-popover.ui54
-rw-r--r--src/resources/ui/nautilus-search-popover.ui336
-rw-r--r--src/resources/ui/nautilus-toolbar-view-menu.ui63
-rw-r--r--src/resources/ui/nautilus-toolbar.ui227
-rw-r--r--src/resources/ui/nautilus-view-controls.ui42
-rw-r--r--src/resources/ui/nautilus-window.ui107
259 files changed, 117448 insertions, 0 deletions
diff --git a/src/check-nautilus b/src/check-nautilus
new file mode 100755
index 0000000..96e4d56
--- /dev/null
+++ b/src/check-nautilus
@@ -0,0 +1,2 @@
+#!/bin/sh
+G_DEBUG=fatal-warnings ./nautilus --check
diff --git a/src/gtk/.editorconfig b/src/gtk/.editorconfig
new file mode 100644
index 0000000..e1ee505
--- /dev/null
+++ b/src/gtk/.editorconfig
@@ -0,0 +1,30 @@
+# SPDX-FileCopyrightText: 2021 The GTK Authors
+# SPDX-License-Identifier: CC0-1.0
+
+[*]
+charset = utf-8
+end_of_line = lf
+trim_trailing_whitespace = true
+
+[*.[ch]]
+indent_size = 2
+indent_style = space
+insert_final_newline = true
+# For the legacy tabs which still exist in the code:
+tab_width = 8
+
+[*.ui]
+indent_size = 2
+indent_style = space
+insert_final_newline = true
+
+[*.xml]
+indent_size = 2
+indent_style = space
+
+[meson.build]
+indent_size = 2
+indent_style = space
+
+[*.md]
+max_line_length = 80
diff --git a/src/gtk/nautilusgtkbookmarksmanager.c b/src/gtk/nautilusgtkbookmarksmanager.c
new file mode 100644
index 0000000..86f3bb4
--- /dev/null
+++ b/src/gtk/nautilusgtkbookmarksmanager.c
@@ -0,0 +1,643 @@
+/* -*- Mode: C; c-file-style: "gnu"; tab-width: 8 -*- */
+/* GTK - The GIMP Toolkit
+ * nautilusgtkbookmarksmanager.c: Utilities to manage and monitor ~/.gtk-bookmarks
+ * Copyright (C) 2003, Red Hat, Inc.
+ * Copyright (C) 2007-2008 Carlos Garnacho
+ * Copyright (C) 2011 Suse
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Federico Mena Quintero <federico@gnome.org>
+ */
+
+#include "config.h"
+#include <glib/gi18n.h>
+#include <gtk/gtk.h>
+#include "nautilus-enum-types.h"
+
+#include <string.h>
+
+#include "nautilusgtkbookmarksmanagerprivate.h"
+
+static void
+_gtk_bookmark_free (gpointer data)
+{
+ GtkBookmark *bookmark = data;
+
+ g_object_unref (bookmark->file);
+ g_free (bookmark->label);
+ g_slice_free (GtkBookmark, bookmark);
+}
+
+static void
+set_error_bookmark_doesnt_exist (GFile *file, GError **error)
+{
+ char *uri = g_file_get_uri (file);
+
+ g_set_error (error,
+ GTK_FILE_CHOOSER_ERROR,
+ GTK_FILE_CHOOSER_ERROR_NONEXISTENT,
+ _("%s does not exist in the bookmarks list"),
+ uri);
+
+ g_free (uri);
+}
+
+static GFile *
+get_legacy_bookmarks_file (void)
+{
+ GFile *file;
+ char *filename;
+
+ filename = g_build_filename (g_get_home_dir (), ".gtk-bookmarks", NULL);
+ file = g_file_new_for_path (filename);
+ g_free (filename);
+
+ return file;
+}
+
+static GFile *
+get_bookmarks_file (void)
+{
+ GFile *file;
+ char *filename;
+
+ /* Use gtk-3.0's bookmarks file as the format didn't change.
+ * Add the 3.0 file format to get_legacy_bookmarks_file() when
+ * the format does change.
+ */
+ filename = g_build_filename (g_get_user_config_dir (), "gtk-3.0", "bookmarks", NULL);
+ file = g_file_new_for_path (filename);
+ g_free (filename);
+
+ return file;
+}
+
+static GSList *
+parse_bookmarks (const char *contents)
+{
+ char **lines, *space;
+ GSList *bookmarks = NULL;
+ int i;
+
+ lines = g_strsplit (contents, "\n", -1);
+
+ for (i = 0; lines[i]; i++)
+ {
+ GtkBookmark *bookmark;
+
+ if (!*lines[i])
+ continue;
+
+ if (!g_utf8_validate (lines[i], -1, NULL))
+ continue;
+
+ bookmark = g_slice_new0 (GtkBookmark);
+
+ if ((space = strchr (lines[i], ' ')) != NULL)
+ {
+ space[0] = '\0';
+ bookmark->label = g_strdup (space + 1);
+ }
+
+ bookmark->file = g_file_new_for_uri (lines[i]);
+ bookmarks = g_slist_prepend (bookmarks, bookmark);
+ }
+
+ bookmarks = g_slist_reverse (bookmarks);
+ g_strfreev (lines);
+
+ return bookmarks;
+}
+
+static GSList *
+read_bookmarks (GFile *file)
+{
+ char *contents;
+ GSList *bookmarks = NULL;
+
+ if (!g_file_load_contents (file, NULL, &contents,
+ NULL, NULL, NULL))
+ return NULL;
+
+ bookmarks = parse_bookmarks (contents);
+
+ g_free (contents);
+
+ return bookmarks;
+}
+
+static void
+notify_changed (NautilusGtkBookmarksManager *manager)
+{
+ if (manager->changed_func)
+ manager->changed_func (manager->changed_func_data);
+}
+
+static void
+read_bookmarks_finish (GObject *source,
+ GAsyncResult *result,
+ gpointer data)
+{
+ GFile *file = G_FILE (source);
+ NautilusGtkBookmarksManager *manager = data;
+ char *contents = NULL;
+ GError *error = NULL;
+
+ if (!g_file_load_contents_finish (file, result, &contents, NULL, NULL, &error))
+ {
+ if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+ g_warning ("Failed to load '%s': %s", g_file_peek_path (file), error->message);
+ g_error_free (error);
+ return;
+ }
+
+ g_slist_free_full (manager->bookmarks, _gtk_bookmark_free);
+ manager->bookmarks = parse_bookmarks (contents);
+
+ g_free (contents);
+
+ notify_changed (manager);
+}
+
+static void
+save_bookmarks (GFile *bookmarks_file,
+ GSList *bookmarks)
+{
+ GError *error = NULL;
+ GString *contents;
+ GSList *l;
+ GFile *parent = NULL;
+
+ contents = g_string_new ("");
+
+ for (l = bookmarks; l; l = l->next)
+ {
+ GtkBookmark *bookmark = l->data;
+ char *uri;
+
+ uri = g_file_get_uri (bookmark->file);
+ if (!uri)
+ continue;
+
+ g_string_append (contents, uri);
+
+ if (bookmark->label && g_utf8_validate (bookmark->label, -1, NULL))
+ g_string_append_printf (contents, " %s", bookmark->label);
+
+ g_string_append_c (contents, '\n');
+ g_free (uri);
+ }
+
+ parent = g_file_get_parent (bookmarks_file);
+ if (!g_file_make_directory_with_parents (parent, NULL, &error))
+ {
+ if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_EXISTS))
+ g_clear_error (&error);
+ else
+ goto out;
+ }
+ if (!g_file_replace_contents (bookmarks_file,
+ contents->str,
+ contents->len,
+ NULL, FALSE, 0, NULL,
+ NULL, &error))
+ goto out;
+
+ out:
+ if (error)
+ {
+ g_critical ("%s", error->message);
+ g_error_free (error);
+ }
+ g_clear_object (&parent);
+ g_string_free (contents, TRUE);
+}
+
+static void
+bookmarks_file_changed (GFileMonitor *monitor,
+ GFile *file,
+ GFile *other_file,
+ GFileMonitorEvent event,
+ gpointer data)
+{
+ NautilusGtkBookmarksManager *manager = data;
+
+ switch (event)
+ {
+ case G_FILE_MONITOR_EVENT_CHANGED:
+ case G_FILE_MONITOR_EVENT_CHANGES_DONE_HINT:
+ case G_FILE_MONITOR_EVENT_CREATED:
+ g_file_load_contents_async (file, manager->cancellable, read_bookmarks_finish, manager);
+ break;
+
+ case G_FILE_MONITOR_EVENT_DELETED:
+ case G_FILE_MONITOR_EVENT_ATTRIBUTE_CHANGED:
+ case G_FILE_MONITOR_EVENT_PRE_UNMOUNT:
+ case G_FILE_MONITOR_EVENT_UNMOUNTED:
+ case G_FILE_MONITOR_EVENT_MOVED:
+ case G_FILE_MONITOR_EVENT_RENAMED:
+ case G_FILE_MONITOR_EVENT_MOVED_IN:
+ case G_FILE_MONITOR_EVENT_MOVED_OUT:
+ default:
+ /* ignore at the moment */
+ break;
+ }
+}
+
+NautilusGtkBookmarksManager *
+_nautilus_gtk_bookmarks_manager_new (GtkBookmarksChangedFunc changed_func, gpointer changed_func_data)
+{
+ NautilusGtkBookmarksManager *manager;
+ GFile *bookmarks_file;
+ GError *error;
+
+ manager = g_new0 (NautilusGtkBookmarksManager, 1);
+
+ manager->changed_func = changed_func;
+ manager->changed_func_data = changed_func_data;
+
+ manager->cancellable = g_cancellable_new ();
+
+ bookmarks_file = get_bookmarks_file ();
+ if (!g_file_query_exists (bookmarks_file, NULL))
+ {
+ GFile *legacy_bookmarks_file;
+
+ /* Read the legacy one and write it to the new one */
+ legacy_bookmarks_file = get_legacy_bookmarks_file ();
+ manager->bookmarks = read_bookmarks (legacy_bookmarks_file);
+ if (manager->bookmarks)
+ save_bookmarks (bookmarks_file, manager->bookmarks);
+
+ g_object_unref (legacy_bookmarks_file);
+ }
+ else
+ g_file_load_contents_async (bookmarks_file, manager->cancellable, read_bookmarks_finish, manager);
+
+ error = NULL;
+ manager->bookmarks_monitor = g_file_monitor_file (bookmarks_file,
+ G_FILE_MONITOR_NONE,
+ NULL, &error);
+ if (error)
+ {
+ g_warning ("%s", error->message);
+ g_error_free (error);
+ }
+ else
+ manager->bookmarks_monitor_changed_id = g_signal_connect (manager->bookmarks_monitor, "changed",
+ G_CALLBACK (bookmarks_file_changed), manager);
+
+
+ g_object_unref (bookmarks_file);
+
+ return manager;
+}
+
+void
+_nautilus_gtk_bookmarks_manager_free (NautilusGtkBookmarksManager *manager)
+{
+ g_return_if_fail (manager != NULL);
+
+ g_cancellable_cancel (manager->cancellable);
+ g_object_unref (manager->cancellable);
+
+ if (manager->bookmarks_monitor)
+ {
+ g_file_monitor_cancel (manager->bookmarks_monitor);
+ g_signal_handler_disconnect (manager->bookmarks_monitor, manager->bookmarks_monitor_changed_id);
+ manager->bookmarks_monitor_changed_id = 0;
+ g_object_unref (manager->bookmarks_monitor);
+ }
+
+ g_slist_free_full (manager->bookmarks, _gtk_bookmark_free);
+
+ g_free (manager);
+}
+
+GSList *
+_nautilus_gtk_bookmarks_manager_list_bookmarks (NautilusGtkBookmarksManager *manager)
+{
+ GSList *bookmarks, *files = NULL;
+
+ g_return_val_if_fail (manager != NULL, NULL);
+
+ bookmarks = manager->bookmarks;
+
+ while (bookmarks)
+ {
+ GtkBookmark *bookmark;
+
+ bookmark = bookmarks->data;
+ bookmarks = bookmarks->next;
+
+ files = g_slist_prepend (files, g_object_ref (bookmark->file));
+ }
+
+ return g_slist_reverse (files);
+}
+
+static GSList *
+find_bookmark_link_for_file (GSList *bookmarks, GFile *file, int *position_ret)
+{
+ int pos;
+
+ pos = 0;
+ for (; bookmarks; bookmarks = bookmarks->next)
+ {
+ GtkBookmark *bookmark = bookmarks->data;
+
+ if (g_file_equal (file, bookmark->file))
+ {
+ if (position_ret)
+ *position_ret = pos;
+
+ return bookmarks;
+ }
+
+ pos++;
+ }
+
+ if (position_ret)
+ *position_ret = -1;
+
+ return NULL;
+}
+
+gboolean
+_nautilus_gtk_bookmarks_manager_has_bookmark (NautilusGtkBookmarksManager *manager,
+ GFile *file)
+{
+ GSList *link;
+
+ link = find_bookmark_link_for_file (manager->bookmarks, file, NULL);
+ return (link != NULL);
+}
+
+gboolean
+_nautilus_gtk_bookmarks_manager_insert_bookmark (NautilusGtkBookmarksManager *manager,
+ GFile *file,
+ int position,
+ GError **error)
+{
+ GSList *link;
+ GtkBookmark *bookmark;
+ GFile *bookmarks_file;
+
+ g_return_val_if_fail (manager != NULL, FALSE);
+ g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
+
+ link = find_bookmark_link_for_file (manager->bookmarks, file, NULL);
+
+ if (link)
+ {
+ char *uri;
+ bookmark = link->data;
+ uri = g_file_get_uri (bookmark->file);
+
+ g_set_error (error,
+ GTK_FILE_CHOOSER_ERROR,
+ GTK_FILE_CHOOSER_ERROR_ALREADY_EXISTS,
+ _("%s already exists in the bookmarks list"),
+ uri);
+
+ g_free (uri);
+
+ return FALSE;
+ }
+
+ bookmark = g_slice_new0 (GtkBookmark);
+ bookmark->file = g_object_ref (file);
+
+ manager->bookmarks = g_slist_insert (manager->bookmarks, bookmark, position);
+
+ bookmarks_file = get_bookmarks_file ();
+ save_bookmarks (bookmarks_file, manager->bookmarks);
+ g_object_unref (bookmarks_file);
+
+ notify_changed (manager);
+
+ return TRUE;
+}
+
+gboolean
+_nautilus_gtk_bookmarks_manager_remove_bookmark (NautilusGtkBookmarksManager *manager,
+ GFile *file,
+ GError **error)
+{
+ GSList *link;
+ GFile *bookmarks_file;
+
+ g_return_val_if_fail (manager != NULL, FALSE);
+ g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
+
+ if (!manager->bookmarks)
+ return FALSE;
+
+ link = find_bookmark_link_for_file (manager->bookmarks, file, NULL);
+ if (link)
+ {
+ GtkBookmark *bookmark = link->data;
+
+ manager->bookmarks = g_slist_remove_link (manager->bookmarks, link);
+ _gtk_bookmark_free (bookmark);
+ g_slist_free_1 (link);
+ }
+ else
+ {
+ set_error_bookmark_doesnt_exist (file, error);
+ return FALSE;
+ }
+
+ bookmarks_file = get_bookmarks_file ();
+ save_bookmarks (bookmarks_file, manager->bookmarks);
+ g_object_unref (bookmarks_file);
+
+ notify_changed (manager);
+
+ return TRUE;
+}
+
+gboolean
+_nautilus_gtk_bookmarks_manager_reorder_bookmark (NautilusGtkBookmarksManager *manager,
+ GFile *file,
+ int new_position,
+ GError **error)
+{
+ GSList *link;
+ GFile *bookmarks_file;
+ int old_position;
+
+ g_return_val_if_fail (manager != NULL, FALSE);
+ g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
+ g_return_val_if_fail (new_position >= 0, FALSE);
+
+ if (!manager->bookmarks)
+ return FALSE;
+
+ link = find_bookmark_link_for_file (manager->bookmarks, file, &old_position);
+ if (new_position == old_position)
+ return TRUE;
+
+ if (link)
+ {
+ GtkBookmark *bookmark = link->data;
+
+ manager->bookmarks = g_slist_remove_link (manager->bookmarks, link);
+ g_slist_free_1 (link);
+
+ if (new_position > old_position)
+ new_position--;
+
+ manager->bookmarks = g_slist_insert (manager->bookmarks, bookmark, new_position);
+ }
+ else
+ {
+ set_error_bookmark_doesnt_exist (file, error);
+ return FALSE;
+ }
+
+ bookmarks_file = get_bookmarks_file ();
+ save_bookmarks (bookmarks_file, manager->bookmarks);
+ g_object_unref (bookmarks_file);
+
+ notify_changed (manager);
+
+ return TRUE;
+}
+
+char *
+_nautilus_gtk_bookmarks_manager_get_bookmark_label (NautilusGtkBookmarksManager *manager,
+ GFile *file)
+{
+ GSList *bookmarks;
+ char *label = NULL;
+
+ g_return_val_if_fail (manager != NULL, NULL);
+ g_return_val_if_fail (file != NULL, NULL);
+
+ bookmarks = manager->bookmarks;
+
+ while (bookmarks)
+ {
+ GtkBookmark *bookmark;
+
+ bookmark = bookmarks->data;
+ bookmarks = bookmarks->next;
+
+ if (g_file_equal (file, bookmark->file))
+ {
+ label = g_strdup (bookmark->label);
+ break;
+ }
+ }
+
+ return label;
+}
+
+gboolean
+_nautilus_gtk_bookmarks_manager_set_bookmark_label (NautilusGtkBookmarksManager *manager,
+ GFile *file,
+ const char *label,
+ GError **error)
+{
+ GFile *bookmarks_file;
+ GSList *link;
+
+ g_return_val_if_fail (manager != NULL, FALSE);
+ g_return_val_if_fail (file != NULL, FALSE);
+
+ link = find_bookmark_link_for_file (manager->bookmarks, file, NULL);
+ if (link)
+ {
+ GtkBookmark *bookmark = link->data;
+
+ g_free (bookmark->label);
+ bookmark->label = g_strdup (label);
+ }
+ else
+ {
+ set_error_bookmark_doesnt_exist (file, error);
+ return FALSE;
+ }
+
+ bookmarks_file = get_bookmarks_file ();
+ save_bookmarks (bookmarks_file, manager->bookmarks);
+ g_object_unref (bookmarks_file);
+
+ notify_changed (manager);
+
+ return TRUE;
+}
+
+static gboolean
+_nautilus_gtk_bookmarks_manager_get_xdg_type (NautilusGtkBookmarksManager *manager,
+ GFile *file,
+ GUserDirectory *directory)
+{
+ GSList *link;
+ gboolean match;
+ GFile *location;
+ const char *path;
+ GUserDirectory dir;
+ GtkBookmark *bookmark;
+
+ link = find_bookmark_link_for_file (manager->bookmarks, file, NULL);
+ if (!link)
+ return FALSE;
+
+ match = FALSE;
+ bookmark = link->data;
+
+ for (dir = 0; dir < G_USER_N_DIRECTORIES; dir++)
+ {
+ path = g_get_user_special_dir (dir);
+ if (!path)
+ continue;
+
+ location = g_file_new_for_path (path);
+ match = g_file_equal (location, bookmark->file);
+ g_object_unref (location);
+
+ if (match)
+ break;
+ }
+
+ if (match && directory != NULL)
+ *directory = dir;
+
+ return match;
+}
+
+gboolean
+_nautilus_gtk_bookmarks_manager_get_is_builtin (NautilusGtkBookmarksManager *manager,
+ GFile *file)
+{
+ GUserDirectory xdg_type;
+
+ /* if this is not an XDG dir, it's never builtin */
+ if (!_nautilus_gtk_bookmarks_manager_get_xdg_type (manager, file, &xdg_type))
+ return FALSE;
+
+ /* exclude XDG locations we don't display by default */
+ return _nautilus_gtk_bookmarks_manager_get_is_xdg_dir_builtin (xdg_type);
+}
+
+gboolean
+_nautilus_gtk_bookmarks_manager_get_is_xdg_dir_builtin (GUserDirectory xdg_type)
+{
+ return (xdg_type != G_USER_DIRECTORY_DESKTOP) &&
+ (xdg_type != G_USER_DIRECTORY_TEMPLATES) &&
+ (xdg_type != G_USER_DIRECTORY_PUBLIC_SHARE);
+}
diff --git a/src/gtk/nautilusgtkbookmarksmanagerprivate.h b/src/gtk/nautilusgtkbookmarksmanagerprivate.h
new file mode 100644
index 0000000..99890a3
--- /dev/null
+++ b/src/gtk/nautilusgtkbookmarksmanagerprivate.h
@@ -0,0 +1,89 @@
+/* -*- Mode: C; c-file-style: "gnu"; tab-width: 8 -*- */
+/* GTK - The GIMP Toolkit
+ * nautilusgtkbookmarksmanager.h: Utilities to manage and monitor ~/.gtk-bookmarks
+ * Copyright (C) 2003, Red Hat, Inc.
+ * Copyright (C) 2007-2008 Carlos Garnacho
+ * Copyright (C) 2011 Suse
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Federico Mena Quintero <federico@gnome.org>
+ */
+
+#ifndef __NAUTILUS_GTK_BOOKMARKS_MANAGER_H__
+#define __NAUTILUS_GTK_BOOKMARKS_MANAGER_H__
+
+#include <gio/gio.h>
+
+typedef void (* GtkBookmarksChangedFunc) (gpointer data);
+
+typedef struct
+{
+ /* This list contains GtkBookmark structs */
+ GSList *bookmarks;
+
+ GFileMonitor *bookmarks_monitor;
+ gulong bookmarks_monitor_changed_id;
+
+ gpointer changed_func_data;
+ GtkBookmarksChangedFunc changed_func;
+
+ GCancellable *cancellable;
+} NautilusGtkBookmarksManager;
+
+typedef struct
+{
+ GFile *file;
+ char *label;
+} GtkBookmark;
+
+NautilusGtkBookmarksManager *_nautilus_gtk_bookmarks_manager_new (GtkBookmarksChangedFunc changed_func,
+ gpointer changed_func_data);
+
+
+void _nautilus_gtk_bookmarks_manager_free (NautilusGtkBookmarksManager *manager);
+
+GSList *_nautilus_gtk_bookmarks_manager_list_bookmarks (NautilusGtkBookmarksManager *manager);
+
+gboolean _nautilus_gtk_bookmarks_manager_insert_bookmark (NautilusGtkBookmarksManager *manager,
+ GFile *file,
+ int position,
+ GError **error);
+
+gboolean _nautilus_gtk_bookmarks_manager_remove_bookmark (NautilusGtkBookmarksManager *manager,
+ GFile *file,
+ GError **error);
+
+gboolean _nautilus_gtk_bookmarks_manager_reorder_bookmark (NautilusGtkBookmarksManager *manager,
+ GFile *file,
+ int new_position,
+ GError **error);
+
+gboolean _nautilus_gtk_bookmarks_manager_has_bookmark (NautilusGtkBookmarksManager *manager,
+ GFile *file);
+
+char * _nautilus_gtk_bookmarks_manager_get_bookmark_label (NautilusGtkBookmarksManager *manager,
+ GFile *file);
+
+gboolean _nautilus_gtk_bookmarks_manager_set_bookmark_label (NautilusGtkBookmarksManager *manager,
+ GFile *file,
+ const char *label,
+ GError **error);
+
+gboolean _nautilus_gtk_bookmarks_manager_get_is_builtin (NautilusGtkBookmarksManager *manager,
+ GFile *file);
+
+gboolean _nautilus_gtk_bookmarks_manager_get_is_xdg_dir_builtin (GUserDirectory xdg_type);
+
+#endif /* __NAUTILUS_GTK_BOOKMARKS_MANAGER_H__ */
diff --git a/src/gtk/nautilusgtkplacessidebar.c b/src/gtk/nautilusgtkplacessidebar.c
new file mode 100644
index 0000000..c536e89
--- /dev/null
+++ b/src/gtk/nautilusgtkplacessidebar.c
@@ -0,0 +1,5142 @@
+/* NautilusGtkPlacesSidebar - sidebar widget for places in the filesystem
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * This code is originally from Nautilus.
+ *
+ * Authors : Mr Jamie McCracken (jamiemcc at blueyonder dot co dot uk)
+ * Cosimo Cecchi <cosimoc@gnome.org>
+ * Federico Mena Quintero <federico@gnome.org>
+ * Carlos Soriano <csoriano@gnome.org>
+ */
+
+#include "config.h"
+#include <glib/gi18n.h>
+#include <gtk/gtk.h>
+#include "nautilus-enum-types.h"
+
+#include <gio/gio.h>
+#include <cloudproviders.h>
+
+#include "nautilusgtkplacessidebarprivate.h"
+#include "nautilusgtksidebarrowprivate.h"
+#include "gdk/gdkkeysyms.h"
+#include "nautilusgtkbookmarksmanagerprivate.h"
+#include "nautilus-dnd.h"
+#include "nautilus-dbus-launcher.h"
+#include "nautilus-file.h"
+#include "nautilus-file-operations.h"
+#include "nautilus-global-preferences.h"
+#include "nautilus-properties-window.h"
+#include "nautilus-trash-monitor.h"
+
+#ifdef GDK_WINDOWING_X11
+#include <gdk/x11/gdkx.h>
+#endif
+
+#pragma GCC diagnostic ignored "-Wshadow"
+
+/*< private >
+ * NautilusGtkPlacesSidebar:
+ *
+ * NautilusGtkPlacesSidebar is a widget that displays a list of frequently-used places in the
+ * file system: the user’s home directory, the user’s bookmarks, and volumes and drives.
+ * This widget is used as a sidebar in GtkFileChooser and may be used by file managers
+ * and similar programs.
+ *
+ * The places sidebar displays drives and volumes, and will automatically mount
+ * or unmount them when the user selects them.
+ *
+ * Applications can hook to various signals in the places sidebar to customize
+ * its behavior. For example, they can add extra commands to the context menu
+ * of the sidebar.
+ *
+ * While bookmarks are completely in control of the user, the places sidebar also
+ * allows individual applications to provide extra shortcut folders that are unique
+ * to each application. For example, a Paint program may want to add a shortcut
+ * for a Clipart folder. You can do this with nautilus_gtk_places_sidebar_add_shortcut().
+ *
+ * To make use of the places sidebar, an application at least needs to connect
+ * to the NautilusGtkPlacesSidebar::open-location signal. This is emitted when the
+ * user selects in the sidebar a location to open. The application should also
+ * call nautilus_gtk_places_sidebar_set_location() when it changes the currently-viewed
+ * location.
+ *
+ * # CSS nodes
+ *
+ * NautilusGtkPlacesSidebar uses a single CSS node with name placessidebar and style
+ * class .sidebar.
+ *
+ * Among the children of the places sidebar, the following style classes can
+ * be used:
+ * - .sidebar-new-bookmark-row for the 'Add new bookmark' row
+ * - .sidebar-placeholder-row for a row that is a placeholder
+ * - .has-open-popup when a popup is open for a row
+ */
+
+/* These are used when a destination-side DND operation is taking place.
+ * Normally, when a common drag action is taking place, the state will be
+ * DROP_STATE_NEW_BOOKMARK_ARMED, however, if the client of NautilusGtkPlacesSidebar
+ * wants to show hints about the valid targets, we sill set it as
+ * DROP_STATE_NEW_BOOKMARK_ARMED_PERMANENT, so the sidebar will show drop hints
+ * until the client says otherwise
+ */
+typedef enum {
+ DROP_STATE_NORMAL,
+ DROP_STATE_NEW_BOOKMARK_ARMED,
+ DROP_STATE_NEW_BOOKMARK_ARMED_PERMANENT,
+} DropState;
+
+struct _NautilusGtkPlacesSidebar {
+ GtkWidget parent;
+
+ GtkWidget *swin;
+ GtkWidget *list_box;
+ GtkWidget *new_bookmark_row;
+
+ NautilusGtkBookmarksManager *bookmarks_manager;
+
+ GActionGroup *row_actions;
+
+ CloudProvidersCollector *cloud_manager;
+ GList *unready_accounts;
+
+ GVolumeMonitor *volume_monitor;
+ GtkSettings *gtk_settings;
+ GFile *current_location;
+
+ GtkWidget *rename_popover;
+ GtkWidget *rename_entry;
+ GtkWidget *rename_button;
+ GtkWidget *rename_error;
+ char *rename_uri;
+
+ GtkWidget *trash_row;
+
+ /* DND */
+ gboolean dragging_over;
+ GtkWidget *drag_row;
+ int drag_row_height;
+ int drag_row_x;
+ int drag_row_y;
+ GtkWidget *row_placeholder;
+ DropState drop_state;
+ guint hover_timer_id;
+ graphene_point_t hover_start_point;
+ GtkListBoxRow *hover_row;
+
+ /* volume mounting - delayed open process */
+ NautilusGtkPlacesOpenFlags go_to_after_mount_open_flags;
+ GCancellable *cancellable;
+
+ GtkWidget *popover;
+ NautilusGtkSidebarRow *context_row;
+ GListStore *shortcuts;
+
+ GDBusProxy *hostnamed_proxy;
+ GCancellable *hostnamed_cancellable;
+ char *hostname;
+
+ NautilusGtkPlacesOpenFlags open_flags;
+
+ guint mounting : 1;
+ guint show_recent_set : 1;
+ guint show_recent : 1;
+ guint show_desktop_set : 1;
+ guint show_desktop : 1;
+ guint show_enter_location : 1;
+ guint show_other_locations : 1;
+ guint show_trash : 1;
+ guint show_starred_location : 1;
+};
+
+struct _NautilusGtkPlacesSidebarClass {
+ GtkWidgetClass parent_class;
+
+ void (* open_location) (NautilusGtkPlacesSidebar *sidebar,
+ GFile *location,
+ NautilusGtkPlacesOpenFlags open_flags);
+ void (* show_error_message) (NautilusGtkPlacesSidebar *sidebar,
+ const char *primary,
+ const char *secondary);
+ GdkDragAction (* drag_action_requested) (NautilusGtkPlacesSidebar *sidebar,
+ GFile *dest_file,
+ GSList *source_file_list);
+ GdkDragAction (* drag_action_ask) (NautilusGtkPlacesSidebar *sidebar,
+ GdkDragAction actions);
+ void (* drag_perform_drop) (NautilusGtkPlacesSidebar *sidebar,
+ GFile *dest_file,
+ GList *source_file_list,
+ GdkDragAction action);
+ void (* show_enter_location) (NautilusGtkPlacesSidebar *sidebar);
+
+ void (* show_other_locations_with_flags) (NautilusGtkPlacesSidebar *sidebar,
+ NautilusGtkPlacesOpenFlags open_flags);
+
+ void (* show_starred_location) (NautilusGtkPlacesSidebar *sidebar);
+
+ void (* mount) (NautilusGtkPlacesSidebar *sidebar,
+ GMountOperation *mount_operation);
+ void (* unmount) (NautilusGtkPlacesSidebar *sidebar,
+ GMountOperation *unmount_operation);
+};
+
+enum {
+ OPEN_LOCATION,
+ SHOW_ERROR_MESSAGE,
+ SHOW_ENTER_LOCATION,
+ DRAG_ACTION_REQUESTED,
+ DRAG_ACTION_ASK,
+ DRAG_PERFORM_DROP,
+ SHOW_OTHER_LOCATIONS_WITH_FLAGS,
+ SHOW_STARRED_LOCATION,
+ MOUNT,
+ UNMOUNT,
+ LAST_SIGNAL
+};
+
+enum {
+ PROP_LOCATION = 1,
+ PROP_OPEN_FLAGS,
+ PROP_SHOW_RECENT,
+ PROP_SHOW_DESKTOP,
+ PROP_SHOW_ENTER_LOCATION,
+ PROP_SHOW_TRASH,
+ PROP_SHOW_STARRED_LOCATION,
+ PROP_SHOW_OTHER_LOCATIONS,
+ NUM_PROPERTIES
+};
+
+/* Names for themed icons */
+#define ICON_NAME_HOME "user-home-symbolic"
+#define ICON_NAME_DESKTOP "user-desktop-symbolic"
+#define ICON_NAME_FILESYSTEM "drive-harddisk-symbolic"
+#define ICON_NAME_EJECT "media-eject-symbolic"
+#define ICON_NAME_NETWORK "network-workgroup-symbolic"
+#define ICON_NAME_NETWORK_SERVER "network-server-symbolic"
+#define ICON_NAME_FOLDER_NETWORK "folder-remote-symbolic"
+#define ICON_NAME_OTHER_LOCATIONS "list-add-symbolic"
+
+#define ICON_NAME_FOLDER "folder-symbolic"
+#define ICON_NAME_FOLDER_DESKTOP "user-desktop-symbolic"
+#define ICON_NAME_FOLDER_DOCUMENTS "folder-documents-symbolic"
+#define ICON_NAME_FOLDER_DOWNLOAD "folder-download-symbolic"
+#define ICON_NAME_FOLDER_MUSIC "folder-music-symbolic"
+#define ICON_NAME_FOLDER_PICTURES "folder-pictures-symbolic"
+#define ICON_NAME_FOLDER_PUBLIC_SHARE "folder-publicshare-symbolic"
+#define ICON_NAME_FOLDER_TEMPLATES "folder-templates-symbolic"
+#define ICON_NAME_FOLDER_VIDEOS "folder-videos-symbolic"
+#define ICON_NAME_FOLDER_SAVED_SEARCH "folder-saved-search-symbolic"
+
+static guint places_sidebar_signals [LAST_SIGNAL] = { 0 };
+static GParamSpec *properties[NUM_PROPERTIES] = { NULL, };
+
+static gboolean eject_or_unmount_bookmark (NautilusGtkSidebarRow *row);
+static gboolean eject_or_unmount_selection (NautilusGtkPlacesSidebar *sidebar);
+static void check_unmount_and_eject (GMount *mount,
+ GVolume *volume,
+ GDrive *drive,
+ gboolean *show_unmount,
+ gboolean *show_eject);
+static void on_row_pressed (GtkGestureClick *gesture,
+ int n_press,
+ double x,
+ double y,
+ NautilusGtkSidebarRow *row);
+static void on_row_released (GtkGestureClick *gesture,
+ int n_press,
+ double x,
+ double y,
+ NautilusGtkSidebarRow *row);
+static void on_row_dragged (GtkGestureDrag *gesture,
+ double x,
+ double y,
+ NautilusGtkSidebarRow *row);
+
+static void popup_menu_cb (NautilusGtkSidebarRow *row);
+static void long_press_cb (GtkGesture *gesture,
+ double x,
+ double y,
+ NautilusGtkPlacesSidebar *sidebar);
+static void stop_drop_feedback (NautilusGtkPlacesSidebar *sidebar);
+static GMountOperation * get_mount_operation (NautilusGtkPlacesSidebar *sidebar);
+static GMountOperation * get_unmount_operation (NautilusGtkPlacesSidebar *sidebar);
+
+
+G_DEFINE_TYPE (NautilusGtkPlacesSidebar, nautilus_gtk_places_sidebar, GTK_TYPE_WIDGET);
+
+static void
+emit_open_location (NautilusGtkPlacesSidebar *sidebar,
+ GFile *location,
+ NautilusGtkPlacesOpenFlags open_flags)
+{
+ if ((open_flags & sidebar->open_flags) == 0)
+ open_flags = NAUTILUS_GTK_PLACES_OPEN_NORMAL;
+
+ g_signal_emit (sidebar, places_sidebar_signals[OPEN_LOCATION], 0,
+ location, open_flags);
+}
+
+static void
+emit_show_error_message (NautilusGtkPlacesSidebar *sidebar,
+ const char *primary,
+ const char *secondary)
+{
+ g_signal_emit (sidebar, places_sidebar_signals[SHOW_ERROR_MESSAGE], 0,
+ primary, secondary);
+}
+
+static void
+emit_show_enter_location (NautilusGtkPlacesSidebar *sidebar)
+{
+ g_signal_emit (sidebar, places_sidebar_signals[SHOW_ENTER_LOCATION], 0);
+}
+
+static void
+emit_show_other_locations_with_flags (NautilusGtkPlacesSidebar *sidebar,
+ NautilusGtkPlacesOpenFlags open_flags)
+{
+ g_signal_emit (sidebar, places_sidebar_signals[SHOW_OTHER_LOCATIONS_WITH_FLAGS],
+ 0, open_flags);
+}
+
+static void
+emit_show_starred_location (NautilusGtkPlacesSidebar *sidebar,
+ NautilusGtkPlacesOpenFlags open_flags)
+{
+ g_signal_emit (sidebar, places_sidebar_signals[SHOW_STARRED_LOCATION], 0,
+ open_flags);
+}
+
+
+static void
+emit_mount_operation (NautilusGtkPlacesSidebar *sidebar,
+ GMountOperation *mount_op)
+{
+ g_signal_emit (sidebar, places_sidebar_signals[MOUNT], 0, mount_op);
+}
+
+static void
+emit_unmount_operation (NautilusGtkPlacesSidebar *sidebar,
+ GMountOperation *mount_op)
+{
+ g_signal_emit (sidebar, places_sidebar_signals[UNMOUNT], 0, mount_op);
+}
+
+static GdkDragAction
+emit_drag_action_requested (NautilusGtkPlacesSidebar *sidebar,
+ NautilusFile *dest_file,
+ GSList *source_file_list)
+{
+ GdkDragAction ret_action = 0;
+
+ g_signal_emit (sidebar, places_sidebar_signals[DRAG_ACTION_REQUESTED], 0,
+ dest_file, source_file_list, &ret_action);
+
+ return ret_action;
+}
+
+static void
+emit_drag_perform_drop (NautilusGtkPlacesSidebar *sidebar,
+ GFile *dest_file,
+ GSList *source_file_list,
+ GdkDragAction action)
+{
+ g_signal_emit (sidebar, places_sidebar_signals[DRAG_PERFORM_DROP], 0,
+ dest_file, source_file_list, action);
+}
+static void
+list_box_header_func (GtkListBoxRow *row,
+ GtkListBoxRow *before,
+ gpointer user_data)
+{
+ NautilusGtkPlacesSectionType row_section_type;
+ NautilusGtkPlacesSectionType before_section_type;
+ GtkWidget *separator;
+
+ gtk_list_box_row_set_header (row, NULL);
+
+ g_object_get (row, "section-type", &row_section_type, NULL);
+ if (before)
+ {
+ g_object_get (before, "section-type", &before_section_type, NULL);
+ }
+ else
+ {
+ before_section_type = NAUTILUS_GTK_PLACES_SECTION_INVALID;
+ }
+
+ if (before && before_section_type != row_section_type)
+ {
+ separator = gtk_separator_new (GTK_ORIENTATION_HORIZONTAL);
+ gtk_list_box_row_set_header (row, separator);
+ }
+}
+
+static GtkWidget*
+add_place (NautilusGtkPlacesSidebar *sidebar,
+ NautilusGtkPlacesPlaceType place_type,
+ NautilusGtkPlacesSectionType section_type,
+ const char *name,
+ GIcon *start_icon,
+ GIcon *end_icon,
+ const char *uri,
+ GDrive *drive,
+ GVolume *volume,
+ GMount *mount,
+ CloudProvidersAccount *cloud_provider_account,
+ const int index,
+ const char *tooltip)
+{
+ gboolean show_eject, show_unmount;
+ gboolean show_eject_button;
+ GtkWidget *row;
+ GtkWidget *eject_button;
+ GtkGesture *gesture;
+ char *eject_tooltip;
+
+ check_unmount_and_eject (mount, volume, drive,
+ &show_unmount, &show_eject);
+
+ if (show_unmount || show_eject)
+ g_assert (place_type != NAUTILUS_GTK_PLACES_BOOKMARK);
+
+ show_eject_button = (show_unmount || show_eject);
+ if (mount != NULL && volume == NULL && drive == NULL)
+ eject_tooltip = _("Disconnect");
+ else if (show_eject)
+ eject_tooltip = _("Eject");
+ else
+ eject_tooltip = _("Unmount");
+
+ row = g_object_new (NAUTILUS_TYPE_GTK_SIDEBAR_ROW,
+ "sidebar", sidebar,
+ "start-icon", start_icon,
+ "end-icon", end_icon,
+ "label", name,
+ "tooltip", tooltip,
+ "ejectable", show_eject_button,
+ "eject-tooltip", eject_tooltip,
+ "order-index", index,
+ "section-type", section_type,
+ "place-type", place_type,
+ "uri", uri,
+ "drive", drive,
+ "volume", volume,
+ "mount", mount,
+ "cloud-provider-account", cloud_provider_account,
+ NULL);
+
+ eject_button = nautilus_gtk_sidebar_row_get_eject_button (NAUTILUS_GTK_SIDEBAR_ROW (row));
+
+ g_signal_connect_swapped (eject_button, "clicked",
+ G_CALLBACK (eject_or_unmount_bookmark), row);
+
+ gesture = gtk_gesture_click_new ();
+ gtk_gesture_single_set_button (GTK_GESTURE_SINGLE (gesture), 0);
+ g_signal_connect (gesture, "pressed",
+ G_CALLBACK (on_row_pressed), row);
+ g_signal_connect (gesture, "released",
+ G_CALLBACK (on_row_released), row);
+ gtk_widget_add_controller (row, GTK_EVENT_CONTROLLER (gesture));
+
+ gesture = gtk_gesture_drag_new ();
+ g_signal_connect (gesture, "drag-update",
+ G_CALLBACK (on_row_dragged), row);
+ gtk_widget_add_controller (row, GTK_EVENT_CONTROLLER (gesture));
+
+ gtk_list_box_insert (GTK_LIST_BOX (sidebar->list_box), GTK_WIDGET (row), -1);
+
+ return row;
+}
+
+static GIcon *
+special_directory_get_gicon (GUserDirectory directory)
+{
+#define ICON_CASE(x) \
+ case G_USER_DIRECTORY_ ## x: \
+ return g_themed_icon_new_with_default_fallbacks (ICON_NAME_FOLDER_ ## x);
+
+ switch (directory)
+ {
+
+ ICON_CASE (DESKTOP);
+ ICON_CASE (DOCUMENTS);
+ ICON_CASE (DOWNLOAD);
+ ICON_CASE (MUSIC);
+ ICON_CASE (PICTURES);
+ ICON_CASE (PUBLIC_SHARE);
+ ICON_CASE (TEMPLATES);
+ ICON_CASE (VIDEOS);
+
+ case G_USER_N_DIRECTORIES:
+ default:
+ return g_themed_icon_new_with_default_fallbacks (ICON_NAME_FOLDER);
+ }
+
+#undef ICON_CASE
+}
+
+static gboolean
+recent_files_setting_is_enabled (NautilusGtkPlacesSidebar *sidebar)
+{
+ GtkSettings *settings;
+ gboolean enabled;
+
+ settings = gtk_widget_get_settings (GTK_WIDGET (sidebar));
+ g_object_get (settings, "gtk-recent-files-enabled", &enabled, NULL);
+
+ return enabled;
+}
+
+static gboolean
+recent_scheme_is_supported (void)
+{
+ const char * const *supported;
+
+ supported = g_vfs_get_supported_uri_schemes (g_vfs_get_default ());
+ if (supported != NULL)
+ return g_strv_contains (supported, "recent");
+
+ return FALSE;
+}
+
+static gboolean
+should_show_recent (NautilusGtkPlacesSidebar *sidebar)
+{
+ return recent_files_setting_is_enabled (sidebar) &&
+ ((sidebar->show_recent_set && sidebar->show_recent) ||
+ (!sidebar->show_recent_set && recent_scheme_is_supported ()));
+}
+
+static gboolean
+path_is_home_dir (const char *path)
+{
+ GFile *home_dir;
+ GFile *location;
+ const char *home_path;
+ gboolean res;
+
+ home_path = g_get_home_dir ();
+ if (!home_path)
+ return FALSE;
+
+ home_dir = g_file_new_for_path (home_path);
+ location = g_file_new_for_path (path);
+ res = g_file_equal (home_dir, location);
+
+ g_object_unref (home_dir);
+ g_object_unref (location);
+
+ return res;
+}
+
+static void
+open_home (NautilusGtkPlacesSidebar *sidebar)
+{
+ const char *home_path;
+ GFile *home_dir;
+
+ home_path = g_get_home_dir ();
+ if (!home_path)
+ return;
+
+ home_dir = g_file_new_for_path (home_path);
+ emit_open_location (sidebar, home_dir, 0);
+
+ g_object_unref (home_dir);
+}
+
+static void
+add_special_dirs (NautilusGtkPlacesSidebar *sidebar)
+{
+ GList *dirs;
+ int index;
+
+ dirs = NULL;
+ for (index = 0; index < G_USER_N_DIRECTORIES; index++)
+ {
+ const char *path;
+ GFile *root;
+ GIcon *start_icon;
+ char *name;
+ char *mount_uri;
+ char *tooltip;
+
+ if (!_nautilus_gtk_bookmarks_manager_get_is_xdg_dir_builtin (index))
+ continue;
+
+ path = g_get_user_special_dir (index);
+
+ /* XDG resets special dirs to the home directory in case
+ * it's not finiding what it expects. We don't want the home
+ * to be added multiple times in that weird configuration.
+ */
+ if (path == NULL ||
+ path_is_home_dir (path) ||
+ g_list_find_custom (dirs, path, (GCompareFunc) g_strcmp0) != NULL)
+ continue;
+
+ root = g_file_new_for_path (path);
+
+ name = _nautilus_gtk_bookmarks_manager_get_bookmark_label (sidebar->bookmarks_manager, root);
+ if (!name)
+ name = g_file_get_basename (root);
+
+ start_icon = special_directory_get_gicon (index);
+ mount_uri = g_file_get_uri (root);
+ tooltip = g_file_get_parse_name (root);
+
+ add_place (sidebar, NAUTILUS_GTK_PLACES_XDG_DIR,
+ NAUTILUS_GTK_PLACES_SECTION_COMPUTER,
+ name, start_icon, NULL, mount_uri,
+ NULL, NULL, NULL, NULL, 0,
+ tooltip);
+ g_free (name);
+ g_object_unref (root);
+ g_object_unref (start_icon);
+ g_free (mount_uri);
+ g_free (tooltip);
+
+ dirs = g_list_prepend (dirs, (char *)path);
+ }
+
+ g_list_free (dirs);
+}
+
+static char *
+get_home_directory_uri (void)
+{
+ const char *home;
+
+ home = g_get_home_dir ();
+ if (!home)
+ return NULL;
+
+ return g_filename_to_uri (home, NULL, NULL);
+}
+
+static char *
+get_desktop_directory_uri (void)
+{
+ const char *name;
+
+ name = g_get_user_special_dir (G_USER_DIRECTORY_DESKTOP);
+
+ /* "To disable a directory, point it to the homedir."
+ * See http://freedesktop.org/wiki/Software/xdg-user-dirs
+ */
+ if (path_is_home_dir (name))
+ return NULL;
+
+ return g_filename_to_uri (name, NULL, NULL);
+}
+
+static gboolean
+file_is_shown (NautilusGtkPlacesSidebar *sidebar,
+ GFile *file)
+{
+ char *uri;
+ GtkWidget *row;
+ gboolean found = FALSE;
+
+ for (row = gtk_widget_get_first_child (GTK_WIDGET (sidebar->list_box));
+ row != NULL && !found;
+ row = gtk_widget_get_next_sibling (row))
+ {
+ if (!GTK_IS_LIST_BOX_ROW (row))
+ continue;
+
+ g_object_get (row, "uri", &uri, NULL);
+ if (uri)
+ {
+ GFile *other;
+ other = g_file_new_for_uri (uri);
+ found = g_file_equal (file, other);
+ g_object_unref (other);
+ g_free (uri);
+ }
+ }
+
+ return found;
+}
+
+typedef struct
+{
+ NautilusGtkPlacesSidebar *sidebar;
+ guint position;
+} ShortcutData;
+
+static void
+on_app_shortcuts_query_complete (GObject *source,
+ GAsyncResult *result,
+ gpointer data)
+{
+ ShortcutData *sdata = data;
+ NautilusGtkPlacesSidebar *sidebar = sdata->sidebar;
+ guint pos = sdata->position;
+ GFile *file = G_FILE (source);
+ GFileInfo *info;
+
+ g_free (sdata);
+
+ info = g_file_query_info_finish (file, result, NULL);
+
+ if (info)
+ {
+ char *uri;
+ char *tooltip;
+ const char *name;
+ GIcon *start_icon;
+
+ name = g_file_info_get_display_name (info);
+ start_icon = g_file_info_get_symbolic_icon (info);
+ uri = g_file_get_uri (file);
+ tooltip = g_file_get_parse_name (file);
+
+ add_place (sidebar, NAUTILUS_GTK_PLACES_BUILT_IN,
+ NAUTILUS_GTK_PLACES_SECTION_COMPUTER,
+ name, start_icon, NULL, uri,
+ NULL, NULL, NULL, NULL,
+ pos,
+ tooltip);
+
+ g_free (uri);
+ g_free (tooltip);
+
+ g_object_unref (info);
+ }
+}
+
+static void
+add_application_shortcuts (NautilusGtkPlacesSidebar *sidebar)
+{
+ guint i, n;
+
+ n = g_list_model_get_n_items (G_LIST_MODEL (sidebar->shortcuts));
+ for (i = 0; i < n; i++)
+ {
+ GFile *file = g_list_model_get_item (G_LIST_MODEL (sidebar->shortcuts), i);
+ ShortcutData *data;
+
+ g_object_unref (file);
+
+ if (file_is_shown (sidebar, file))
+ continue;
+
+ data = g_new (ShortcutData, 1);
+ data->sidebar = sidebar;
+ data->position = i;
+ g_file_query_info_async (file,
+ "standard::display-name,standard::symbolic-icon",
+ G_FILE_QUERY_INFO_NONE,
+ G_PRIORITY_DEFAULT,
+ sidebar->cancellable,
+ on_app_shortcuts_query_complete,
+ data);
+ }
+}
+
+typedef struct {
+ NautilusGtkPlacesSidebar *sidebar;
+ int index;
+ gboolean is_native;
+} BookmarkQueryClosure;
+
+static void
+on_bookmark_query_info_complete (GObject *source,
+ GAsyncResult *result,
+ gpointer data)
+{
+ BookmarkQueryClosure *clos = data;
+ NautilusGtkPlacesSidebar *sidebar = clos->sidebar;
+ GFile *root = G_FILE (source);
+ GError *error = NULL;
+ GFileInfo *info;
+ char *bookmark_name;
+ char *mount_uri;
+ char *tooltip;
+ GIcon *start_icon;
+
+ info = g_file_query_info_finish (root, result, &error);
+ if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+ goto out;
+
+ bookmark_name = _nautilus_gtk_bookmarks_manager_get_bookmark_label (sidebar->bookmarks_manager, root);
+ if (bookmark_name == NULL && info != NULL)
+ bookmark_name = g_strdup (g_file_info_get_display_name (info));
+ else if (bookmark_name == NULL)
+ {
+ /* Don't add non-UTF-8 bookmarks */
+ bookmark_name = g_file_get_basename (root);
+ if (bookmark_name == NULL)
+ goto out;
+
+ if (!g_utf8_validate (bookmark_name, -1, NULL))
+ {
+ g_free (bookmark_name);
+ goto out;
+ }
+ }
+
+ if (info)
+ start_icon = g_object_ref (g_file_info_get_symbolic_icon (info));
+ else
+ start_icon = g_themed_icon_new_with_default_fallbacks (clos->is_native ? ICON_NAME_FOLDER : ICON_NAME_FOLDER_NETWORK);
+
+ mount_uri = g_file_get_uri (root);
+ tooltip = clos->is_native ? g_file_get_path (root) : g_uri_unescape_string (mount_uri, NULL);
+
+ add_place (sidebar, NAUTILUS_GTK_PLACES_BOOKMARK,
+ NAUTILUS_GTK_PLACES_SECTION_BOOKMARKS,
+ bookmark_name, start_icon, NULL, mount_uri,
+ NULL, NULL, NULL, NULL, clos->index,
+ tooltip);
+
+ g_free (mount_uri);
+ g_free (tooltip);
+ g_free (bookmark_name);
+ g_object_unref (start_icon);
+
+out:
+ g_clear_object (&info);
+ g_clear_error (&error);
+ g_slice_free (BookmarkQueryClosure, clos);
+}
+
+static gboolean
+is_external_volume (GVolume *volume)
+{
+ gboolean is_external;
+ GDrive *drive;
+ char *id;
+
+ drive = g_volume_get_drive (volume);
+ id = g_volume_get_identifier (volume, G_VOLUME_IDENTIFIER_KIND_CLASS);
+
+ is_external = g_volume_can_eject (volume);
+
+ /* NULL volume identifier only happens on removable devices */
+ is_external |= !id;
+
+ if (drive)
+ is_external |= g_drive_is_removable (drive);
+
+ g_clear_object (&drive);
+ g_free (id);
+
+ return is_external;
+}
+
+static void
+update_trash_icon (NautilusGtkPlacesSidebar *sidebar)
+{
+ if (sidebar->trash_row)
+ {
+ GIcon *icon;
+
+ icon = nautilus_trash_monitor_get_symbolic_icon ();
+ nautilus_gtk_sidebar_row_set_start_icon (NAUTILUS_GTK_SIDEBAR_ROW (sidebar->trash_row), icon);
+ g_object_unref (icon);
+ }
+}
+
+static gboolean
+create_cloud_provider_account_row (NautilusGtkPlacesSidebar *sidebar,
+ CloudProvidersAccount *account)
+{
+ GIcon *end_icon;
+ GIcon *start_icon;
+ const char *mount_path;
+ const char *name;
+ char *mount_uri;
+ char *tooltip;
+ guint provider_account_status;
+
+ start_icon = cloud_providers_account_get_icon (account);
+ name = cloud_providers_account_get_name (account);
+ provider_account_status = cloud_providers_account_get_status (account);
+ mount_path = cloud_providers_account_get_path (account);
+ if (start_icon != NULL
+ && name != NULL
+ && provider_account_status != CLOUD_PROVIDERS_ACCOUNT_STATUS_INVALID
+ && mount_path != NULL)
+ {
+ switch (provider_account_status)
+ {
+ case CLOUD_PROVIDERS_ACCOUNT_STATUS_IDLE:
+ end_icon = NULL;
+ break;
+
+ case CLOUD_PROVIDERS_ACCOUNT_STATUS_SYNCING:
+ end_icon = g_themed_icon_new ("emblem-synchronizing-symbolic");
+ break;
+
+ case CLOUD_PROVIDERS_ACCOUNT_STATUS_ERROR:
+ end_icon = g_themed_icon_new ("dialog-warning-symbolic");
+ break;
+
+ default:
+ return FALSE;
+ }
+
+ mount_uri = g_strconcat ("file://", mount_path, NULL);
+
+ /* translators: %s is the name of a cloud provider for files */
+ tooltip = g_strdup_printf (_("Open %s"), name);
+
+ add_place (sidebar, NAUTILUS_GTK_PLACES_BUILT_IN,
+ NAUTILUS_GTK_PLACES_SECTION_CLOUD,
+ name, start_icon, end_icon, mount_uri,
+ NULL, NULL, NULL, account, 0,
+ tooltip);
+
+ g_free (tooltip);
+ g_free (mount_uri);
+ g_clear_object (&end_icon);
+ return TRUE;
+ }
+ else
+ {
+ return FALSE;
+ }
+}
+
+static void
+on_account_updated (GObject *object,
+ GParamSpec *pspec,
+ gpointer user_data)
+{
+ CloudProvidersAccount *account = CLOUD_PROVIDERS_ACCOUNT (object);
+ NautilusGtkPlacesSidebar *sidebar = NAUTILUS_GTK_PLACES_SIDEBAR (user_data);
+
+ if (create_cloud_provider_account_row (sidebar, account))
+ {
+ g_signal_handlers_disconnect_by_data (account, sidebar);
+ sidebar->unready_accounts = g_list_remove (sidebar->unready_accounts, account);
+ g_object_unref (account);
+ }
+}
+
+static void
+update_places (NautilusGtkPlacesSidebar *sidebar)
+{
+ GList *mounts, *l, *ll;
+ GMount *mount;
+ GList *drives;
+ GDrive *drive;
+ GList *volumes;
+ GVolume *volume;
+ GSList *bookmarks, *sl;
+ int index;
+ char *original_uri, *name, *identifier;
+ GtkListBoxRow *selected;
+ char *home_uri;
+ GIcon *start_icon;
+ GFile *root;
+ char *tooltip;
+ GList *network_mounts, *network_volumes;
+ GIcon *new_bookmark_icon;
+ GtkWidget *child;
+ GList *cloud_providers;
+ GList *cloud_providers_accounts;
+ CloudProvidersAccount *cloud_provider_account;
+ CloudProvidersProvider *cloud_provider;
+
+ /* save original selection */
+ selected = gtk_list_box_get_selected_row (GTK_LIST_BOX (sidebar->list_box));
+ if (selected)
+ g_object_get (selected, "uri", &original_uri, NULL);
+ else
+ original_uri = NULL;
+
+ g_cancellable_cancel (sidebar->cancellable);
+
+ g_object_unref (sidebar->cancellable);
+ sidebar->cancellable = g_cancellable_new ();
+
+ /* Reset drag state, just in case we update the places while dragging or
+ * ending a drag */
+ stop_drop_feedback (sidebar);
+ while ((child = gtk_widget_get_first_child (GTK_WIDGET (sidebar->list_box))))
+ gtk_list_box_remove (GTK_LIST_BOX (sidebar->list_box), child);
+
+ network_mounts = network_volumes = NULL;
+
+ /* add built-in places */
+ if (should_show_recent (sidebar))
+ {
+ start_icon = g_themed_icon_new_with_default_fallbacks ("document-open-recent-symbolic");
+ add_place (sidebar, NAUTILUS_GTK_PLACES_BUILT_IN,
+ NAUTILUS_GTK_PLACES_SECTION_COMPUTER,
+ _("Recent"), start_icon, NULL, "recent:///",
+ NULL, NULL, NULL, NULL, 0,
+ _("Recent files"));
+ g_object_unref (start_icon);
+ }
+
+ if (sidebar->show_starred_location)
+ {
+ start_icon = g_themed_icon_new_with_default_fallbacks ("starred-symbolic");
+ add_place (sidebar, NAUTILUS_GTK_PLACES_STARRED_LOCATION,
+ NAUTILUS_GTK_PLACES_SECTION_COMPUTER,
+ _("Starred"), start_icon, NULL, "starred:///",
+ NULL, NULL, NULL, NULL, 0,
+ _("Starred files"));
+ g_object_unref (start_icon);
+ }
+
+ /* home folder */
+ home_uri = get_home_directory_uri ();
+ start_icon = g_themed_icon_new_with_default_fallbacks (ICON_NAME_HOME);
+ add_place (sidebar, NAUTILUS_GTK_PLACES_BUILT_IN,
+ NAUTILUS_GTK_PLACES_SECTION_COMPUTER,
+ _("Home"), start_icon, NULL, home_uri,
+ NULL, NULL, NULL, NULL, 0,
+ _("Open your personal folder"));
+ g_object_unref (start_icon);
+ g_free (home_uri);
+
+ /* desktop */
+ if (sidebar->show_desktop)
+ {
+ char *mount_uri = get_desktop_directory_uri ();
+ if (mount_uri)
+ {
+ start_icon = g_themed_icon_new_with_default_fallbacks (ICON_NAME_DESKTOP);
+ add_place (sidebar, NAUTILUS_GTK_PLACES_BUILT_IN,
+ NAUTILUS_GTK_PLACES_SECTION_COMPUTER,
+ _("Desktop"), start_icon, NULL, mount_uri,
+ NULL, NULL, NULL, NULL, 0,
+ _("Open the contents of your desktop in a folder"));
+ g_object_unref (start_icon);
+ g_free (mount_uri);
+ }
+ }
+
+ /* XDG directories */
+ add_special_dirs (sidebar);
+
+ if (sidebar->show_enter_location)
+ {
+ start_icon = g_themed_icon_new_with_default_fallbacks (ICON_NAME_NETWORK_SERVER);
+ add_place (sidebar, NAUTILUS_GTK_PLACES_ENTER_LOCATION,
+ NAUTILUS_GTK_PLACES_SECTION_COMPUTER,
+ _("Enter Location"), start_icon, NULL, NULL,
+ NULL, NULL, NULL, NULL, 0,
+ _("Manually enter a location"));
+ g_object_unref (start_icon);
+ }
+
+ /* Trash */
+ if (sidebar->show_trash)
+ {
+ start_icon = nautilus_trash_monitor_get_symbolic_icon ();
+ sidebar->trash_row = add_place (sidebar, NAUTILUS_GTK_PLACES_BUILT_IN,
+ NAUTILUS_GTK_PLACES_SECTION_COMPUTER,
+ _("Trash"), start_icon, NULL, "trash:///",
+ NULL, NULL, NULL, NULL, 0,
+ _("Open the trash"));
+ g_object_add_weak_pointer (G_OBJECT (sidebar->trash_row),
+ (gpointer *) &sidebar->trash_row);
+ g_object_unref (start_icon);
+ }
+
+ /* Application-side shortcuts */
+ add_application_shortcuts (sidebar);
+
+ /* Cloud providers */
+ cloud_providers = cloud_providers_collector_get_providers (sidebar->cloud_manager);
+ for (l = sidebar->unready_accounts; l != NULL; l = l->next)
+ {
+ g_signal_handlers_disconnect_by_data (l->data, sidebar);
+ }
+ g_list_free_full (sidebar->unready_accounts, g_object_unref);
+ sidebar->unready_accounts = NULL;
+ for (l = cloud_providers; l != NULL; l = l->next)
+ {
+ cloud_provider = CLOUD_PROVIDERS_PROVIDER (l->data);
+ g_signal_connect_swapped (cloud_provider, "accounts-changed",
+ G_CALLBACK (update_places), sidebar);
+ cloud_providers_accounts = cloud_providers_provider_get_accounts (cloud_provider);
+ for (ll = cloud_providers_accounts; ll != NULL; ll = ll->next)
+ {
+ cloud_provider_account = CLOUD_PROVIDERS_ACCOUNT (ll->data);
+ if (!create_cloud_provider_account_row (sidebar, cloud_provider_account))
+ {
+
+ g_signal_connect (cloud_provider_account, "notify::name",
+ G_CALLBACK (on_account_updated), sidebar);
+ g_signal_connect (cloud_provider_account, "notify::status",
+ G_CALLBACK (on_account_updated), sidebar);
+ g_signal_connect (cloud_provider_account, "notify::status-details",
+ G_CALLBACK (on_account_updated), sidebar);
+ g_signal_connect (cloud_provider_account, "notify::path",
+ G_CALLBACK (on_account_updated), sidebar);
+ sidebar->unready_accounts = g_list_append (sidebar->unready_accounts,
+ g_object_ref (cloud_provider_account));
+ continue;
+ }
+
+ }
+ }
+
+ /* go through all connected drives */
+ drives = g_volume_monitor_get_connected_drives (sidebar->volume_monitor);
+
+ for (l = drives; l != NULL; l = l->next)
+ {
+ drive = l->data;
+
+ volumes = g_drive_get_volumes (drive);
+ if (volumes != NULL)
+ {
+ for (ll = volumes; ll != NULL; ll = ll->next)
+ {
+ volume = ll->data;
+ identifier = g_volume_get_identifier (volume, G_VOLUME_IDENTIFIER_KIND_CLASS);
+
+ if (g_strcmp0 (identifier, "network") == 0)
+ {
+ g_free (identifier);
+ network_volumes = g_list_prepend (network_volumes, volume);
+ continue;
+ }
+ g_free (identifier);
+
+ if (sidebar->show_other_locations && !is_external_volume (volume))
+ {
+ g_object_unref (volume);
+ continue;
+ }
+
+ mount = g_volume_get_mount (volume);
+ if (mount != NULL)
+ {
+ char *mount_uri;
+
+ /* Show mounted volume in the sidebar */
+ start_icon = g_mount_get_symbolic_icon (mount);
+ root = g_mount_get_default_location (mount);
+ mount_uri = g_file_get_uri (root);
+ name = g_mount_get_name (mount);
+ tooltip = g_file_get_parse_name (root);
+
+ add_place (sidebar, NAUTILUS_GTK_PLACES_MOUNTED_VOLUME,
+ NAUTILUS_GTK_PLACES_SECTION_MOUNTS,
+ name, start_icon, NULL, mount_uri,
+ drive, volume, mount, NULL, 0, tooltip);
+ g_object_unref (root);
+ g_object_unref (mount);
+ g_object_unref (start_icon);
+ g_free (tooltip);
+ g_free (name);
+ g_free (mount_uri);
+ }
+ else
+ {
+ /* Do show the unmounted volumes in the sidebar;
+ * this is so the user can mount it (in case automounting
+ * is off).
+ *
+ * Also, even if automounting is enabled, this gives a visual
+ * cue that the user should remember to yank out the media if
+ * he just unmounted it.
+ */
+ start_icon = g_volume_get_symbolic_icon (volume);
+ name = g_volume_get_name (volume);
+ tooltip = g_strdup_printf (_("Mount and open “%s”"), name);
+
+ add_place (sidebar, NAUTILUS_GTK_PLACES_MOUNTED_VOLUME,
+ NAUTILUS_GTK_PLACES_SECTION_MOUNTS,
+ name, start_icon, NULL, NULL,
+ drive, volume, NULL, NULL, 0, tooltip);
+ g_object_unref (start_icon);
+ g_free (name);
+ g_free (tooltip);
+ }
+ g_object_unref (volume);
+ }
+ g_list_free (volumes);
+ }
+ else
+ {
+ if (g_drive_is_media_removable (drive) && !g_drive_is_media_check_automatic (drive))
+ {
+ /* If the drive has no mountable volumes and we cannot detect media change.. we
+ * display the drive in the sidebar so the user can manually poll the drive by
+ * right clicking and selecting "Rescan..."
+ *
+ * This is mainly for drives like floppies where media detection doesn't
+ * work.. but it's also for human beings who like to turn off media detection
+ * in the OS to save battery juice.
+ */
+ start_icon = g_drive_get_symbolic_icon (drive);
+ name = g_drive_get_name (drive);
+ tooltip = g_strdup_printf (_("Mount and open “%s”"), name);
+
+ add_place (sidebar, NAUTILUS_GTK_PLACES_BUILT_IN,
+ NAUTILUS_GTK_PLACES_SECTION_MOUNTS,
+ name, start_icon, NULL, NULL,
+ drive, NULL, NULL, NULL, 0, tooltip);
+ g_object_unref (start_icon);
+ g_free (tooltip);
+ g_free (name);
+ }
+ }
+ }
+ g_list_free_full (drives, g_object_unref);
+
+ /* add all network volumes that are not associated with a drive, and
+ * loop devices
+ */
+ volumes = g_volume_monitor_get_volumes (sidebar->volume_monitor);
+ for (l = volumes; l != NULL; l = l->next)
+ {
+ gboolean is_loop = FALSE;
+ volume = l->data;
+ drive = g_volume_get_drive (volume);
+ if (drive != NULL)
+ {
+ g_object_unref (volume);
+ g_object_unref (drive);
+ continue;
+ }
+
+ identifier = g_volume_get_identifier (volume, G_VOLUME_IDENTIFIER_KIND_CLASS);
+
+ if (g_strcmp0 (identifier, "network") == 0)
+ {
+ g_free (identifier);
+ network_volumes = g_list_prepend (network_volumes, volume);
+ continue;
+ }
+ else if (g_strcmp0 (identifier, "loop") == 0)
+ is_loop = TRUE;
+ g_free (identifier);
+
+ if (sidebar->show_other_locations &&
+ !is_external_volume (volume) &&
+ !is_loop)
+ {
+ g_object_unref (volume);
+ continue;
+ }
+
+ mount = g_volume_get_mount (volume);
+ if (mount != NULL)
+ {
+ char *mount_uri;
+
+ start_icon = g_mount_get_symbolic_icon (mount);
+ root = g_mount_get_default_location (mount);
+ mount_uri = g_file_get_uri (root);
+ tooltip = g_file_get_parse_name (root);
+ name = g_mount_get_name (mount);
+ add_place (sidebar, NAUTILUS_GTK_PLACES_MOUNTED_VOLUME,
+ NAUTILUS_GTK_PLACES_SECTION_MOUNTS,
+ name, start_icon, NULL, mount_uri,
+ NULL, volume, mount, NULL, 0, tooltip);
+ g_object_unref (mount);
+ g_object_unref (root);
+ g_object_unref (start_icon);
+ g_free (name);
+ g_free (tooltip);
+ g_free (mount_uri);
+ }
+ else
+ {
+ /* see comment above in why we add an icon for an unmounted mountable volume */
+ start_icon = g_volume_get_symbolic_icon (volume);
+ name = g_volume_get_name (volume);
+ add_place (sidebar, NAUTILUS_GTK_PLACES_MOUNTED_VOLUME,
+ NAUTILUS_GTK_PLACES_SECTION_MOUNTS,
+ name, start_icon, NULL, NULL,
+ NULL, volume, NULL, NULL, 0, name);
+ g_object_unref (start_icon);
+ g_free (name);
+ }
+ g_object_unref (volume);
+ }
+ g_list_free (volumes);
+
+ /* file system root */
+ if (!sidebar->show_other_locations)
+ {
+ start_icon = g_themed_icon_new_with_default_fallbacks (ICON_NAME_FILESYSTEM);
+ add_place (sidebar, NAUTILUS_GTK_PLACES_BUILT_IN,
+ NAUTILUS_GTK_PLACES_SECTION_MOUNTS,
+ sidebar->hostname, start_icon, NULL, "file:///",
+ NULL, NULL, NULL, NULL, 0,
+ _("Open the contents of the file system"));
+ g_object_unref (start_icon);
+ }
+
+ /* add mounts that has no volume (/etc/mtab mounts, ftp, sftp,...) */
+ mounts = g_volume_monitor_get_mounts (sidebar->volume_monitor);
+
+ for (l = mounts; l != NULL; l = l->next)
+ {
+ char *mount_uri;
+
+ mount = l->data;
+ if (g_mount_is_shadowed (mount))
+ {
+ g_object_unref (mount);
+ continue;
+ }
+ volume = g_mount_get_volume (mount);
+ if (volume != NULL)
+ {
+ g_object_unref (volume);
+ g_object_unref (mount);
+ continue;
+ }
+ root = g_mount_get_default_location (mount);
+
+ if (!g_file_is_native (root))
+ {
+ network_mounts = g_list_prepend (network_mounts, mount);
+ g_object_unref (root);
+ continue;
+ }
+
+ start_icon = g_mount_get_symbolic_icon (mount);
+ mount_uri = g_file_get_uri (root);
+ name = g_mount_get_name (mount);
+ tooltip = g_file_get_parse_name (root);
+ add_place (sidebar, NAUTILUS_GTK_PLACES_MOUNTED_VOLUME,
+ NAUTILUS_GTK_PLACES_SECTION_COMPUTER,
+ name, start_icon, NULL, mount_uri,
+ NULL, NULL, mount, NULL, 0, tooltip);
+ g_object_unref (root);
+ g_object_unref (mount);
+ g_object_unref (start_icon);
+ g_free (name);
+ g_free (mount_uri);
+ g_free (tooltip);
+ }
+ g_list_free (mounts);
+
+ /* add bookmarks */
+ bookmarks = _nautilus_gtk_bookmarks_manager_list_bookmarks (sidebar->bookmarks_manager);
+
+ for (sl = bookmarks, index = 0; sl; sl = sl->next, index++)
+ {
+ gboolean is_native;
+ BookmarkQueryClosure *clos;
+
+ root = sl->data;
+ is_native = g_file_is_native (root);
+
+ if (_nautilus_gtk_bookmarks_manager_get_is_builtin (sidebar->bookmarks_manager, root))
+ continue;
+
+ clos = g_slice_new (BookmarkQueryClosure);
+ clos->sidebar = sidebar;
+ clos->index = index;
+ clos->is_native = is_native;
+ g_file_query_info_async (root,
+ "standard::display-name,standard::symbolic-icon",
+ G_FILE_QUERY_INFO_NONE,
+ G_PRIORITY_DEFAULT,
+ sidebar->cancellable,
+ on_bookmark_query_info_complete,
+ clos);
+ }
+
+ g_slist_free_full (bookmarks, g_object_unref);
+
+ /* Add new bookmark row */
+ new_bookmark_icon = g_themed_icon_new ("bookmark-new-symbolic");
+ sidebar->new_bookmark_row = add_place (sidebar, NAUTILUS_GTK_PLACES_DROP_FEEDBACK,
+ NAUTILUS_GTK_PLACES_SECTION_BOOKMARKS,
+ _("New bookmark"), new_bookmark_icon, NULL, NULL,
+ NULL, NULL, NULL, NULL, 0,
+ _("Add a new bookmark"));
+ gtk_widget_add_css_class (sidebar->new_bookmark_row, "sidebar-new-bookmark-row");
+ g_object_unref (new_bookmark_icon);
+
+ /* network */
+ network_volumes = g_list_reverse (network_volumes);
+ for (l = network_volumes; l != NULL; l = l->next)
+ {
+ volume = l->data;
+ mount = g_volume_get_mount (volume);
+
+ if (mount != NULL)
+ {
+ network_mounts = g_list_prepend (network_mounts, mount);
+ continue;
+ }
+ else
+ {
+ start_icon = g_volume_get_symbolic_icon (volume);
+ name = g_volume_get_name (volume);
+ tooltip = g_strdup_printf (_("Mount and open “%s”"), name);
+
+ add_place (sidebar, NAUTILUS_GTK_PLACES_MOUNTED_VOLUME,
+ NAUTILUS_GTK_PLACES_SECTION_MOUNTS,
+ name, start_icon, NULL, NULL,
+ NULL, volume, NULL, NULL, 0, tooltip);
+ g_object_unref (start_icon);
+ g_free (name);
+ g_free (tooltip);
+ }
+ }
+
+ network_mounts = g_list_reverse (network_mounts);
+ for (l = network_mounts; l != NULL; l = l->next)
+ {
+ char *mount_uri;
+
+ mount = l->data;
+ root = g_mount_get_default_location (mount);
+ start_icon = g_mount_get_symbolic_icon (mount);
+ mount_uri = g_file_get_uri (root);
+ name = g_mount_get_name (mount);
+ tooltip = g_file_get_parse_name (root);
+ add_place (sidebar, NAUTILUS_GTK_PLACES_MOUNTED_VOLUME,
+ NAUTILUS_GTK_PLACES_SECTION_MOUNTS,
+ name, start_icon, NULL, mount_uri,
+ NULL, NULL, mount, NULL, 0, tooltip);
+ g_object_unref (root);
+ g_object_unref (start_icon);
+ g_free (name);
+ g_free (mount_uri);
+ g_free (tooltip);
+ }
+
+
+ g_list_free_full (network_volumes, g_object_unref);
+ g_list_free_full (network_mounts, g_object_unref);
+
+ /* Other locations */
+ if (sidebar->show_other_locations)
+ {
+ start_icon = g_themed_icon_new_with_default_fallbacks (ICON_NAME_OTHER_LOCATIONS);
+
+ add_place (sidebar, NAUTILUS_GTK_PLACES_OTHER_LOCATIONS,
+ NAUTILUS_GTK_PLACES_SECTION_OTHER_LOCATIONS,
+ _("Other Locations"), start_icon, NULL, "other-locations:///",
+ NULL, NULL, NULL, NULL, 0, _("Show other locations"));
+
+ g_object_unref (start_icon);
+ }
+
+ gtk_widget_show (GTK_WIDGET (sidebar));
+ /* We want this hidden by default, but need to do it after the show_all call */
+ nautilus_gtk_sidebar_row_hide (NAUTILUS_GTK_SIDEBAR_ROW (sidebar->new_bookmark_row), TRUE);
+
+ /* restore original selection */
+ if (original_uri)
+ {
+ GFile *restore;
+
+ restore = g_file_new_for_uri (original_uri);
+ nautilus_gtk_places_sidebar_set_location (sidebar, restore);
+ g_object_unref (restore);
+ g_free (original_uri);
+ }
+}
+
+static gboolean
+hover_timer (gpointer user_data)
+{
+ NautilusGtkPlacesSidebar *sidebar = user_data;
+ gboolean open_folder_on_hover;
+ g_autofree gchar *uri = NULL;
+ g_autoptr (GFile) location = NULL;
+
+ open_folder_on_hover = g_settings_get_boolean (nautilus_preferences,
+ NAUTILUS_PREFERENCES_OPEN_FOLDER_ON_DND_HOVER);
+ sidebar->hover_timer_id = 0;
+
+ if (open_folder_on_hover && sidebar->hover_row != NULL)
+ {
+ g_object_get (sidebar->hover_row, "uri", &uri, NULL);
+ if (uri != NULL)
+ {
+ location = g_file_new_for_uri (uri);
+ emit_open_location (sidebar, location, 0);
+ }
+ }
+
+ return G_SOURCE_REMOVE;
+}
+
+static gboolean
+check_valid_drop_target (NautilusGtkPlacesSidebar *sidebar,
+ NautilusGtkSidebarRow *row,
+ const GValue *value)
+{
+ NautilusGtkPlacesPlaceType place_type;
+ NautilusGtkPlacesSectionType section_type;
+ g_autoptr (NautilusFile) dest_file = NULL;
+ gboolean valid = FALSE;
+ char *uri;
+ int drag_action;
+
+ g_return_val_if_fail (value != NULL, TRUE);
+
+ if (row == NULL)
+ return FALSE;
+
+ g_object_get (row,
+ "place-type", &place_type,
+ "section_type", &section_type,
+ "uri", &uri,
+ "file", &dest_file,
+ NULL);
+
+ if (place_type == NAUTILUS_GTK_PLACES_CONNECT_TO_SERVER)
+ {
+ g_free (uri);
+ return FALSE;
+ }
+
+ if (place_type == NAUTILUS_GTK_PLACES_OTHER_LOCATIONS)
+ {
+ g_free (uri);
+ return FALSE;
+ }
+
+ if (place_type == NAUTILUS_GTK_PLACES_DROP_FEEDBACK)
+ {
+ g_free (uri);
+ return TRUE;
+ }
+
+ /* Disallow drops on recent:/// */
+ if (place_type == NAUTILUS_GTK_PLACES_BUILT_IN)
+ {
+ if (g_strcmp0 (uri, "recent:///") == 0)
+ {
+ g_free (uri);
+ return FALSE;
+ }
+ }
+
+ /* Dragging a bookmark? */
+ if (G_VALUE_HOLDS (value, NAUTILUS_TYPE_GTK_SIDEBAR_ROW))
+ {
+ /* Don't allow reordering bookmarks into non-bookmark areas */
+ valid = section_type == NAUTILUS_GTK_PLACES_SECTION_BOOKMARKS;
+ }
+ else if (G_VALUE_HOLDS (value, GDK_TYPE_FILE_LIST))
+ {
+ /* Dragging a file */
+ if (uri != NULL)
+ {
+ drag_action = emit_drag_action_requested (sidebar, dest_file, g_value_get_boxed (value));
+ valid = drag_action > 0;
+ }
+ else
+ {
+ valid = FALSE;
+ }
+ }
+ else
+ {
+ g_assert_not_reached ();
+ valid = TRUE;
+ }
+
+ g_free (uri);
+ return valid;
+}
+
+static void
+update_possible_drop_targets (NautilusGtkPlacesSidebar *sidebar,
+ const GValue *value)
+{
+ GtkWidget *row;
+
+ for (row = gtk_widget_get_first_child (GTK_WIDGET (sidebar->list_box));
+ row != NULL;
+ row = gtk_widget_get_next_sibling (row))
+ {
+ gboolean sensitive;
+
+ if (!GTK_IS_LIST_BOX_ROW (row))
+ continue;
+
+ sensitive = value == NULL ||
+ check_valid_drop_target (sidebar, NAUTILUS_GTK_SIDEBAR_ROW (row), value);
+ gtk_widget_set_sensitive (row, sensitive);
+ }
+}
+
+static void
+start_drop_feedback (NautilusGtkPlacesSidebar *sidebar,
+ const GValue *value)
+{
+ if (value != NULL && G_VALUE_HOLDS (value, GDK_TYPE_FILE_LIST))
+ {
+ GSList *source_list = g_value_get_boxed (value);
+ if (g_slist_length (source_list) == 1)
+ {
+ g_autoptr (NautilusFile) file = NULL;
+ file = nautilus_file_get (source_list->data);
+ if (nautilus_file_is_directory (file))
+ nautilus_gtk_sidebar_row_reveal (NAUTILUS_GTK_SIDEBAR_ROW (sidebar->new_bookmark_row));
+ }
+ }
+ if (value && !G_VALUE_HOLDS (value, NAUTILUS_TYPE_GTK_SIDEBAR_ROW))
+ {
+ /* If the state is permanent, don't change it. The application controls it. */
+ if (sidebar->drop_state != DROP_STATE_NEW_BOOKMARK_ARMED_PERMANENT)
+ sidebar->drop_state = DROP_STATE_NEW_BOOKMARK_ARMED;
+ }
+
+ update_possible_drop_targets (sidebar, value);
+}
+
+static void
+stop_drop_feedback (NautilusGtkPlacesSidebar *sidebar)
+{
+ update_possible_drop_targets (sidebar, NULL);
+
+ if (sidebar->drop_state != DROP_STATE_NEW_BOOKMARK_ARMED_PERMANENT &&
+ sidebar->new_bookmark_row != NULL)
+ {
+ nautilus_gtk_sidebar_row_hide (NAUTILUS_GTK_SIDEBAR_ROW (sidebar->new_bookmark_row), FALSE);
+ sidebar->drop_state = DROP_STATE_NORMAL;
+ }
+
+ if (sidebar->drag_row != NULL)
+ {
+ gtk_widget_show (sidebar->drag_row);
+ sidebar->drag_row = NULL;
+ }
+
+ if (sidebar->row_placeholder != NULL)
+ {
+ if (gtk_widget_get_parent (sidebar->row_placeholder) != NULL)
+ gtk_list_box_remove (GTK_LIST_BOX (sidebar->list_box), sidebar->row_placeholder);
+ sidebar->row_placeholder = NULL;
+ }
+
+ sidebar->dragging_over = FALSE;
+}
+
+static GtkWidget *
+create_placeholder_row (NautilusGtkPlacesSidebar *sidebar)
+{
+ return g_object_new (NAUTILUS_TYPE_GTK_SIDEBAR_ROW, "placeholder", TRUE, NULL);
+}
+
+static GdkDragAction
+drag_motion_callback (GtkDropTarget *target,
+ double x,
+ double y,
+ NautilusGtkPlacesSidebar *sidebar)
+{
+ GdkDragAction action;
+ GtkListBoxRow *row;
+ NautilusGtkPlacesPlaceType place_type;
+ char *drop_target_uri = NULL;
+ int row_index;
+ int row_placeholder_index;
+ const GValue *value;
+ graphene_point_t start;
+
+ sidebar->dragging_over = TRUE;
+ action = 0;
+ row = gtk_list_box_get_row_at_y (GTK_LIST_BOX (sidebar->list_box), y);
+
+ start = sidebar->hover_start_point;
+ if (row != sidebar->hover_row ||
+ gtk_drag_check_threshold (GTK_WIDGET (sidebar), start.x, start.y, x, y))
+ {
+ g_clear_handle_id (&sidebar->hover_timer_id, g_source_remove);
+ sidebar->hover_row = row;
+ sidebar->hover_timer_id = g_timeout_add (HOVER_TIMEOUT, hover_timer, sidebar);
+ sidebar->hover_start_point.x = x;
+ sidebar->hover_start_point.y = y;
+ }
+
+ /* Workaround https://gitlab.gnome.org/GNOME/gtk/-/issues/5023 */
+ gtk_list_box_drag_unhighlight_row (GTK_LIST_BOX (sidebar->list_box));
+
+ /* Nothing to do if no value yet */
+ value = gtk_drop_target_get_value (target);
+ if (value == NULL)
+ goto out;
+
+ /* Nothing to do if the target is not valid drop destination */
+ if (!check_valid_drop_target (sidebar, NAUTILUS_GTK_SIDEBAR_ROW (row), value))
+ goto out;
+
+ if (G_VALUE_HOLDS (value, NAUTILUS_TYPE_GTK_SIDEBAR_ROW))
+ {
+ /* Dragging bookmarks always moves them to another position in the bookmarks list */
+ action = GDK_ACTION_MOVE;
+ if (sidebar->row_placeholder == NULL)
+ {
+ sidebar->row_placeholder = create_placeholder_row (sidebar);
+ g_object_ref_sink (sidebar->row_placeholder);
+ }
+ else if (GTK_WIDGET (row) == sidebar->row_placeholder)
+ {
+ goto out;
+ }
+
+ if (gtk_widget_get_parent (sidebar->row_placeholder) != NULL)
+ gtk_list_box_remove (GTK_LIST_BOX (sidebar->list_box), sidebar->row_placeholder);
+
+ if (row != NULL)
+ {
+ g_object_get (row, "order-index", &row_index, NULL);
+ g_object_get (sidebar->row_placeholder, "order-index", &row_placeholder_index, NULL);
+ /* We order the bookmarks sections based on the bookmark index that we
+ * set on the row as order-index property, but we have to deal with
+ * the placeholder row wanting to be between two consecutive bookmarks,
+ * with two consecutive order-index values which is the usual case.
+ * For that, in the list box sort func we give priority to the placeholder row,
+ * that means that if the index-order is the same as another bookmark
+ * the placeholder row goes before. However if we want to show it after
+ * the current row, for instance when the cursor is in the lower half
+ * of the row, we need to increase the order-index.
+ */
+ row_placeholder_index = row_index;
+ gtk_widget_translate_coordinates (GTK_WIDGET (sidebar), GTK_WIDGET (row),
+ x, y,
+ &x, &y);
+
+ if (y > sidebar->drag_row_height / 2 && row_index > 0)
+ row_placeholder_index++;
+ }
+ else
+ {
+ /* If the user is dragging over an area that has no row, place the row
+ * placeholder in the last position
+ */
+ row_placeholder_index = G_MAXINT32;
+ }
+
+ g_object_set (sidebar->row_placeholder, "order-index", row_placeholder_index, NULL);
+
+ gtk_list_box_prepend (GTK_LIST_BOX (sidebar->list_box),
+ sidebar->row_placeholder);
+ }
+ else if (G_VALUE_HOLDS (value, GDK_TYPE_FILE_LIST))
+ {
+ NautilusFile *file;
+ gtk_list_box_drag_highlight_row (GTK_LIST_BOX (sidebar->list_box), row);
+
+ g_object_get (row,
+ "place-type", &place_type,
+ "uri", &drop_target_uri,
+ "file", &file,
+ NULL);
+ /* URIs are being dragged. See if the caller wants to handle a
+ * file move/copy operation itself, or if we should only try to
+ * create bookmarks out of the dragged URIs.
+ */
+ if (place_type == NAUTILUS_GTK_PLACES_DROP_FEEDBACK)
+ {
+ action = GDK_ACTION_COPY;
+ }
+ else
+ {
+ /* uri may be NULL for unmounted volumes, for example, so we don't allow drops there */
+ if (drop_target_uri != NULL)
+ {
+ GFile *dest_file = g_file_new_for_uri (drop_target_uri);
+
+ action = emit_drag_action_requested (sidebar, file, g_value_get_boxed (value));
+
+ g_object_unref (dest_file);
+ }
+ }
+
+ nautilus_file_unref (file);
+ g_free (drop_target_uri);
+ }
+ else
+ {
+ g_assert_not_reached ();
+ }
+
+ out:
+ start_drop_feedback (sidebar, value);
+ return action;
+}
+
+/* Reorders the bookmark to the specified position */
+static void
+reorder_bookmarks (NautilusGtkPlacesSidebar *sidebar,
+ NautilusGtkSidebarRow *row,
+ int new_position)
+{
+ char *uri;
+ GFile *file;
+
+ g_object_get (row, "uri", &uri, NULL);
+ file = g_file_new_for_uri (uri);
+ _nautilus_gtk_bookmarks_manager_reorder_bookmark (sidebar->bookmarks_manager, file, new_position, NULL);
+
+ g_object_unref (file);
+ g_free (uri);
+}
+
+/* Creates bookmarks for the specified files at the given position in the bookmarks list */
+static void
+drop_files_as_bookmarks (NautilusGtkPlacesSidebar *sidebar,
+ GSList *files,
+ int position)
+{
+ GSList *l;
+
+ for (l = files; l; l = l->next)
+ {
+ GFile *f = G_FILE (l->data);
+ GFileInfo *info = g_file_query_info (f,
+ G_FILE_ATTRIBUTE_STANDARD_TYPE,
+ G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
+ NULL,
+ NULL);
+
+ if (info)
+ {
+ if ((g_file_info_get_file_type (info) == G_FILE_TYPE_DIRECTORY ||
+ g_file_info_get_file_type (info) == G_FILE_TYPE_MOUNTABLE ||
+ g_file_info_get_file_type (info) == G_FILE_TYPE_SHORTCUT ||
+ g_file_info_get_file_type (info) == G_FILE_TYPE_SYMBOLIC_LINK))
+ _nautilus_gtk_bookmarks_manager_insert_bookmark (sidebar->bookmarks_manager, f, position++, NULL);
+
+ g_object_unref (info);
+ }
+ }
+}
+
+static gboolean
+drag_drop_callback (GtkDropTarget *target,
+ const GValue *value,
+ double x,
+ double y,
+ NautilusGtkPlacesSidebar *sidebar)
+{
+ int target_order_index;
+ NautilusGtkPlacesPlaceType target_place_type;
+ NautilusGtkPlacesSectionType target_section_type;
+ char *target_uri;
+ GtkListBoxRow *target_row;
+ gboolean result;
+
+ target_row = gtk_list_box_get_row_at_y (GTK_LIST_BOX (sidebar->list_box), y);
+ if (target_row == NULL)
+ return FALSE;
+
+ if (!check_valid_drop_target (sidebar, NAUTILUS_GTK_SIDEBAR_ROW (target_row), value))
+ return FALSE;
+
+ g_object_get (target_row,
+ "place-type", &target_place_type,
+ "section-type", &target_section_type,
+ "order-index", &target_order_index,
+ "uri", &target_uri,
+ NULL);
+ result = FALSE;
+
+ if (G_VALUE_HOLDS (value, NAUTILUS_TYPE_GTK_SIDEBAR_ROW))
+ {
+ GtkWidget *source_row;
+ /* A bookmark got reordered */
+ if (target_section_type != NAUTILUS_GTK_PLACES_SECTION_BOOKMARKS)
+ goto out;
+
+ source_row = g_value_get_object (value);
+
+ if (sidebar->row_placeholder != NULL)
+ g_object_get (sidebar->row_placeholder, "order-index", &target_order_index, NULL);
+
+ reorder_bookmarks (sidebar, NAUTILUS_GTK_SIDEBAR_ROW (source_row), target_order_index);
+ result = TRUE;
+ }
+ else if (G_VALUE_HOLDS (value, GDK_TYPE_FILE_LIST))
+ {
+ /* Dropping URIs! */
+ if (target_place_type == NAUTILUS_GTK_PLACES_DROP_FEEDBACK)
+ {
+ drop_files_as_bookmarks (sidebar, g_value_get_boxed (value), target_order_index);
+ }
+ else
+ {
+ GFile *dest_file = g_file_new_for_uri (target_uri);
+ GdkDragAction actions;
+
+ actions = gdk_drop_get_actions (gtk_drop_target_get_current_drop (target));
+
+ #ifdef GDK_WINDOWING_X11
+ if (GDK_IS_X11_DISPLAY (gtk_widget_get_display (GTK_WIDGET (sidebar))))
+ {
+ /* Temporary workaround until the below GTK MR (or equivalend fix)
+ * is merged. Without this fix, the preferred action isn't set correctly.
+ * https://gitlab.gnome.org/GNOME/gtk/-/merge_requests/4982 */
+ GdkDrag *drag = gdk_drop_get_drag (gtk_drop_target_get_current_drop (target));
+ actions = gdk_drag_get_selected_action (drag);
+ }
+ #endif
+
+ emit_drag_perform_drop (sidebar,
+ dest_file,
+ g_value_get_boxed (value),
+ actions);
+
+ g_object_unref (dest_file);
+ }
+ result = TRUE;
+ }
+ else
+ {
+ g_assert_not_reached ();
+ }
+
+out:
+ stop_drop_feedback (sidebar);
+ g_free (target_uri);
+ return result;
+}
+
+static void
+dnd_finished_cb (GdkDrag *drag,
+ NautilusGtkPlacesSidebar *sidebar)
+{
+ stop_drop_feedback (sidebar);
+}
+
+static void
+dnd_cancel_cb (GdkDrag *drag,
+ GdkDragCancelReason reason,
+ NautilusGtkPlacesSidebar *sidebar)
+{
+ stop_drop_feedback (sidebar);
+}
+
+/* This functions is called every time the drag source leaves
+ * the sidebar widget.
+ * The problem is that, we start showing hints for drop when the source
+ * start being above the sidebar or when the application request so show
+ * drop hints, but at some moment we need to restore to normal
+ * state.
+ * One could think that here we could simply call stop_drop_feedback,
+ * but that's not true, because this function is called also before drag_drop,
+ * which needs the data from the drag so we cannot free the drag data here.
+ * So now one could think we could just do nothing here, and wait for
+ * drag-end or drag-cancel signals and just stop_drop_feedback there. But that
+ * is also not true, since when the drag comes from a different widget than the
+ * sidebar, when the drag stops the last drag signal we receive is drag-leave.
+ * So here what we will do is restore the state of the sidebar as if no drag
+ * is being done (and if the application didn't request for permanent hints with
+ * nautilus_gtk_places_sidebar_show_drop_hints) and we will free the drag data next time
+ * we build new drag data in drag_data_received.
+ */
+static void
+drag_leave_callback (GtkDropTarget *dest,
+ gpointer user_data)
+{
+ NautilusGtkPlacesSidebar *sidebar = NAUTILUS_GTK_PLACES_SIDEBAR (user_data);
+
+ gtk_list_box_drag_unhighlight_row (GTK_LIST_BOX (sidebar->list_box));
+
+ if (sidebar->drop_state != DROP_STATE_NEW_BOOKMARK_ARMED_PERMANENT)
+ {
+ update_possible_drop_targets (sidebar, FALSE);
+ nautilus_gtk_sidebar_row_hide (NAUTILUS_GTK_SIDEBAR_ROW (sidebar->new_bookmark_row), FALSE);
+ sidebar->drop_state = DROP_STATE_NORMAL;
+ }
+
+ g_clear_handle_id (&sidebar->hover_timer_id, g_source_remove);
+ sidebar->dragging_over = FALSE;
+}
+
+static void
+check_unmount_and_eject (GMount *mount,
+ GVolume *volume,
+ GDrive *drive,
+ gboolean *show_unmount,
+ gboolean *show_eject)
+{
+ *show_unmount = FALSE;
+ *show_eject = FALSE;
+
+ if (drive != NULL)
+ *show_eject = g_drive_can_eject (drive);
+
+ if (volume != NULL)
+ *show_eject |= g_volume_can_eject (volume);
+
+ if (mount != NULL)
+ {
+ *show_eject |= g_mount_can_eject (mount);
+ *show_unmount = g_mount_can_unmount (mount) && !*show_eject;
+ }
+}
+
+static void
+drive_start_from_bookmark_cb (GObject *source_object,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ NautilusGtkPlacesSidebar *sidebar;
+ GError *error;
+ char *primary;
+ char *name;
+
+ sidebar = NAUTILUS_GTK_PLACES_SIDEBAR (user_data);
+
+ error = NULL;
+ if (!g_drive_poll_for_media_finish (G_DRIVE (source_object), res, &error))
+ {
+ if (error->code != G_IO_ERROR_FAILED_HANDLED)
+ {
+ name = g_drive_get_name (G_DRIVE (source_object));
+ primary = g_strdup_printf (_("Unable to start “%s”"), name);
+ g_free (name);
+ emit_show_error_message (sidebar, primary, error->message);
+ g_free (primary);
+ }
+ g_error_free (error);
+ }
+}
+
+static void
+volume_mount_cb (GObject *source_object,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ NautilusGtkSidebarRow *row = NAUTILUS_GTK_SIDEBAR_ROW (user_data);
+ NautilusGtkPlacesSidebar *sidebar;
+ GVolume *volume;
+ GError *error;
+ char *primary;
+ char *name;
+ GMount *mount;
+
+ volume = G_VOLUME (source_object);
+ g_object_get (row, "sidebar", &sidebar, NULL);
+
+ error = NULL;
+ if (!g_volume_mount_finish (volume, result, &error))
+ {
+ if (error->code != G_IO_ERROR_FAILED_HANDLED &&
+ error->code != G_IO_ERROR_ALREADY_MOUNTED)
+ {
+ name = g_volume_get_name (G_VOLUME (source_object));
+ if (g_str_has_prefix (error->message, "Error unlocking"))
+ /* Translators: This means that unlocking an encrypted storage
+ * device failed. %s is the name of the device.
+ */
+ primary = g_strdup_printf (_("Error unlocking “%s”"), name);
+ else
+ primary = g_strdup_printf (_("Unable to access “%s”"), name);
+ g_free (name);
+ emit_show_error_message (sidebar, primary, error->message);
+ g_free (primary);
+ }
+ g_error_free (error);
+ }
+
+ sidebar->mounting = FALSE;
+ nautilus_gtk_sidebar_row_set_busy (row, FALSE);
+
+ mount = g_volume_get_mount (volume);
+ if (mount != NULL)
+ {
+ GFile *location;
+
+ location = g_mount_get_default_location (mount);
+ emit_open_location (sidebar, location, sidebar->go_to_after_mount_open_flags);
+
+ g_object_unref (G_OBJECT (location));
+ g_object_unref (G_OBJECT (mount));
+ }
+
+ g_object_unref (row);
+ g_object_unref (sidebar);
+}
+
+static void
+mount_volume (NautilusGtkSidebarRow *row,
+ GVolume *volume)
+{
+ NautilusGtkPlacesSidebar *sidebar;
+ GMountOperation *mount_op;
+
+ g_object_get (row, "sidebar", &sidebar, NULL);
+
+ mount_op = get_mount_operation (sidebar);
+ g_mount_operation_set_password_save (mount_op, G_PASSWORD_SAVE_FOR_SESSION);
+
+ g_object_ref (row);
+ g_object_ref (sidebar);
+ g_volume_mount (volume, 0, mount_op, NULL, volume_mount_cb, row);
+}
+
+static void
+open_drive (NautilusGtkSidebarRow *row,
+ GDrive *drive,
+ NautilusGtkPlacesOpenFlags open_flags)
+{
+ NautilusGtkPlacesSidebar *sidebar;
+
+ g_object_get (row, "sidebar", &sidebar, NULL);
+
+ if (drive != NULL &&
+ (g_drive_can_start (drive) || g_drive_can_start_degraded (drive)))
+ {
+ GMountOperation *mount_op;
+
+ nautilus_gtk_sidebar_row_set_busy (row, TRUE);
+ mount_op = get_mount_operation (sidebar);
+ g_drive_start (drive, G_DRIVE_START_NONE, mount_op, NULL, drive_start_from_bookmark_cb, NULL);
+ g_object_unref (mount_op);
+ }
+}
+
+static void
+open_volume (NautilusGtkSidebarRow *row,
+ GVolume *volume,
+ NautilusGtkPlacesOpenFlags open_flags)
+{
+ NautilusGtkPlacesSidebar *sidebar;
+
+ g_object_get (row, "sidebar", &sidebar, NULL);
+
+ if (volume != NULL && !sidebar->mounting)
+ {
+ sidebar->mounting = TRUE;
+ sidebar->go_to_after_mount_open_flags = open_flags;
+ nautilus_gtk_sidebar_row_set_busy (row, TRUE);
+ mount_volume (row, volume);
+ }
+}
+
+static void
+open_uri (NautilusGtkPlacesSidebar *sidebar,
+ const char *uri,
+ NautilusGtkPlacesOpenFlags open_flags)
+{
+ GFile *location;
+
+ location = g_file_new_for_uri (uri);
+ emit_open_location (sidebar, location, open_flags);
+ g_object_unref (location);
+}
+
+static void
+open_row (NautilusGtkSidebarRow *row,
+ NautilusGtkPlacesOpenFlags open_flags)
+{
+ char *uri;
+ GDrive *drive;
+ GVolume *volume;
+ NautilusGtkPlacesPlaceType place_type;
+ NautilusGtkPlacesSidebar *sidebar;
+
+ g_object_get (row,
+ "sidebar", &sidebar,
+ "uri", &uri,
+ "place-type", &place_type,
+ "drive", &drive,
+ "volume", &volume,
+ NULL);
+
+ if (place_type == NAUTILUS_GTK_PLACES_OTHER_LOCATIONS)
+ {
+ emit_show_other_locations_with_flags (sidebar, open_flags);
+ }
+ else if (place_type == NAUTILUS_GTK_PLACES_STARRED_LOCATION)
+ {
+ emit_show_starred_location (sidebar, open_flags);
+ }
+ else if (uri != NULL)
+ {
+ open_uri (sidebar, uri, open_flags);
+ }
+ else if (place_type == NAUTILUS_GTK_PLACES_ENTER_LOCATION)
+ {
+ emit_show_enter_location (sidebar);
+ }
+ else if (volume != NULL)
+ {
+ open_volume (row, volume, open_flags);
+ }
+ else if (drive != NULL)
+ {
+ open_drive (row, drive, open_flags);
+ }
+
+ g_object_unref (sidebar);
+ if (drive)
+ g_object_unref (drive);
+ if (volume)
+ g_object_unref (volume);
+ g_free (uri);
+}
+
+/* Callback used for the "Open" menu items in the context menu */
+static void
+open_shortcut_cb (GSimpleAction *action,
+ GVariant *parameter,
+ gpointer data)
+{
+ NautilusGtkPlacesSidebar *sidebar = data;
+ NautilusGtkPlacesOpenFlags flags;
+
+ flags = (NautilusGtkPlacesOpenFlags)g_variant_get_int32 (parameter);
+ open_row (sidebar->context_row, flags);
+}
+
+/* Add bookmark for the selected item - just used from mount points */
+static void
+add_shortcut_cb (GSimpleAction *action,
+ GVariant *parameter,
+ gpointer data)
+{
+ NautilusGtkPlacesSidebar *sidebar = data;
+ char *uri;
+ char *name;
+ GFile *location;
+
+ g_object_get (sidebar->context_row,
+ "uri", &uri,
+ "label", &name,
+ NULL);
+
+ if (uri != NULL)
+ {
+ location = g_file_new_for_uri (uri);
+ if (_nautilus_gtk_bookmarks_manager_insert_bookmark (sidebar->bookmarks_manager, location, -1, NULL))
+ _nautilus_gtk_bookmarks_manager_set_bookmark_label (sidebar->bookmarks_manager, location, name, NULL);
+ g_object_unref (location);
+ }
+
+ g_free (uri);
+ g_free (name);
+}
+
+static void
+rename_entry_changed (GtkEntry *entry,
+ NautilusGtkPlacesSidebar *sidebar)
+{
+ NautilusGtkPlacesPlaceType type;
+ char *name;
+ char *uri;
+ const char *new_name;
+ gboolean found = FALSE;
+ GtkWidget *row;
+
+ new_name = gtk_editable_get_text (GTK_EDITABLE (sidebar->rename_entry));
+
+ if (strcmp (new_name, "") == 0)
+ {
+ gtk_widget_set_sensitive (sidebar->rename_button, FALSE);
+ gtk_label_set_label (GTK_LABEL (sidebar->rename_error), "");
+ return;
+ }
+
+ for (row = gtk_widget_get_first_child (GTK_WIDGET (sidebar->list_box));
+ row != NULL && !found;
+ row = gtk_widget_get_next_sibling (row))
+ {
+ if (!GTK_IS_LIST_BOX_ROW (row))
+ continue;
+
+ g_object_get (row,
+ "place-type", &type,
+ "uri", &uri,
+ "label", &name,
+ NULL);
+
+ if ((type == NAUTILUS_GTK_PLACES_XDG_DIR || type == NAUTILUS_GTK_PLACES_BOOKMARK) &&
+ strcmp (uri, sidebar->rename_uri) != 0 &&
+ strcmp (new_name, name) == 0)
+ found = TRUE;
+
+ g_free (uri);
+ g_free (name);
+ }
+
+ gtk_widget_set_sensitive (sidebar->rename_button, !found);
+ gtk_label_set_label (GTK_LABEL (sidebar->rename_error),
+ found ? _("This name is already taken") : "");
+}
+
+static void
+do_rename (GtkButton *button,
+ NautilusGtkPlacesSidebar *sidebar)
+{
+ char *new_text;
+ GFile *file;
+
+ new_text = g_strdup (gtk_editable_get_text (GTK_EDITABLE (sidebar->rename_entry)));
+
+ file = g_file_new_for_uri (sidebar->rename_uri);
+ if (!_nautilus_gtk_bookmarks_manager_has_bookmark (sidebar->bookmarks_manager, file))
+ _nautilus_gtk_bookmarks_manager_insert_bookmark (sidebar->bookmarks_manager, file, -1, NULL);
+
+ if (sidebar->rename_popover)
+ {
+ gtk_popover_popdown (GTK_POPOVER (sidebar->rename_popover));
+ }
+
+ _nautilus_gtk_bookmarks_manager_set_bookmark_label (sidebar->bookmarks_manager, file, new_text, NULL);
+
+ g_object_unref (file);
+ g_free (new_text);
+
+ g_clear_pointer (&sidebar->rename_uri, g_free);
+
+}
+
+static void
+on_rename_popover_destroy (GtkWidget *rename_popover,
+ NautilusGtkPlacesSidebar *sidebar)
+{
+ if (sidebar)
+ {
+ sidebar->rename_popover = NULL;
+ sidebar->rename_entry = NULL;
+ sidebar->rename_button = NULL;
+ sidebar->rename_error = NULL;
+ }
+}
+
+static void
+create_rename_popover (NautilusGtkPlacesSidebar *sidebar)
+{
+ GtkWidget *popover;
+ GtkWidget *grid;
+ GtkWidget *label;
+ GtkWidget *entry;
+ GtkWidget *button;
+ GtkWidget *error;
+ char *str;
+
+ if (sidebar->rename_popover)
+ return;
+
+ popover = gtk_popover_new ();
+ gtk_widget_set_parent (popover, GTK_WIDGET (sidebar));
+ /* Clean sidebar pointer when its destroyed, most of the times due to its
+ * relative_to associated row being destroyed */
+ g_signal_connect (popover, "destroy", G_CALLBACK (on_rename_popover_destroy), sidebar);
+ gtk_popover_set_position (GTK_POPOVER (popover), GTK_POS_RIGHT);
+ grid = gtk_grid_new ();
+ gtk_popover_set_child (GTK_POPOVER (popover), grid);
+ g_object_set (grid,
+ "margin-start", 10,
+ "margin-end", 10,
+ "margin-top", 10,
+ "margin-bottom", 10,
+ "row-spacing", 6,
+ "column-spacing", 6,
+ NULL);
+ entry = gtk_entry_new ();
+ gtk_entry_set_activates_default (GTK_ENTRY (entry), TRUE);
+ g_signal_connect (entry, "changed", G_CALLBACK (rename_entry_changed), sidebar);
+ str = g_strdup_printf ("<b>%s</b>", _("Name"));
+ label = gtk_label_new (str);
+ gtk_widget_set_halign (label, GTK_ALIGN_START);
+ gtk_label_set_use_markup (GTK_LABEL (label), TRUE);
+ gtk_label_set_mnemonic_widget (GTK_LABEL (label), entry);
+ g_free (str);
+ button = gtk_button_new_with_mnemonic (_("_Rename"));
+ gtk_widget_add_css_class (button, "suggested-action");
+ g_signal_connect (button, "clicked", G_CALLBACK (do_rename), sidebar);
+ error = gtk_label_new ("");
+ gtk_widget_set_halign (error, GTK_ALIGN_START);
+ gtk_grid_attach (GTK_GRID (grid), label, 0, 0, 2, 1);
+ gtk_grid_attach (GTK_GRID (grid), entry, 0, 1, 1, 1);
+ gtk_grid_attach (GTK_GRID (grid), button,1, 1, 1, 1);
+ gtk_grid_attach (GTK_GRID (grid), error, 0, 2, 2, 1);
+ gtk_popover_set_default_widget (GTK_POPOVER (popover), button);
+
+ sidebar->rename_popover = popover;
+ sidebar->rename_entry = entry;
+ sidebar->rename_button = button;
+ sidebar->rename_error = error;
+}
+
+/* Style the row differently while we show a popover for it.
+ * Otherwise, the popover is 'pointing to nothing'. Since the
+ * main popover and the rename popover interleave their hiding
+ * and showing, we have to count to ensure that we don't loose
+ * the state before the last popover is gone.
+ *
+ * This would be nicer as a state, but reusing hover for this
+ * interferes with the normal handling of this state, so just
+ * use a style class.
+ */
+static void
+update_popover_shadowing (GtkWidget *row,
+ gboolean shown)
+{
+ int count;
+
+ count = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (row), "popover-count"));
+ count = shown ? count + 1 : count - 1;
+ g_object_set_data (G_OBJECT (row), "popover-count", GINT_TO_POINTER (count));
+
+ if (count > 0)
+ gtk_widget_add_css_class (row, "has-open-popup");
+ else
+ gtk_widget_remove_css_class (row, "has-open-popup");
+}
+
+static void
+set_prelight (NautilusGtkPlacesSidebar *sidebar)
+{
+ update_popover_shadowing (GTK_WIDGET (sidebar->context_row), TRUE);
+}
+
+static void
+unset_prelight (NautilusGtkPlacesSidebar *sidebar)
+{
+ update_popover_shadowing (GTK_WIDGET (sidebar->context_row), FALSE);
+}
+
+static void
+setup_popover_shadowing (GtkWidget *popover,
+ NautilusGtkPlacesSidebar *sidebar)
+{
+ g_signal_connect_swapped (popover, "map", G_CALLBACK (set_prelight), sidebar);
+ g_signal_connect_swapped (popover, "unmap", G_CALLBACK (unset_prelight), sidebar);
+}
+
+static void
+_popover_set_pointing_to_widget (GtkPopover *popover,
+ GtkWidget *target)
+{
+ GtkWidget *parent;
+ double x, y, w, h;
+
+ parent = gtk_widget_get_parent (GTK_WIDGET (popover));
+
+ if (!gtk_widget_translate_coordinates (target, parent, 0, 0, &x, &y))
+ return;
+
+ w = gtk_widget_get_allocated_width (GTK_WIDGET (target));
+ h = gtk_widget_get_allocated_height (GTK_WIDGET (target));
+
+ gtk_popover_set_pointing_to (popover, &(GdkRectangle){x, y, w, h});
+}
+
+static void
+show_rename_popover (NautilusGtkSidebarRow *row)
+{
+ char *name;
+ char *uri;
+ NautilusGtkPlacesSidebar *sidebar;
+
+ g_object_get (row,
+ "sidebar", &sidebar,
+ "label", &name,
+ "uri", &uri,
+ NULL);
+
+ create_rename_popover (sidebar);
+
+ if (sidebar->rename_uri)
+ g_free (sidebar->rename_uri);
+ sidebar->rename_uri = g_strdup (uri);
+
+ gtk_editable_set_text (GTK_EDITABLE (sidebar->rename_entry), name);
+
+ _popover_set_pointing_to_widget (GTK_POPOVER (sidebar->rename_popover),
+ GTK_WIDGET (row));
+
+ setup_popover_shadowing (sidebar->rename_popover, sidebar);
+
+ gtk_popover_popup (GTK_POPOVER (sidebar->rename_popover));
+ gtk_widget_grab_focus (sidebar->rename_entry);
+
+ g_free (name);
+ g_free (uri);
+ g_object_unref (sidebar);
+}
+
+static void
+rename_bookmark (NautilusGtkSidebarRow *row)
+{
+ NautilusGtkPlacesPlaceType type;
+
+ g_object_get (row, "place-type", &type, NULL);
+
+ if (type != NAUTILUS_GTK_PLACES_BOOKMARK && type != NAUTILUS_GTK_PLACES_XDG_DIR)
+ return;
+
+ show_rename_popover (row);
+}
+
+static void
+rename_shortcut_cb (GSimpleAction *action,
+ GVariant *parameter,
+ gpointer data)
+{
+ NautilusGtkPlacesSidebar *sidebar = data;
+
+ rename_bookmark (sidebar->context_row);
+}
+
+static void
+properties_cb (GSimpleAction *action,
+ GVariant *parameter,
+ gpointer data)
+{
+ NautilusGtkPlacesSidebar *sidebar = data;
+ GList *list;
+ NautilusFile *file;
+
+ g_object_get (sidebar->context_row, "file", &file, NULL);
+
+ list = g_list_append (NULL, file);
+ nautilus_properties_window_present (list, GTK_WIDGET (sidebar), NULL, NULL, NULL);
+
+ nautilus_file_list_free (list);
+}
+
+static void
+empty_trash_cb (GSimpleAction *action,
+ GVariant *parameter,
+ gpointer data)
+{
+ NautilusGtkPlacesSidebar *sidebar = data;
+ nautilus_file_operations_empty_trash (GTK_WIDGET (sidebar), TRUE, NULL);
+}
+
+static void
+remove_bookmark (NautilusGtkSidebarRow *row)
+{
+ NautilusGtkPlacesPlaceType type;
+ char *uri;
+ GFile *file;
+ NautilusGtkPlacesSidebar *sidebar;
+
+ g_object_get (row,
+ "sidebar", &sidebar,
+ "place-type", &type,
+ "uri", &uri,
+ NULL);
+
+ if (type == NAUTILUS_GTK_PLACES_BOOKMARK)
+ {
+ file = g_file_new_for_uri (uri);
+ _nautilus_gtk_bookmarks_manager_remove_bookmark (sidebar->bookmarks_manager, file, NULL);
+ g_object_unref (file);
+ }
+
+ g_free (uri);
+ g_object_unref (sidebar);
+}
+
+static void
+remove_shortcut_cb (GSimpleAction *action,
+ GVariant *parameter,
+ gpointer data)
+{
+ NautilusGtkPlacesSidebar *sidebar = data;
+
+ remove_bookmark (sidebar->context_row);
+}
+
+static void
+mount_shortcut_cb (GSimpleAction *action,
+ GVariant *parameter,
+ gpointer data)
+{
+ NautilusGtkPlacesSidebar *sidebar = data;
+ GVolume *volume;
+
+ g_object_get (sidebar->context_row,
+ "volume", &volume,
+ NULL);
+
+ if (volume != NULL)
+ mount_volume (sidebar->context_row, volume);
+
+ g_object_unref (volume);
+}
+
+static GMountOperation *
+get_mount_operation (NautilusGtkPlacesSidebar *sidebar)
+{
+ GMountOperation *mount_op;
+
+ mount_op = gtk_mount_operation_new (GTK_WINDOW (gtk_widget_get_root (GTK_WIDGET (sidebar))));
+
+ emit_mount_operation (sidebar, mount_op);
+
+ return mount_op;
+}
+
+static GMountOperation *
+get_unmount_operation (NautilusGtkPlacesSidebar *sidebar)
+{
+ GMountOperation *mount_op;
+
+ mount_op = gtk_mount_operation_new (GTK_WINDOW (gtk_widget_get_root (GTK_WIDGET (sidebar))));
+
+ emit_unmount_operation (sidebar, mount_op);
+
+ return mount_op;
+}
+
+/* Returns TRUE if file1 is prefix of file2 or if both files have the
+ * same path
+ */
+static gboolean
+file_prefix_or_same (GFile *file1,
+ GFile *file2)
+{
+ return g_file_has_prefix (file1, file2) ||
+ g_file_equal (file1, file2);
+}
+
+static gboolean
+is_current_location_on_volume (NautilusGtkPlacesSidebar *sidebar,
+ GMount *mount,
+ GVolume *volume,
+ GDrive *drive)
+{
+ gboolean current_location_on_volume;
+ GFile *mount_default_location;
+ GMount *mount_for_volume;
+ GList *volumes_for_drive;
+ GList *volume_for_drive;
+
+ current_location_on_volume = FALSE;
+
+ if (sidebar->current_location != NULL)
+ {
+ if (mount != NULL)
+ {
+ mount_default_location = g_mount_get_default_location (mount);
+ current_location_on_volume = file_prefix_or_same (sidebar->current_location,
+ mount_default_location);
+
+ g_object_unref (mount_default_location);
+ }
+ /* This code path is probably never reached since mount always exists,
+ * and if it doesn't exists we don't offer a way to eject a volume or
+ * drive in the UI. Do it for defensive programming
+ */
+ else if (volume != NULL)
+ {
+ mount_for_volume = g_volume_get_mount (volume);
+ if (mount_for_volume != NULL)
+ {
+ mount_default_location = g_mount_get_default_location (mount_for_volume);
+ current_location_on_volume = file_prefix_or_same (sidebar->current_location,
+ mount_default_location);
+
+ g_object_unref (mount_default_location);
+ g_object_unref (mount_for_volume);
+ }
+ }
+ /* This code path is probably never reached since mount always exists,
+ * and if it doesn't exists we don't offer a way to eject a volume or
+ * drive in the UI. Do it for defensive programming
+ */
+ else if (drive != NULL)
+ {
+ volumes_for_drive = g_drive_get_volumes (drive);
+ for (volume_for_drive = volumes_for_drive; volume_for_drive != NULL; volume_for_drive = volume_for_drive->next)
+ {
+ mount_for_volume = g_volume_get_mount (volume_for_drive->data);
+ if (mount_for_volume != NULL)
+ {
+ mount_default_location = g_mount_get_default_location (mount_for_volume);
+ current_location_on_volume = file_prefix_or_same (sidebar->current_location,
+ mount_default_location);
+
+ g_object_unref (mount_default_location);
+ g_object_unref (mount_for_volume);
+
+ if (current_location_on_volume)
+ break;
+ }
+ }
+ g_list_free_full (volumes_for_drive, g_object_unref);
+ }
+ }
+
+ return current_location_on_volume;
+}
+
+static void
+do_unmount (GMount *mount,
+ NautilusGtkPlacesSidebar *sidebar)
+{
+ if (mount != NULL)
+ {
+ GMountOperation *mount_op;
+ GtkWindow *parent;
+
+ if (is_current_location_on_volume (sidebar, mount, NULL, NULL))
+ open_home (sidebar);
+
+ mount_op = get_unmount_operation (sidebar);
+ parent = gtk_mount_operation_get_parent (GTK_MOUNT_OPERATION (mount_op));
+ nautilus_file_operations_unmount_mount_full (parent, mount, mount_op,
+ FALSE, TRUE, NULL, NULL);
+ g_object_unref (mount_op);
+ }
+}
+
+static void
+unmount_shortcut_cb (GSimpleAction *action,
+ GVariant *parameter,
+ gpointer data)
+{
+ NautilusGtkPlacesSidebar *sidebar = data;
+ GMount *mount;
+
+ g_object_get (sidebar->context_row,
+ "mount", &mount,
+ NULL);
+
+ do_unmount (mount, sidebar);
+
+ if (mount)
+ g_object_unref (mount);
+}
+
+static void
+drive_stop_cb (GObject *source_object,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ NautilusGtkPlacesSidebar *sidebar;
+ GError *error;
+ char *primary;
+ char *name;
+
+ sidebar = user_data;
+
+ error = NULL;
+ if (!g_drive_stop_finish (G_DRIVE (source_object), res, &error))
+ {
+ if (error->code != G_IO_ERROR_FAILED_HANDLED)
+ {
+ name = g_drive_get_name (G_DRIVE (source_object));
+ primary = g_strdup_printf (_("Unable to stop “%s”"), name);
+ g_free (name);
+ emit_show_error_message (sidebar, primary, error->message);
+ g_free (primary);
+ }
+ g_error_free (error);
+ }
+
+ g_object_unref (sidebar);
+}
+
+static void
+drive_eject_cb (GObject *source_object,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ NautilusGtkPlacesSidebar *sidebar;
+ GError *error;
+ char *primary;
+ char *name;
+
+ sidebar = user_data;
+
+ error = NULL;
+ if (!g_drive_eject_with_operation_finish (G_DRIVE (source_object), res, &error))
+ {
+ if (error->code != G_IO_ERROR_FAILED_HANDLED)
+ {
+ name = g_drive_get_name (G_DRIVE (source_object));
+ primary = g_strdup_printf (_("Unable to eject “%s”"), name);
+ g_free (name);
+ emit_show_error_message (sidebar, primary, error->message);
+ g_free (primary);
+ }
+ g_error_free (error);
+ }
+
+ g_object_unref (sidebar);
+}
+
+static void
+volume_eject_cb (GObject *source_object,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ NautilusGtkPlacesSidebar *sidebar;
+ GError *error;
+ char *primary;
+ char *name;
+
+ sidebar = user_data;
+
+ error = NULL;
+ if (!g_volume_eject_with_operation_finish (G_VOLUME (source_object), res, &error))
+ {
+ if (error->code != G_IO_ERROR_FAILED_HANDLED)
+ {
+ name = g_volume_get_name (G_VOLUME (source_object));
+ primary = g_strdup_printf (_("Unable to eject %s"), name);
+ g_free (name);
+ emit_show_error_message (sidebar, primary, error->message);
+ g_free (primary);
+ }
+ g_error_free (error);
+ }
+
+ g_object_unref (sidebar);
+}
+
+static void
+do_eject (GMount *mount,
+ GVolume *volume,
+ GDrive *drive,
+ NautilusGtkPlacesSidebar *sidebar)
+{
+ GMountOperation *mount_op;
+ GtkWindow *parent;
+
+ mount_op = get_unmount_operation (sidebar);
+
+ if (is_current_location_on_volume (sidebar, mount, volume, drive))
+ open_home (sidebar);
+
+ if (mount != NULL)
+ {
+ parent = gtk_mount_operation_get_parent (GTK_MOUNT_OPERATION (mount_op));
+ nautilus_file_operations_unmount_mount_full (parent, mount, mount_op,
+ TRUE, TRUE, NULL, NULL);
+ }
+ /* This code path is probably never reached since mount always exists,
+ * and if it doesn't exists we don't offer a way to eject a volume or
+ * drive in the UI. Do it for defensive programming
+ */
+ else if (volume != NULL)
+ g_volume_eject_with_operation (volume, 0, mount_op, NULL, volume_eject_cb,
+ g_object_ref (sidebar));
+ /* This code path is probably never reached since mount always exists,
+ * and if it doesn't exists we don't offer a way to eject a volume or
+ * drive in the UI. Do it for defensive programming
+ */
+ else if (drive != NULL)
+ {
+ if (g_drive_can_stop (drive))
+ g_drive_stop (drive, 0, mount_op, NULL, drive_stop_cb,
+ g_object_ref (sidebar));
+ else
+ g_drive_eject_with_operation (drive, 0, mount_op, NULL, drive_eject_cb,
+ g_object_ref (sidebar));
+ }
+ g_object_unref (mount_op);
+}
+
+static void
+eject_shortcut_cb (GSimpleAction *action,
+ GVariant *parameter,
+ gpointer data)
+{
+ NautilusGtkPlacesSidebar *sidebar = data;
+ GMount *mount;
+ GVolume *volume;
+ GDrive *drive;
+
+ g_object_get (sidebar->context_row,
+ "mount", &mount,
+ "volume", &volume,
+ "drive", &drive,
+ NULL);
+
+ do_eject (mount, volume, drive, sidebar);
+
+ if (mount)
+ g_object_unref (mount);
+ if (volume)
+ g_object_unref (volume);
+ if (drive)
+ g_object_unref (drive);
+}
+
+static gboolean
+eject_or_unmount_bookmark (NautilusGtkSidebarRow *row)
+{
+ gboolean can_unmount, can_eject;
+ GMount *mount;
+ GVolume *volume;
+ GDrive *drive;
+ gboolean ret;
+ NautilusGtkPlacesSidebar *sidebar;
+
+ g_object_get (row,
+ "sidebar", &sidebar,
+ "mount", &mount,
+ "volume", &volume,
+ "drive", &drive,
+ NULL);
+ ret = FALSE;
+
+ check_unmount_and_eject (mount, volume, drive, &can_unmount, &can_eject);
+ /* if we can eject, it has priority over unmount */
+ if (can_eject)
+ {
+ do_eject (mount, volume, drive, sidebar);
+ ret = TRUE;
+ }
+ else if (can_unmount)
+ {
+ do_unmount (mount, sidebar);
+ ret = TRUE;
+ }
+
+ g_object_unref (sidebar);
+ if (mount)
+ g_object_unref (mount);
+ if (volume)
+ g_object_unref (volume);
+ if (drive)
+ g_object_unref (drive);
+
+ return ret;
+}
+
+static gboolean
+eject_or_unmount_selection (NautilusGtkPlacesSidebar *sidebar)
+{
+ gboolean ret;
+ GtkListBoxRow *row;
+
+ row = gtk_list_box_get_selected_row (GTK_LIST_BOX (sidebar->list_box));
+ ret = eject_or_unmount_bookmark (NAUTILUS_GTK_SIDEBAR_ROW (row));
+
+ return ret;
+}
+
+static void
+drive_poll_for_media_cb (GObject *source_object,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ NautilusGtkPlacesSidebar *sidebar;
+ GError *error;
+ char *primary;
+ char *name;
+
+ sidebar = NAUTILUS_GTK_PLACES_SIDEBAR (user_data);
+
+ error = NULL;
+ if (!g_drive_poll_for_media_finish (G_DRIVE (source_object), res, &error))
+ {
+ if (error->code != G_IO_ERROR_FAILED_HANDLED)
+ {
+ name = g_drive_get_name (G_DRIVE (source_object));
+ primary = g_strdup_printf (_("Unable to poll “%s” for media changes"), name);
+ g_free (name);
+ emit_show_error_message (sidebar, primary, error->message);
+ g_free (primary);
+ }
+ g_error_free (error);
+ }
+
+ g_object_unref (sidebar);
+}
+
+static void
+rescan_shortcut_cb (GSimpleAction *action,
+ GVariant *parameter,
+ gpointer data)
+{
+ NautilusGtkPlacesSidebar *sidebar = data;
+ GDrive *drive;
+
+ g_object_get (sidebar->context_row,
+ "drive", &drive,
+ NULL);
+
+ if (drive != NULL)
+ {
+ g_drive_poll_for_media (drive, NULL, drive_poll_for_media_cb, g_object_ref (sidebar));
+ g_object_unref (drive);
+ }
+}
+
+static void
+drive_start_cb (GObject *source_object,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ NautilusGtkPlacesSidebar *sidebar;
+ GError *error;
+ char *primary;
+ char *name;
+
+ sidebar = NAUTILUS_GTK_PLACES_SIDEBAR (user_data);
+
+ error = NULL;
+ if (!g_drive_start_finish (G_DRIVE (source_object), res, &error))
+ {
+ if (error->code != G_IO_ERROR_FAILED_HANDLED)
+ {
+ name = g_drive_get_name (G_DRIVE (source_object));
+ primary = g_strdup_printf (_("Unable to start “%s”"), name);
+ g_free (name);
+ emit_show_error_message (sidebar, primary, error->message);
+ g_free (primary);
+ }
+ g_error_free (error);
+ }
+
+ g_object_unref (sidebar);
+}
+
+static void
+start_shortcut_cb (GSimpleAction *action,
+ GVariant *parameter,
+ gpointer data)
+{
+ NautilusGtkPlacesSidebar *sidebar = data;
+ GDrive *drive;
+
+ g_object_get (sidebar->context_row,
+ "drive", &drive,
+ NULL);
+
+ if (drive != NULL)
+ {
+ GMountOperation *mount_op;
+
+ mount_op = get_mount_operation (sidebar);
+
+ g_drive_start (drive, G_DRIVE_START_NONE, mount_op, NULL, drive_start_cb, g_object_ref (sidebar));
+
+ g_object_unref (mount_op);
+ g_object_unref (drive);
+ }
+}
+
+static void
+stop_shortcut_cb (GSimpleAction *action,
+ GVariant *parameter,
+ gpointer data)
+{
+ NautilusGtkPlacesSidebar *sidebar = data;
+ GDrive *drive;
+
+ g_object_get (sidebar->context_row,
+ "drive", &drive,
+ NULL);
+
+ if (drive != NULL)
+ {
+ GMountOperation *mount_op;
+
+ mount_op = get_unmount_operation (sidebar);
+ g_drive_stop (drive, G_MOUNT_UNMOUNT_NONE, mount_op, NULL, drive_stop_cb,
+ g_object_ref (sidebar));
+
+ g_object_unref (mount_op);
+ g_object_unref (drive);
+ }
+}
+
+static gboolean
+on_key_pressed (GtkEventControllerKey *controller,
+ guint keyval,
+ guint keycode,
+ GdkModifierType state,
+ NautilusGtkPlacesSidebar *sidebar)
+{
+ guint modifiers;
+ GtkListBoxRow *row;
+
+ row = gtk_list_box_get_selected_row (GTK_LIST_BOX (sidebar->list_box));
+ if (row)
+ {
+ modifiers = gtk_accelerator_get_default_mod_mask ();
+
+ if (keyval == GDK_KEY_Return ||
+ keyval == GDK_KEY_KP_Enter ||
+ keyval == GDK_KEY_ISO_Enter ||
+ keyval == GDK_KEY_space)
+ {
+ NautilusGtkPlacesOpenFlags open_flags = NAUTILUS_GTK_PLACES_OPEN_NORMAL;
+
+ if ((state & modifiers) == GDK_SHIFT_MASK)
+ open_flags = NAUTILUS_GTK_PLACES_OPEN_NEW_TAB;
+ else if ((state & modifiers) == GDK_CONTROL_MASK)
+ open_flags = NAUTILUS_GTK_PLACES_OPEN_NEW_WINDOW;
+
+ open_row (NAUTILUS_GTK_SIDEBAR_ROW (row), open_flags);
+
+ return TRUE;
+ }
+
+ if (keyval == GDK_KEY_Down &&
+ (state & modifiers) == GDK_ALT_MASK)
+ return eject_or_unmount_selection (sidebar);
+
+ if ((keyval == GDK_KEY_Delete ||
+ keyval == GDK_KEY_KP_Delete) &&
+ (state & modifiers) == 0)
+ {
+ remove_bookmark (NAUTILUS_GTK_SIDEBAR_ROW (row));
+ return TRUE;
+ }
+
+ if ((keyval == GDK_KEY_F2) &&
+ (state & modifiers) == 0)
+ {
+ rename_bookmark (NAUTILUS_GTK_SIDEBAR_ROW (row));
+ return TRUE;
+ }
+
+ if ((keyval == GDK_KEY_Menu) ||
+ ((keyval == GDK_KEY_F10) &&
+ (state & modifiers) == GDK_SHIFT_MASK))
+ {
+ popup_menu_cb (NAUTILUS_GTK_SIDEBAR_ROW (row));
+ return TRUE;
+ }
+ }
+
+ return FALSE;
+}
+
+static void
+format_cb (GSimpleAction *action,
+ GVariant *variant,
+ gpointer data)
+{
+ NautilusGtkPlacesSidebar *sidebar = data;
+ g_autoptr (GVolume) volume = NULL;
+ g_autofree gchar *device_identifier = NULL;
+ GVariant *parameters;
+
+ g_object_get (sidebar->context_row, "volume", &volume, NULL);
+ device_identifier = g_volume_get_identifier (volume,
+ G_VOLUME_IDENTIFIER_KIND_UNIX_DEVICE);
+
+ parameters = g_variant_new_parsed ("(objectpath '/org/gnome/DiskUtility', @aay [], "
+ "{'options': <{'block-device': <%s>, "
+ "'format-device': <true> }> })", device_identifier);
+
+ nautilus_dbus_launcher_call (nautilus_dbus_launcher_get(),
+ NAUTILUS_DBUS_LAUNCHER_DISKS,
+ "CommandLine",
+ parameters,
+ GTK_WINDOW (gtk_widget_get_root (GTK_WIDGET (sidebar))));
+
+}
+
+static GActionEntry entries[] = {
+ { "open", open_shortcut_cb, "i", NULL, NULL },
+ { "open-other", open_shortcut_cb, "i", NULL, NULL },
+ { "bookmark", add_shortcut_cb, NULL, NULL, NULL },
+ { "remove", remove_shortcut_cb, NULL, NULL, NULL },
+ { "rename", rename_shortcut_cb, NULL, NULL, NULL },
+ { "mount", mount_shortcut_cb, NULL, NULL, NULL },
+ { "unmount", unmount_shortcut_cb, NULL, NULL, NULL },
+ { "eject", eject_shortcut_cb, NULL, NULL, NULL },
+ { "rescan", rescan_shortcut_cb, NULL, NULL, NULL },
+ { "start", start_shortcut_cb, NULL, NULL, NULL },
+ { "stop", stop_shortcut_cb, NULL, NULL, NULL },
+ { "properties", properties_cb, NULL, NULL, NULL },
+ { "empty-trash", empty_trash_cb, NULL, NULL, NULL },
+ { "format", format_cb, NULL, NULL, NULL },
+};
+
+static gboolean
+should_show_format_command (GVolume *volume,
+ gchar *uri)
+{
+ g_autofree gchar *unix_device_id = NULL;
+ gboolean disks_available;
+
+ if (volume == NULL || !G_IS_VOLUME (volume) || g_str_has_prefix (uri, "mtp://"))
+ return FALSE;
+
+ unix_device_id = g_volume_get_identifier (volume, G_VOLUME_IDENTIFIER_KIND_UNIX_DEVICE);
+ disks_available = nautilus_dbus_launcher_is_available (nautilus_dbus_launcher_get(),
+ NAUTILUS_DBUS_LAUNCHER_DISKS);
+
+ return unix_device_id != NULL && disks_available;
+}
+
+static void
+on_row_popover_destroy (GtkWidget *row_popover,
+ NautilusGtkPlacesSidebar *sidebar)
+{
+ if (sidebar)
+ sidebar->popover = NULL;
+}
+
+static void
+build_popup_menu_using_gmenu (NautilusGtkSidebarRow *row)
+{
+ CloudProvidersAccount *cloud_provider_account;
+ NautilusGtkPlacesSidebar *sidebar;
+ GMenuModel *cloud_provider_menu;
+ GActionGroup *cloud_provider_action_group;
+
+ g_object_get (row,
+ "sidebar", &sidebar,
+ "cloud-provider-account", &cloud_provider_account,
+ NULL);
+
+ /* Cloud provider account */
+ if (cloud_provider_account)
+ {
+ GMenu *menu = g_menu_new ();
+ GMenuItem *item;
+ item = g_menu_item_new (_("_Open"), "row.open");
+ g_menu_item_set_action_and_target_value (item, "row.open",
+ g_variant_new_int32 (NAUTILUS_GTK_PLACES_OPEN_NORMAL));
+ g_menu_append_item (menu, item);
+ g_object_unref (item);
+
+ if (sidebar->open_flags & NAUTILUS_GTK_PLACES_OPEN_NEW_TAB)
+ {
+ item = g_menu_item_new (_("Open in New _Tab"), "row.open-other");
+ g_menu_item_set_action_and_target_value (item, "row.open-other", g_variant_new_int32(NAUTILUS_GTK_PLACES_OPEN_NEW_TAB));
+ g_menu_append_item (menu, item);
+ g_object_unref (item);
+ }
+ if (sidebar->open_flags & NAUTILUS_GTK_PLACES_OPEN_NEW_WINDOW)
+ {
+ item = g_menu_item_new (_("Open in New _Window"), "row.open-other");
+ g_menu_item_set_action_and_target_value (item, "row.open-other", g_variant_new_int32(NAUTILUS_GTK_PLACES_OPEN_NEW_WINDOW));
+ g_menu_append_item (menu, item);
+ g_object_unref (item);
+ }
+ cloud_provider_menu = cloud_providers_account_get_menu_model (cloud_provider_account);
+ cloud_provider_action_group = cloud_providers_account_get_action_group (cloud_provider_account);
+ if (cloud_provider_menu != NULL && cloud_provider_action_group != NULL)
+ {
+ g_menu_append_section (menu, NULL, cloud_provider_menu);
+ gtk_widget_insert_action_group (GTK_WIDGET (sidebar),
+ "cloudprovider",
+ G_ACTION_GROUP (cloud_provider_action_group));
+ }
+ if (sidebar->popover)
+ gtk_widget_unparent (sidebar->popover);
+
+ sidebar->popover = gtk_popover_menu_new_from_model_full (G_MENU_MODEL (menu),
+ GTK_POPOVER_MENU_NESTED);
+ g_object_unref (menu);
+ gtk_widget_set_parent (sidebar->popover, GTK_WIDGET (sidebar));
+ gtk_widget_set_halign (sidebar->popover, GTK_ALIGN_START);
+ gtk_popover_set_has_arrow (GTK_POPOVER (sidebar->popover), FALSE);
+ g_signal_connect (sidebar->popover, "destroy",
+ G_CALLBACK (on_row_popover_destroy), sidebar);
+
+ setup_popover_shadowing (sidebar->popover, sidebar);
+
+ g_object_unref (sidebar);
+ g_object_unref (cloud_provider_account);
+ }
+}
+
+/* Constructs the popover for the sidebar row if needed */
+static void
+create_row_popover (NautilusGtkPlacesSidebar *sidebar,
+ NautilusGtkSidebarRow *row)
+{
+ NautilusGtkPlacesPlaceType type;
+ GMenu *menu, *section;
+ GMenuItem *item;
+ GMount *mount;
+ GVolume *volume;
+ GDrive *drive;
+ GAction *action;
+ gboolean show_unmount, show_eject;
+ gboolean show_stop;
+ g_autofree gchar *uri = NULL;
+ g_autoptr (GFile) file = NULL;
+ gboolean show_properties;
+ g_autoptr (GFile) trash = NULL;
+ gboolean is_trash;
+ CloudProvidersAccount *cloud_provider_account;
+
+ g_object_get (row,
+ "place-type", &type,
+ "drive", &drive,
+ "volume", &volume,
+ "mount", &mount,
+ "uri", &uri,
+ NULL);
+
+ check_unmount_and_eject (mount, volume, drive, &show_unmount, &show_eject);
+ if (uri != NULL)
+ {
+ file = g_file_new_for_uri (uri);
+ trash = g_file_new_for_uri("trash:///");
+ is_trash = g_file_equal (trash, file);
+ show_properties = (g_file_is_native (file) || is_trash || mount != NULL);
+ }
+ else
+ {
+ show_properties = FALSE;
+ is_trash = FALSE;
+ }
+
+ g_object_get (row, "cloud-provider-account", &cloud_provider_account, NULL);
+
+ if (cloud_provider_account)
+ {
+ build_popup_menu_using_gmenu (row);
+ return;
+ }
+
+ action = g_action_map_lookup_action (G_ACTION_MAP (sidebar->row_actions), "remove");
+ g_simple_action_set_enabled (G_SIMPLE_ACTION (action), (type == NAUTILUS_GTK_PLACES_BOOKMARK));
+ action = g_action_map_lookup_action (G_ACTION_MAP (sidebar->row_actions), "rename");
+ g_simple_action_set_enabled (G_SIMPLE_ACTION (action), (type == NAUTILUS_GTK_PLACES_BOOKMARK ||
+ type == NAUTILUS_GTK_PLACES_XDG_DIR));
+ action = g_action_map_lookup_action (G_ACTION_MAP (sidebar->row_actions), "bookmark");
+ g_simple_action_set_enabled (G_SIMPLE_ACTION (action), (type == NAUTILUS_GTK_PLACES_MOUNTED_VOLUME));
+ action = g_action_map_lookup_action (G_ACTION_MAP (sidebar->row_actions), "open");
+ g_simple_action_set_enabled (G_SIMPLE_ACTION (action), !gtk_list_box_row_is_selected (GTK_LIST_BOX_ROW (row)));
+ action = g_action_map_lookup_action (G_ACTION_MAP (sidebar->row_actions), "empty-trash");
+ g_simple_action_set_enabled (G_SIMPLE_ACTION (action), !nautilus_trash_monitor_is_empty());
+
+ menu = g_menu_new ();
+ section = g_menu_new ();
+
+ item = g_menu_item_new (_("_Open"), "row.open");
+ g_menu_item_set_action_and_target_value (item, "row.open",
+ g_variant_new_int32 (NAUTILUS_GTK_PLACES_OPEN_NORMAL));
+ g_menu_append_item (section, item);
+ g_object_unref (item);
+
+ if (sidebar->open_flags & NAUTILUS_GTK_PLACES_OPEN_NEW_TAB)
+ {
+ item = g_menu_item_new (_("Open in New _Tab"), "row.open-other");
+ g_menu_item_set_action_and_target_value (item, "row.open-other",
+ g_variant_new_int32 (NAUTILUS_GTK_PLACES_OPEN_NEW_TAB));
+ g_menu_append_item (section, item);
+ g_object_unref (item);
+ }
+
+ if (sidebar->open_flags & NAUTILUS_GTK_PLACES_OPEN_NEW_WINDOW)
+ {
+ item = g_menu_item_new (_("Open in New _Window"), "row.open-other");
+ g_menu_item_set_action_and_target_value (item, "row.open-other",
+ g_variant_new_int32 (NAUTILUS_GTK_PLACES_OPEN_NEW_WINDOW));
+ g_menu_append_item (section, item);
+ g_object_unref (item);
+ }
+
+ g_menu_append_section (menu, NULL, G_MENU_MODEL (section));
+ g_object_unref (section);
+
+ section = g_menu_new ();
+ item = g_menu_item_new (_("Add to _Bookmarks"), "row.bookmark");
+ g_menu_item_set_attribute (item, "hidden-when", "s", "action-disabled");
+ g_menu_append_item (section, item);
+ g_object_unref (item);
+
+ item = g_menu_item_new (_("_Remove from Bookmarks"), "row.remove");
+ g_menu_item_set_attribute (item, "hidden-when", "s", "action-disabled");
+ g_menu_append_item (section, item);
+ g_object_unref (item);
+
+ item = g_menu_item_new (_("_Rename"), "row.rename");
+ g_menu_append_item (section, item);
+ g_object_unref (item);
+
+ g_menu_append_section (menu, NULL, G_MENU_MODEL (section));
+ g_object_unref (section);
+
+ if (is_trash) {
+ section = g_menu_new ();
+ item = g_menu_item_new (_("Empty Trash"), "row.empty-trash");
+ g_menu_append_item (section, item);
+ g_object_unref (item);
+
+ g_menu_append_section (menu, NULL, G_MENU_MODEL (section));
+ g_object_unref (section);
+ }
+
+ section = g_menu_new ();
+
+ if (volume != NULL && mount == NULL &&
+ g_volume_can_mount (volume))
+ {
+ item = g_menu_item_new (_("_Mount"), "row.mount");
+ g_menu_append_item (section, item);
+ g_object_unref (item);
+ }
+
+ show_stop = (drive != NULL && g_drive_can_stop (drive));
+
+ if (show_unmount && !show_stop)
+ {
+ item = g_menu_item_new (_("_Unmount"), "row.unmount");
+ g_menu_append_item (section, item);
+ g_object_unref (item);
+ }
+
+ if (show_eject)
+ {
+ item = g_menu_item_new (_("_Eject"), "row.eject");
+ g_menu_append_item (section, item);
+ g_object_unref (item);
+ }
+
+ if (drive != NULL &&
+ g_drive_is_media_removable (drive) &&
+ !g_drive_is_media_check_automatic (drive) &&
+ g_drive_can_poll_for_media (drive))
+ {
+ item = g_menu_item_new (_("_Detect Media"), "row.rescan");
+ g_menu_append_item (section, item);
+ g_object_unref (item);
+ }
+
+ if (drive != NULL &&
+ (g_drive_can_start (drive) || g_drive_can_start_degraded (drive)))
+ {
+ const guint ss_type = g_drive_get_start_stop_type (drive);
+ const char *start_label = _("_Start");
+
+ if (ss_type == G_DRIVE_START_STOP_TYPE_SHUTDOWN) start_label = _("_Power On");
+ else if (ss_type == G_DRIVE_START_STOP_TYPE_NETWORK) start_label = _("_Connect Drive");
+ else if (ss_type == G_DRIVE_START_STOP_TYPE_MULTIDISK) start_label = _("_Start Multi-disk Device");
+ else if (ss_type == G_DRIVE_START_STOP_TYPE_PASSWORD) start_label = _("_Unlock Device");
+
+ item = g_menu_item_new (start_label, "row.start");
+ g_menu_append_item (section, item);
+ g_object_unref (item);
+ }
+
+ if (show_stop && !show_unmount)
+ {
+ const guint ss_type = g_drive_get_start_stop_type (drive);
+ const char *stop_label = _("_Stop");
+
+ if (ss_type == G_DRIVE_START_STOP_TYPE_SHUTDOWN) stop_label = _("_Safely Remove Drive");
+ else if (ss_type == G_DRIVE_START_STOP_TYPE_NETWORK) stop_label = _("_Disconnect Drive");
+ else if (ss_type == G_DRIVE_START_STOP_TYPE_MULTIDISK) stop_label = _("_Stop Multi-disk Device");
+ else if (ss_type == G_DRIVE_START_STOP_TYPE_PASSWORD) stop_label = _("_Lock Device");
+
+ item = g_menu_item_new (stop_label, "row.stop");
+ g_menu_append_item (section, item);
+ g_object_unref (item);
+ }
+
+ if (should_show_format_command (volume, uri))
+ {
+ item = g_menu_item_new (_("Format…"), "row.format");
+ g_menu_append_item (section, item);
+ g_object_unref (item);
+ }
+
+ g_menu_append_section (menu, NULL, G_MENU_MODEL (section));
+ g_object_unref (section);
+
+ if (show_properties) {
+ section = g_menu_new ();
+ item = g_menu_item_new (_("Properties"), "row.properties");
+ g_menu_append_item (section, item);
+ g_object_unref (item);
+
+ g_menu_append_section (menu, NULL, G_MENU_MODEL (section));
+ g_object_unref (section);
+ }
+
+ sidebar->popover = gtk_popover_menu_new_from_model (G_MENU_MODEL (menu));
+ g_object_unref (menu);
+ gtk_widget_set_parent (sidebar->popover, GTK_WIDGET (sidebar));
+ gtk_widget_set_halign (sidebar->popover, GTK_ALIGN_START);
+ gtk_popover_set_has_arrow (GTK_POPOVER (sidebar->popover), FALSE);
+ g_signal_connect (sidebar->popover, "destroy", G_CALLBACK (on_row_popover_destroy), sidebar);
+
+ setup_popover_shadowing (sidebar->popover, sidebar);
+}
+
+static void
+show_row_popover (NautilusGtkSidebarRow *row,
+ double x,
+ double y)
+{
+ NautilusGtkPlacesSidebar *sidebar;
+ double x_in_sidebar, y_in_sidebar;
+
+ g_object_get (row, "sidebar", &sidebar, NULL);
+
+ g_clear_pointer (&sidebar->popover, gtk_widget_unparent);
+
+ create_row_popover (sidebar, row);
+
+ if (x == -1 && y == -1)
+ _popover_set_pointing_to_widget (GTK_POPOVER (sidebar->popover), GTK_WIDGET (row));
+ else
+ {
+ gtk_widget_translate_coordinates (GTK_WIDGET (row), GTK_WIDGET (sidebar),
+ x, y, &x_in_sidebar, &y_in_sidebar);
+ gtk_popover_set_pointing_to (GTK_POPOVER (sidebar->popover),
+ &(GdkRectangle){x_in_sidebar, y_in_sidebar, 0, 0});
+ }
+
+ sidebar->context_row = row;
+ gtk_popover_popup (GTK_POPOVER (sidebar->popover));
+
+ g_object_unref (sidebar);
+}
+
+static void
+on_row_activated (GtkListBox *list_box,
+ GtkListBoxRow *row,
+ gpointer user_data)
+{
+ NautilusGtkSidebarRow *selected_row;
+
+ /* Avoid to open a location if the user is dragging. Changing the location
+ * while dragging usually makes clients changing the view of the files, which
+ * is confusing while the user has the attention on the drag
+ */
+ if (NAUTILUS_GTK_PLACES_SIDEBAR (user_data)->dragging_over)
+ return;
+
+ selected_row = NAUTILUS_GTK_SIDEBAR_ROW (gtk_list_box_get_selected_row (list_box));
+ open_row (selected_row, 0);
+}
+
+static void
+on_row_pressed (GtkGestureClick *gesture,
+ int n_press,
+ double x,
+ double y,
+ NautilusGtkSidebarRow *row)
+{
+ NautilusGtkPlacesSidebar *sidebar;
+ NautilusGtkPlacesSectionType section_type;
+ NautilusGtkPlacesPlaceType row_type;
+
+ g_object_get (row,
+ "sidebar", &sidebar,
+ "section_type", &section_type,
+ "place-type", &row_type,
+ NULL);
+
+ if (section_type == NAUTILUS_GTK_PLACES_SECTION_BOOKMARKS)
+ {
+ sidebar->drag_row = GTK_WIDGET (row);
+ sidebar->drag_row_x = (int)x;
+ sidebar->drag_row_y = (int)y;
+ }
+
+ g_object_unref (sidebar);
+}
+
+static void
+on_row_released (GtkGestureClick *gesture,
+ int n_press,
+ double x,
+ double y,
+ NautilusGtkSidebarRow *row)
+{
+ NautilusGtkPlacesSidebar *sidebar;
+ NautilusGtkPlacesSectionType section_type;
+ NautilusGtkPlacesPlaceType row_type;
+ guint button, state;
+
+ g_object_get (row,
+ "sidebar", &sidebar,
+ "section_type", &section_type,
+ "place-type", &row_type,
+ NULL);
+
+ button = gtk_gesture_single_get_current_button (GTK_GESTURE_SINGLE (gesture));
+ state = gtk_event_controller_get_current_event_state (GTK_EVENT_CONTROLLER (gesture));
+
+ if (row)
+ {
+ if (button == 2)
+ {
+ NautilusGtkPlacesOpenFlags open_flags = NAUTILUS_GTK_PLACES_OPEN_NORMAL;
+
+ open_flags = (state & GDK_CONTROL_MASK) ?
+ NAUTILUS_GTK_PLACES_OPEN_NEW_WINDOW :
+ NAUTILUS_GTK_PLACES_OPEN_NEW_TAB;
+
+ open_row (NAUTILUS_GTK_SIDEBAR_ROW (row), open_flags);
+ gtk_gesture_set_state (GTK_GESTURE (gesture),
+ GTK_EVENT_SEQUENCE_CLAIMED);
+ }
+ else if (button == 3)
+ {
+ if (row_type != NAUTILUS_GTK_PLACES_CONNECT_TO_SERVER)
+ show_row_popover (NAUTILUS_GTK_SIDEBAR_ROW (row), x, y);
+ }
+ }
+}
+
+static void
+on_row_dragged (GtkGestureDrag *gesture,
+ double x,
+ double y,
+ NautilusGtkSidebarRow *row)
+{
+ NautilusGtkPlacesSidebar *sidebar;
+
+ g_object_get (row, "sidebar", &sidebar, NULL);
+
+ if (sidebar->drag_row == NULL || sidebar->dragging_over)
+ {
+ g_object_unref (sidebar);
+ return;
+ }
+
+ if (gtk_drag_check_threshold (GTK_WIDGET (row), 0, 0, x, y))
+ {
+ double start_x, start_y;
+ double drag_x, drag_y;
+ GdkContentProvider *content;
+ GdkSurface *surface;
+ GdkDevice *device;
+ GtkAllocation allocation;
+ GtkWidget *drag_widget;
+ GdkDrag *drag;
+
+ gtk_gesture_drag_get_start_point (gesture, &start_x, &start_y);
+ gtk_widget_translate_coordinates (GTK_WIDGET (row),
+ GTK_WIDGET (sidebar),
+ start_x, start_y,
+ &drag_x, &drag_y);
+
+ sidebar->dragging_over = TRUE;
+
+ content = gdk_content_provider_new_typed (NAUTILUS_TYPE_GTK_SIDEBAR_ROW, sidebar->drag_row);
+
+ surface = gtk_native_get_surface (gtk_widget_get_native (GTK_WIDGET (sidebar)));
+ device = gtk_gesture_get_device (GTK_GESTURE (gesture));
+
+ drag = gdk_drag_begin (surface, device, content, GDK_ACTION_MOVE, drag_x, drag_y);
+
+ g_object_unref (content);
+
+ g_signal_connect (drag, "dnd-finished", G_CALLBACK (dnd_finished_cb), sidebar);
+ g_signal_connect (drag, "cancel", G_CALLBACK (dnd_cancel_cb), sidebar);
+
+ gtk_widget_get_allocation (sidebar->drag_row, &allocation);
+ gtk_widget_hide (sidebar->drag_row);
+
+ drag_widget = GTK_WIDGET (nautilus_gtk_sidebar_row_clone (NAUTILUS_GTK_SIDEBAR_ROW (sidebar->drag_row)));
+ sidebar->drag_row_height = allocation.height;
+ gtk_widget_set_size_request (drag_widget, allocation.width, allocation.height);
+ gtk_widget_set_opacity (drag_widget, 0.8);
+
+ gtk_drag_icon_set_child (GTK_DRAG_ICON (gtk_drag_icon_get_for_drag (drag)), drag_widget);
+
+ g_object_unref (drag);
+ }
+
+ g_object_unref (sidebar);
+}
+
+static void
+popup_menu_cb (NautilusGtkSidebarRow *row)
+{
+ NautilusGtkPlacesPlaceType row_type;
+
+ g_object_get (row, "place-type", &row_type, NULL);
+
+ if (row_type != NAUTILUS_GTK_PLACES_CONNECT_TO_SERVER)
+ show_row_popover (row, -1, -1);
+}
+
+static void
+long_press_cb (GtkGesture *gesture,
+ double x,
+ double y,
+ NautilusGtkPlacesSidebar *sidebar)
+{
+ GtkWidget *row;
+
+ row = GTK_WIDGET (gtk_list_box_get_row_at_y (GTK_LIST_BOX (sidebar->list_box), y));
+ if (NAUTILUS_IS_GTK_SIDEBAR_ROW (row))
+ popup_menu_cb (NAUTILUS_GTK_SIDEBAR_ROW (row));
+}
+
+static int
+list_box_sort_func (GtkListBoxRow *row1,
+ GtkListBoxRow *row2,
+ gpointer user_data)
+{
+ NautilusGtkPlacesSectionType section_type_1, section_type_2;
+ NautilusGtkPlacesPlaceType place_type_1, place_type_2;
+ char *label_1, *label_2;
+ int index_1, index_2;
+ int retval = 0;
+
+ g_object_get (row1,
+ "label", &label_1,
+ "place-type", &place_type_1,
+ "section-type", &section_type_1,
+ "order-index", &index_1,
+ NULL);
+ g_object_get (row2,
+ "label", &label_2,
+ "place-type", &place_type_2,
+ "section-type", &section_type_2,
+ "order-index", &index_2,
+ NULL);
+
+ /* Always last position for "connect to server" */
+ if (place_type_1 == NAUTILUS_GTK_PLACES_CONNECT_TO_SERVER)
+ {
+ retval = 1;
+ }
+ else if (place_type_2 == NAUTILUS_GTK_PLACES_CONNECT_TO_SERVER)
+ {
+ retval = -1;
+ }
+ else
+ {
+ if (section_type_1 == section_type_2)
+ {
+ if ((section_type_1 == NAUTILUS_GTK_PLACES_SECTION_COMPUTER &&
+ place_type_1 == place_type_2 &&
+ place_type_1 == NAUTILUS_GTK_PLACES_XDG_DIR) ||
+ section_type_1 == NAUTILUS_GTK_PLACES_SECTION_MOUNTS)
+ {
+ retval = g_utf8_collate (label_1, label_2);
+ }
+ else if ((place_type_1 == NAUTILUS_GTK_PLACES_BOOKMARK || place_type_2 == NAUTILUS_GTK_PLACES_DROP_FEEDBACK) &&
+ (place_type_1 == NAUTILUS_GTK_PLACES_DROP_FEEDBACK || place_type_2 == NAUTILUS_GTK_PLACES_BOOKMARK))
+ {
+ retval = index_1 - index_2;
+ }
+ /* We order the bookmarks sections based on the bookmark index that we
+ * set on the row as an order-index property, but we have to deal with
+ * the placeholder row wanted to be between two consecutive bookmarks,
+ * with two consecutive order-index values which is the usual case.
+ * For that, in the list box sort func we give priority to the placeholder row,
+ * that means that if the index-order is the same as another bookmark
+ * the placeholder row goes before. However if we want to show it after
+ * the current row, for instance when the cursor is in the lower half
+ * of the row, we need to increase the order-index.
+ */
+ else if (place_type_1 == NAUTILUS_GTK_PLACES_BOOKMARK_PLACEHOLDER && place_type_2 == NAUTILUS_GTK_PLACES_BOOKMARK)
+ {
+ if (index_1 == index_2)
+ retval = index_1 - index_2 - 1;
+ else
+ retval = index_1 - index_2;
+ }
+ else if (place_type_1 == NAUTILUS_GTK_PLACES_BOOKMARK && place_type_2 == NAUTILUS_GTK_PLACES_BOOKMARK_PLACEHOLDER)
+ {
+ if (index_1 == index_2)
+ retval = index_1 - index_2 + 1;
+ else
+ retval = index_1 - index_2;
+ }
+ }
+ else
+ {
+ /* Order by section. That means the order in the enum of section types
+ * define the actual order of them in the list */
+ retval = section_type_1 - section_type_2;
+ }
+ }
+
+ g_free (label_1);
+ g_free (label_2);
+
+ return retval;
+}
+
+static void
+update_hostname (NautilusGtkPlacesSidebar *sidebar)
+{
+ GVariant *variant;
+ gsize len;
+ const char *hostname;
+
+ if (sidebar->hostnamed_proxy == NULL)
+ return;
+
+ variant = g_dbus_proxy_get_cached_property (sidebar->hostnamed_proxy,
+ "PrettyHostname");
+ if (variant == NULL)
+ return;
+
+ hostname = g_variant_get_string (variant, &len);
+ if (len > 0 &&
+ g_strcmp0 (sidebar->hostname, hostname) != 0)
+ {
+ g_free (sidebar->hostname);
+ sidebar->hostname = g_strdup (hostname);
+ update_places (sidebar);
+ }
+
+ g_variant_unref (variant);
+}
+
+static void
+hostname_proxy_new_cb (GObject *source_object,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ NautilusGtkPlacesSidebar *sidebar = user_data;
+ GError *error = NULL;
+ GDBusProxy *proxy;
+
+ proxy = g_dbus_proxy_new_for_bus_finish (res, &error);
+ if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+ {
+ g_error_free (error);
+ return;
+ }
+
+ sidebar->hostnamed_proxy = proxy;
+ g_clear_object (&sidebar->hostnamed_cancellable);
+
+ if (error != NULL)
+ {
+ g_debug ("Failed to create D-Bus proxy: %s", error->message);
+ g_error_free (error);
+ return;
+ }
+
+ g_signal_connect_swapped (sidebar->hostnamed_proxy,
+ "g-properties-changed",
+ G_CALLBACK (update_hostname),
+ sidebar);
+ update_hostname (sidebar);
+}
+
+static void
+create_volume_monitor (NautilusGtkPlacesSidebar *sidebar)
+{
+ g_assert (sidebar->volume_monitor == NULL);
+
+ sidebar->volume_monitor = g_volume_monitor_get ();
+
+ g_signal_connect_object (sidebar->volume_monitor, "volume_added",
+ G_CALLBACK (update_places), sidebar, G_CONNECT_SWAPPED);
+ g_signal_connect_object (sidebar->volume_monitor, "volume_removed",
+ G_CALLBACK (update_places), sidebar, G_CONNECT_SWAPPED);
+ g_signal_connect_object (sidebar->volume_monitor, "volume_changed",
+ G_CALLBACK (update_places), sidebar, G_CONNECT_SWAPPED);
+ g_signal_connect_object (sidebar->volume_monitor, "mount_added",
+ G_CALLBACK (update_places), sidebar, G_CONNECT_SWAPPED);
+ g_signal_connect_object (sidebar->volume_monitor, "mount_removed",
+ G_CALLBACK (update_places), sidebar, G_CONNECT_SWAPPED);
+ g_signal_connect_object (sidebar->volume_monitor, "mount_changed",
+ G_CALLBACK (update_places), sidebar, G_CONNECT_SWAPPED);
+ g_signal_connect_object (sidebar->volume_monitor, "drive_disconnected",
+ G_CALLBACK (update_places), sidebar, G_CONNECT_SWAPPED);
+ g_signal_connect_object (sidebar->volume_monitor, "drive_connected",
+ G_CALLBACK (update_places), sidebar, G_CONNECT_SWAPPED);
+ g_signal_connect_object (sidebar->volume_monitor, "drive_changed",
+ G_CALLBACK (update_places), sidebar, G_CONNECT_SWAPPED);
+}
+
+static void
+shell_shows_desktop_changed (GtkSettings *settings,
+ GParamSpec *pspec,
+ gpointer user_data)
+{
+ NautilusGtkPlacesSidebar *sidebar = user_data;
+ gboolean show_desktop;
+
+ g_assert (settings == sidebar->gtk_settings);
+
+ /* Check if the user explicitly set this and, if so, don't change it. */
+ if (sidebar->show_desktop_set)
+ return;
+
+ g_object_get (settings, "gtk-shell-shows-desktop", &show_desktop, NULL);
+
+ if (show_desktop != sidebar->show_desktop)
+ {
+ sidebar->show_desktop = show_desktop;
+ update_places (sidebar);
+ g_object_notify_by_pspec (G_OBJECT (sidebar), properties[PROP_SHOW_DESKTOP]);
+ }
+}
+
+static void
+nautilus_gtk_places_sidebar_init (NautilusGtkPlacesSidebar *sidebar)
+{
+ GtkDropTarget *target;
+ gboolean show_desktop;
+ GtkEventController *controller;
+ GtkGesture *gesture;
+
+ sidebar->cancellable = g_cancellable_new ();
+
+ sidebar->show_trash = TRUE;
+ sidebar->show_other_locations = TRUE;
+ sidebar->show_recent = TRUE;
+ sidebar->show_desktop = TRUE;
+
+ sidebar->shortcuts = g_list_store_new (G_TYPE_FILE);
+
+ create_volume_monitor (sidebar);
+
+ sidebar->open_flags = NAUTILUS_GTK_PLACES_OPEN_NORMAL;
+
+ sidebar->bookmarks_manager = _nautilus_gtk_bookmarks_manager_new ((GtkBookmarksChangedFunc)update_places, sidebar);
+
+ g_signal_connect_object (nautilus_trash_monitor_get (), "trash-state-changed",
+ G_CALLBACK (update_trash_icon), sidebar,
+ G_CONNECT_SWAPPED);
+
+ sidebar->swin = gtk_scrolled_window_new ();
+ gtk_widget_set_parent (sidebar->swin, GTK_WIDGET (sidebar));
+ gtk_widget_set_size_request (sidebar->swin, 140, 280);
+
+ gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (sidebar->swin),
+ GTK_POLICY_NEVER,
+ GTK_POLICY_AUTOMATIC);
+
+ /* list box */
+ sidebar->list_box = gtk_list_box_new ();
+ gtk_widget_add_css_class (sidebar->list_box, "navigation-sidebar");
+
+ gtk_list_box_set_header_func (GTK_LIST_BOX (sidebar->list_box),
+ list_box_header_func, sidebar, NULL);
+ gtk_list_box_set_sort_func (GTK_LIST_BOX (sidebar->list_box),
+ list_box_sort_func, NULL, NULL);
+ gtk_list_box_set_selection_mode (GTK_LIST_BOX (sidebar->list_box), GTK_SELECTION_SINGLE);
+ gtk_list_box_set_activate_on_single_click (GTK_LIST_BOX (sidebar->list_box), TRUE);
+
+ g_signal_connect (sidebar->list_box, "row-activated",
+ G_CALLBACK (on_row_activated), sidebar);
+
+ controller = gtk_event_controller_key_new ();
+ g_signal_connect (controller, "key-pressed",
+ G_CALLBACK (on_key_pressed), sidebar);
+ gtk_widget_add_controller (sidebar->list_box, controller);
+
+ gesture = gtk_gesture_long_press_new ();
+ gtk_gesture_single_set_touch_only (GTK_GESTURE_SINGLE (gesture), TRUE);
+ g_signal_connect (gesture, "pressed",
+ G_CALLBACK (long_press_cb), sidebar);
+ gtk_widget_add_controller (GTK_WIDGET (sidebar), GTK_EVENT_CONTROLLER (gesture));
+
+ /* DND support */
+ target = gtk_drop_target_new (G_TYPE_INVALID, GDK_ACTION_ALL);
+ gtk_drop_target_set_preload (target, TRUE);
+ gtk_drop_target_set_gtypes (target, (GType[2]) { NAUTILUS_TYPE_GTK_SIDEBAR_ROW, GDK_TYPE_FILE_LIST }, 2);
+ g_signal_connect (target, "enter", G_CALLBACK (drag_motion_callback), sidebar);
+ g_signal_connect (target, "motion", G_CALLBACK (drag_motion_callback), sidebar);
+ g_signal_connect (target, "drop", G_CALLBACK (drag_drop_callback), sidebar);
+ g_signal_connect (target, "leave", G_CALLBACK (drag_leave_callback), sidebar);
+ gtk_widget_add_controller (sidebar->list_box, GTK_EVENT_CONTROLLER (target));
+
+ sidebar->drag_row = NULL;
+ sidebar->row_placeholder = NULL;
+ sidebar->dragging_over = FALSE;
+
+ gtk_scrolled_window_set_child (GTK_SCROLLED_WINDOW (sidebar->swin), sidebar->list_box);
+
+ sidebar->hostname = g_strdup (_("Computer"));
+ sidebar->hostnamed_cancellable = g_cancellable_new ();
+ g_dbus_proxy_new_for_bus (G_BUS_TYPE_SYSTEM,
+ G_DBUS_PROXY_FLAGS_GET_INVALIDATED_PROPERTIES,
+ NULL,
+ "org.freedesktop.hostname1",
+ "/org/freedesktop/hostname1",
+ "org.freedesktop.hostname1",
+ sidebar->hostnamed_cancellable,
+ hostname_proxy_new_cb,
+ sidebar);
+
+ sidebar->drop_state = DROP_STATE_NORMAL;
+
+ /* Don't bother trying to trace this across hierarchy changes... */
+ sidebar->gtk_settings = gtk_settings_get_default ();
+ g_signal_connect (sidebar->gtk_settings, "notify::gtk-shell-shows-desktop",
+ G_CALLBACK (shell_shows_desktop_changed), sidebar);
+ g_object_get (sidebar->gtk_settings, "gtk-shell-shows-desktop", &show_desktop, NULL);
+ sidebar->show_desktop = show_desktop;
+
+ /* Cloud providers */
+ sidebar->cloud_manager = cloud_providers_collector_dup_singleton ();
+ g_signal_connect_swapped (sidebar->cloud_manager,
+ "providers-changed",
+ G_CALLBACK (update_places),
+ sidebar);
+
+ /* populate the sidebar */
+ update_places (sidebar);
+
+ sidebar->row_actions = G_ACTION_GROUP (g_simple_action_group_new ());
+ g_action_map_add_action_entries (G_ACTION_MAP (sidebar->row_actions),
+ entries, G_N_ELEMENTS (entries),
+ sidebar);
+ gtk_widget_insert_action_group (GTK_WIDGET (sidebar), "row", sidebar->row_actions);
+
+ gtk_accessible_update_property (GTK_ACCESSIBLE (sidebar),
+ GTK_ACCESSIBLE_PROPERTY_LABEL, _("Sidebar"),
+ GTK_ACCESSIBLE_PROPERTY_DESCRIPTION,
+ _("List of common shortcuts, mountpoints, and bookmarks."), -1);
+}
+
+static void
+nautilus_gtk_places_sidebar_set_property (GObject *obj,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ NautilusGtkPlacesSidebar *sidebar = NAUTILUS_GTK_PLACES_SIDEBAR (obj);
+
+ switch (property_id)
+ {
+ case PROP_LOCATION:
+ nautilus_gtk_places_sidebar_set_location (sidebar, g_value_get_object (value));
+ break;
+
+ case PROP_OPEN_FLAGS:
+ nautilus_gtk_places_sidebar_set_open_flags (sidebar, g_value_get_flags (value));
+ break;
+
+ case PROP_SHOW_RECENT:
+ nautilus_gtk_places_sidebar_set_show_recent (sidebar, g_value_get_boolean (value));
+ break;
+
+ case PROP_SHOW_DESKTOP:
+ nautilus_gtk_places_sidebar_set_show_desktop (sidebar, g_value_get_boolean (value));
+ break;
+
+ case PROP_SHOW_ENTER_LOCATION:
+ nautilus_gtk_places_sidebar_set_show_enter_location (sidebar, g_value_get_boolean (value));
+ break;
+
+ case PROP_SHOW_OTHER_LOCATIONS:
+ nautilus_gtk_places_sidebar_set_show_other_locations (sidebar, g_value_get_boolean (value));
+ break;
+
+ case PROP_SHOW_TRASH:
+ nautilus_gtk_places_sidebar_set_show_trash (sidebar, g_value_get_boolean (value));
+ break;
+
+ case PROP_SHOW_STARRED_LOCATION:
+ nautilus_gtk_places_sidebar_set_show_starred_location (sidebar, g_value_get_boolean (value));
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (obj, property_id, pspec);
+ break;
+ }
+}
+
+static void
+nautilus_gtk_places_sidebar_get_property (GObject *obj,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ NautilusGtkPlacesSidebar *sidebar = NAUTILUS_GTK_PLACES_SIDEBAR (obj);
+
+ switch (property_id)
+ {
+ case PROP_LOCATION:
+ g_value_take_object (value, nautilus_gtk_places_sidebar_get_location (sidebar));
+ break;
+
+ case PROP_OPEN_FLAGS:
+ g_value_set_flags (value, nautilus_gtk_places_sidebar_get_open_flags (sidebar));
+ break;
+
+ case PROP_SHOW_RECENT:
+ g_value_set_boolean (value, nautilus_gtk_places_sidebar_get_show_recent (sidebar));
+ break;
+
+ case PROP_SHOW_DESKTOP:
+ g_value_set_boolean (value, nautilus_gtk_places_sidebar_get_show_desktop (sidebar));
+ break;
+
+ case PROP_SHOW_ENTER_LOCATION:
+ g_value_set_boolean (value, nautilus_gtk_places_sidebar_get_show_enter_location (sidebar));
+ break;
+
+ case PROP_SHOW_OTHER_LOCATIONS:
+ g_value_set_boolean (value, nautilus_gtk_places_sidebar_get_show_other_locations (sidebar));
+ break;
+
+ case PROP_SHOW_TRASH:
+ g_value_set_boolean (value, nautilus_gtk_places_sidebar_get_show_trash (sidebar));
+ break;
+
+ case PROP_SHOW_STARRED_LOCATION:
+ g_value_set_boolean (value, nautilus_gtk_places_sidebar_get_show_starred_location (sidebar));
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (obj, property_id, pspec);
+ break;
+ }
+}
+
+static void
+nautilus_gtk_places_sidebar_dispose (GObject *object)
+{
+ NautilusGtkPlacesSidebar *sidebar;
+ GList *l;
+
+ sidebar = NAUTILUS_GTK_PLACES_SIDEBAR (object);
+
+ if (sidebar->cancellable)
+ {
+ g_cancellable_cancel (sidebar->cancellable);
+ g_object_unref (sidebar->cancellable);
+ sidebar->cancellable = NULL;
+ }
+
+ if (sidebar->bookmarks_manager != NULL)
+ {
+ _nautilus_gtk_bookmarks_manager_free (sidebar->bookmarks_manager);
+ sidebar->bookmarks_manager = NULL;
+ }
+
+ g_clear_pointer (&sidebar->popover, gtk_widget_unparent);
+
+ if (sidebar->rename_popover)
+ {
+ gtk_widget_unparent (sidebar->rename_popover);
+ sidebar->rename_popover = NULL;
+ sidebar->rename_entry = NULL;
+ sidebar->rename_button = NULL;
+ sidebar->rename_error = NULL;
+ }
+
+ if (sidebar->trash_row)
+ {
+ g_object_remove_weak_pointer (G_OBJECT (sidebar->trash_row),
+ (gpointer *) &sidebar->trash_row);
+ sidebar->trash_row = NULL;
+ }
+
+ if (sidebar->volume_monitor != NULL)
+ {
+ g_signal_handlers_disconnect_by_func (sidebar->volume_monitor,
+ update_places, sidebar);
+ g_clear_object (&sidebar->volume_monitor);
+ }
+
+ if (sidebar->hostnamed_cancellable != NULL)
+ {
+ g_cancellable_cancel (sidebar->hostnamed_cancellable);
+ g_clear_object (&sidebar->hostnamed_cancellable);
+ }
+
+ g_clear_object (&sidebar->hostnamed_proxy);
+ g_free (sidebar->hostname);
+ sidebar->hostname = NULL;
+
+ if (sidebar->gtk_settings)
+ {
+ g_signal_handlers_disconnect_by_func (sidebar->gtk_settings, shell_shows_desktop_changed, sidebar);
+ sidebar->gtk_settings = NULL;
+ }
+
+ g_clear_object (&sidebar->current_location);
+ g_clear_pointer (&sidebar->rename_uri, g_free);
+ g_clear_object (&sidebar->shortcuts);
+
+ g_clear_handle_id (&sidebar->hover_timer_id, g_source_remove);
+
+ for (l = sidebar->unready_accounts; l != NULL; l = l->next)
+ {
+ g_signal_handlers_disconnect_by_data (l->data, sidebar);
+ }
+ g_list_free_full (sidebar->unready_accounts, g_object_unref);
+ sidebar->unready_accounts = NULL;
+ if (sidebar->cloud_manager)
+ {
+ g_signal_handlers_disconnect_by_data (sidebar->cloud_manager, sidebar);
+ for (l = cloud_providers_collector_get_providers (sidebar->cloud_manager);
+ l != NULL; l = l->next)
+ {
+ g_signal_handlers_disconnect_by_data (l->data, sidebar);
+ }
+ g_object_unref (sidebar->cloud_manager);
+ sidebar->cloud_manager = NULL;
+ }
+
+ G_OBJECT_CLASS (nautilus_gtk_places_sidebar_parent_class)->dispose (object);
+}
+
+static void
+nautilus_gtk_places_sidebar_finalize (GObject *object)
+{
+ NautilusGtkPlacesSidebar *sidebar = NAUTILUS_GTK_PLACES_SIDEBAR (object);
+
+ g_clear_object (&sidebar->row_actions);
+
+ g_clear_pointer (&sidebar->swin, gtk_widget_unparent);
+
+ G_OBJECT_CLASS (nautilus_gtk_places_sidebar_parent_class)->finalize (object);
+}
+
+static void
+nautilus_gtk_places_sidebar_measure (GtkWidget *widget,
+ GtkOrientation orientation,
+ int for_size,
+ int *minimum,
+ int *natural,
+ int *minimum_baseline,
+ int *natural_baseline)
+{
+ NautilusGtkPlacesSidebar *sidebar = NAUTILUS_GTK_PLACES_SIDEBAR (widget);
+
+ gtk_widget_measure (sidebar->swin,
+ orientation,
+ for_size,
+ minimum, natural,
+ minimum_baseline, natural_baseline);
+}
+
+static void
+nautilus_gtk_places_sidebar_size_allocate (GtkWidget *widget,
+ int width,
+ int height,
+ int baseline)
+{
+ NautilusGtkPlacesSidebar *sidebar = NAUTILUS_GTK_PLACES_SIDEBAR (widget);
+
+ gtk_widget_size_allocate (sidebar->swin,
+ &(GtkAllocation) { 0, 0, width, height },
+ baseline);
+
+ if (sidebar->popover)
+ gtk_popover_present (GTK_POPOVER (sidebar->popover));
+
+ if (sidebar->rename_popover)
+ gtk_popover_present (GTK_POPOVER (sidebar->rename_popover));
+}
+
+static void
+nautilus_gtk_places_sidebar_class_init (NautilusGtkPlacesSidebarClass *class)
+{
+ GObjectClass *gobject_class = G_OBJECT_CLASS (class);
+ GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (class);
+
+
+ gobject_class->dispose = nautilus_gtk_places_sidebar_dispose;
+ gobject_class->finalize = nautilus_gtk_places_sidebar_finalize;
+ gobject_class->set_property = nautilus_gtk_places_sidebar_set_property;
+ gobject_class->get_property = nautilus_gtk_places_sidebar_get_property;
+
+ widget_class->measure = nautilus_gtk_places_sidebar_measure;
+ widget_class->size_allocate = nautilus_gtk_places_sidebar_size_allocate;
+
+ /*
+ * NautilusGtkPlacesSidebar::open-location:
+ * @sidebar: the object which received the signal.
+ * @location: (type Gio.File): GFile to which the caller should switch.
+ * @open_flags: a single value from NautilusGtkPlacesOpenFlags specifying how the @location should be opened.
+ *
+ * The places sidebar emits this signal when the user selects a location
+ * in it. The calling application should display the contents of that
+ * location; for example, a file manager should show a list of files in
+ * the specified location.
+ */
+ places_sidebar_signals [OPEN_LOCATION] =
+ g_signal_new ("open-location",
+ G_OBJECT_CLASS_TYPE (gobject_class),
+ G_SIGNAL_RUN_FIRST,
+ G_STRUCT_OFFSET (NautilusGtkPlacesSidebarClass, open_location),
+ NULL, NULL,
+ NULL,
+ G_TYPE_NONE, 2,
+ G_TYPE_OBJECT,
+ NAUTILUS_TYPE_OPEN_FLAGS);
+
+ /*
+ * NautilusGtkPlacesSidebar::show-error-message:
+ * @sidebar: the object which received the signal.
+ * @primary: primary message with a summary of the error to show.
+ * @secondary: secondary message with details of the error to show.
+ *
+ * The places sidebar emits this signal when it needs the calling
+ * application to present an error message. Most of these messages
+ * refer to mounting or unmounting media, for example, when a drive
+ * cannot be started for some reason.
+ */
+ places_sidebar_signals [SHOW_ERROR_MESSAGE] =
+ g_signal_new ("show-error-message",
+ G_OBJECT_CLASS_TYPE (gobject_class),
+ G_SIGNAL_RUN_FIRST,
+ G_STRUCT_OFFSET (NautilusGtkPlacesSidebarClass, show_error_message),
+ NULL, NULL,
+ NULL,
+ G_TYPE_NONE, 2,
+ G_TYPE_STRING,
+ G_TYPE_STRING);
+
+ /*
+ * NautilusGtkPlacesSidebar::show-enter-location:
+ * @sidebar: the object which received the signal.
+ *
+ * The places sidebar emits this signal when it needs the calling
+ * application to present a way to directly enter a location.
+ * For example, the application may bring up a dialog box asking for
+ * a URL like "http://http.example.com".
+ */
+ places_sidebar_signals [SHOW_ENTER_LOCATION] =
+ g_signal_new ("show-enter-location",
+ G_OBJECT_CLASS_TYPE (gobject_class),
+ G_SIGNAL_RUN_FIRST,
+ G_STRUCT_OFFSET (NautilusGtkPlacesSidebarClass, show_enter_location),
+ NULL, NULL,
+ NULL,
+ G_TYPE_NONE, 0);
+
+ /*
+ * NautilusGtkPlacesSidebar::drag-action-requested:
+ * @sidebar: the object which received the signal.
+ * @drop: (type Gdk.Drop): GdkDrop with information about the drag operation
+ * @dest_file: (type Gio.File): GFile with the tentative location that is being hovered for a drop
+ * @source_file_list: (type GLib.SList) (element-type GFile) (transfer none):
+ * List of GFile that are being dragged
+ *
+ * When the user starts a drag-and-drop operation and the sidebar needs
+ * to ask the application for which drag action to perform, then the
+ * sidebar will emit this signal.
+ *
+ * The application can evaluate the @context for customary actions, or
+ * it can check the type of the files indicated by @source_file_list against the
+ * possible actions for the destination @dest_file.
+ *
+ * The drag action to use must be the return value of the signal handler.
+ *
+ * Returns: The drag action to use, for example, GDK_ACTION_COPY
+ * or GDK_ACTION_MOVE, or 0 if no action is allowed here (i.e. drops
+ * are not allowed in the specified @dest_file).
+ */
+ places_sidebar_signals [DRAG_ACTION_REQUESTED] =
+ g_signal_new ("drag-action-requested",
+ G_OBJECT_CLASS_TYPE (gobject_class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (NautilusGtkPlacesSidebarClass, drag_action_requested),
+ NULL, NULL,
+ NULL,
+ GDK_TYPE_DRAG_ACTION, 2,
+ G_TYPE_OBJECT,
+ GDK_TYPE_FILE_LIST);
+
+ /*
+ * NautilusGtkPlacesSidebar::drag-action-ask:
+ * @sidebar: the object which received the signal.
+ * @actions: Possible drag actions that need to be asked for.
+ *
+ * The places sidebar emits this signal when it needs to ask the application
+ * to pop up a menu to ask the user for which drag action to perform.
+ *
+ * Returns: the final drag action that the sidebar should pass to the drag side
+ * of the drag-and-drop operation.
+ */
+ places_sidebar_signals [DRAG_ACTION_ASK] =
+ g_signal_new ("drag-action-ask",
+ G_OBJECT_CLASS_TYPE (gobject_class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (NautilusGtkPlacesSidebarClass, drag_action_ask),
+ NULL, NULL,
+ NULL,
+ GDK_TYPE_DRAG_ACTION, 1,
+ GDK_TYPE_DRAG_ACTION);
+
+ /*
+ * NautilusGtkPlacesSidebar::drag-perform-drop:
+ * @sidebar: the object which received the signal.
+ * @dest_file: (type Gio.File): Destination GFile.
+ * @source_file_list: (type GLib.SList) (element-type GFile) (transfer none):
+ * GSList of GFile that got dropped.
+ * @action: Drop action to perform.
+ *
+ * The places sidebar emits this signal when the user completes a
+ * drag-and-drop operation and one of the sidebar's items is the
+ * destination. This item is in the @dest_file, and the
+ * @source_file_list has the list of files that are dropped into it and
+ * which should be copied/moved/etc. based on the specified @action.
+ */
+ places_sidebar_signals [DRAG_PERFORM_DROP] =
+ g_signal_new ("drag-perform-drop",
+ G_OBJECT_CLASS_TYPE (gobject_class),
+ G_SIGNAL_RUN_FIRST,
+ G_STRUCT_OFFSET (NautilusGtkPlacesSidebarClass, drag_perform_drop),
+ NULL, NULL,
+ NULL,
+ G_TYPE_NONE, 3,
+ G_TYPE_OBJECT,
+ GDK_TYPE_FILE_LIST,
+ GDK_TYPE_DRAG_ACTION);
+
+ /*
+ * NautilusGtkPlacesSidebar::show-other-locations-with-flags:
+ * @sidebar: the object which received the signal.
+ * @open_flags: a single value from NautilusGtkPlacesOpenFlags specifying how it should be opened.
+ *
+ * The places sidebar emits this signal when it needs the calling
+ * application to present a way to show other locations e.g. drives
+ * and network access points.
+ * For example, the application may bring up a page showing persistent
+ * volumes and discovered network addresses.
+ */
+ places_sidebar_signals [SHOW_OTHER_LOCATIONS_WITH_FLAGS] =
+ g_signal_new ("show-other-locations-with-flags",
+ G_OBJECT_CLASS_TYPE (gobject_class),
+ G_SIGNAL_RUN_FIRST,
+ G_STRUCT_OFFSET (NautilusGtkPlacesSidebarClass, show_other_locations_with_flags),
+ NULL, NULL,
+ NULL,
+ G_TYPE_NONE, 1,
+ NAUTILUS_TYPE_OPEN_FLAGS);
+
+ /*
+ * NautilusGtkPlacesSidebar::mount:
+ * @sidebar: the object which received the signal.
+ * @mount_operation: the GMountOperation that is going to start.
+ *
+ * The places sidebar emits this signal when it starts a new operation
+ * because the user clicked on some location that needs mounting.
+ * In this way the application using the NautilusGtkPlacesSidebar can track the
+ * progress of the operation and, for example, show a notification.
+ */
+ places_sidebar_signals [MOUNT] =
+ g_signal_new ("mount",
+ G_OBJECT_CLASS_TYPE (gobject_class),
+ G_SIGNAL_RUN_FIRST,
+ G_STRUCT_OFFSET (NautilusGtkPlacesSidebarClass, mount),
+ NULL, NULL,
+ NULL,
+ G_TYPE_NONE,
+ 1,
+ G_TYPE_MOUNT_OPERATION);
+ /*
+ * NautilusGtkPlacesSidebar::unmount:
+ * @sidebar: the object which received the signal.
+ * @mount_operation: the GMountOperation that is going to start.
+ *
+ * The places sidebar emits this signal when it starts a new operation
+ * because the user for example ejected some drive or unmounted a mount.
+ * In this way the application using the NautilusGtkPlacesSidebar can track the
+ * progress of the operation and, for example, show a notification.
+ */
+ places_sidebar_signals [UNMOUNT] =
+ g_signal_new ("unmount",
+ G_OBJECT_CLASS_TYPE (gobject_class),
+ G_SIGNAL_RUN_FIRST,
+ G_STRUCT_OFFSET (NautilusGtkPlacesSidebarClass, unmount),
+ NULL, NULL,
+ NULL,
+ G_TYPE_NONE,
+ 1,
+ G_TYPE_MOUNT_OPERATION);
+
+ /*
+ * NautilusGtkPlacesSidebar::show-starred-location:
+ * @sidebar: the object which received the signal
+ * @flags: the flags for the operation
+ *
+ * The places sidebar emits this signal when it needs the calling
+ * application to present a way to show the starred files. In GNOME,
+ * starred files are implemented by setting the nao:predefined-tag-favorite
+ * tag in the tracker database.
+ */
+ places_sidebar_signals [SHOW_STARRED_LOCATION] =
+ g_signal_new ("show-starred-location",
+ G_OBJECT_CLASS_TYPE (gobject_class),
+ G_SIGNAL_RUN_FIRST,
+ G_STRUCT_OFFSET (NautilusGtkPlacesSidebarClass, show_starred_location),
+ NULL, NULL,
+ NULL,
+ G_TYPE_NONE, 1,
+ NAUTILUS_TYPE_OPEN_FLAGS);
+
+ properties[PROP_LOCATION] =
+ g_param_spec_object ("location",
+ "Location to Select",
+ "The location to highlight in the sidebar",
+ G_TYPE_FILE,
+ G_PARAM_READWRITE|G_PARAM_STATIC_NAME|G_PARAM_STATIC_NICK|G_PARAM_STATIC_BLURB);
+ properties[PROP_OPEN_FLAGS] =
+ g_param_spec_flags ("open-flags",
+ "Open Flags",
+ "Modes in which the calling application can open locations selected in the sidebar",
+ NAUTILUS_TYPE_OPEN_FLAGS,
+ NAUTILUS_GTK_PLACES_OPEN_NORMAL,
+ G_PARAM_READWRITE|G_PARAM_STATIC_NAME|G_PARAM_STATIC_NICK|G_PARAM_STATIC_BLURB);
+ properties[PROP_SHOW_RECENT] =
+ g_param_spec_boolean ("show-recent",
+ "Show recent files",
+ "Whether the sidebar includes a builtin shortcut for recent files",
+ TRUE,
+ G_PARAM_READWRITE|G_PARAM_STATIC_NAME|G_PARAM_STATIC_NICK|G_PARAM_STATIC_BLURB);
+ properties[PROP_SHOW_DESKTOP] =
+ g_param_spec_boolean ("show-desktop",
+ "Show “Desktop”",
+ "Whether the sidebar includes a builtin shortcut to the Desktop folder",
+ TRUE,
+ G_PARAM_READWRITE|G_PARAM_STATIC_NAME|G_PARAM_STATIC_NICK|G_PARAM_STATIC_BLURB);
+ properties[PROP_SHOW_ENTER_LOCATION] =
+ g_param_spec_boolean ("show-enter-location",
+ "Show “Enter Location”",
+ "Whether the sidebar includes a builtin shortcut to manually enter a location",
+ FALSE,
+ G_PARAM_READWRITE|G_PARAM_STATIC_NAME|G_PARAM_STATIC_NICK|G_PARAM_STATIC_BLURB);
+ properties[PROP_SHOW_TRASH] =
+ g_param_spec_boolean ("show-trash",
+ "Show “Trash”",
+ "Whether the sidebar includes a builtin shortcut to the Trash location",
+ TRUE,
+ G_PARAM_READWRITE|G_PARAM_STATIC_NAME|G_PARAM_STATIC_NICK|G_PARAM_STATIC_BLURB);
+ properties[PROP_SHOW_OTHER_LOCATIONS] =
+ g_param_spec_boolean ("show-other-locations",
+ "Show “Other locations”",
+ "Whether the sidebar includes an item to show external locations",
+ TRUE,
+ G_PARAM_READWRITE|G_PARAM_STATIC_NAME|G_PARAM_STATIC_NICK|G_PARAM_STATIC_BLURB);
+ properties[PROP_SHOW_STARRED_LOCATION] =
+ g_param_spec_boolean ("show-starred-location",
+ "Show “Starred Location”",
+ "Whether the sidebar includes an item to show starred files",
+ FALSE,
+ G_PARAM_READWRITE|G_PARAM_STATIC_NAME|G_PARAM_STATIC_NICK|G_PARAM_STATIC_BLURB);
+
+ g_object_class_install_properties (gobject_class, NUM_PROPERTIES, properties);
+
+ gtk_widget_class_set_css_name (widget_class, "placessidebar");
+ gtk_widget_class_set_accessible_role (widget_class, GTK_ACCESSIBLE_ROLE_LIST);
+}
+
+/*
+ * nautilus_gtk_places_sidebar_new:
+ *
+ * Creates a new NautilusGtkPlacesSidebar widget.
+ *
+ * The application should connect to at least the
+ * NautilusGtkPlacesSidebar::open-location signal to be notified
+ * when the user makes a selection in the sidebar.
+ *
+ * Returns: a newly created NautilusGtkPlacesSidebar
+ */
+GtkWidget *
+nautilus_gtk_places_sidebar_new (void)
+{
+ return GTK_WIDGET (g_object_new (nautilus_gtk_places_sidebar_get_type (), NULL));
+}
+
+/*
+ * nautilus_gtk_places_sidebar_set_open_flags:
+ * @sidebar: a places sidebar
+ * @flags: Bitmask of modes in which the calling application can open locations
+ *
+ * Sets the way in which the calling application can open new locations from
+ * the places sidebar. For example, some applications only open locations
+ * “directly” into their main view, while others may support opening locations
+ * in a new notebook tab or a new window.
+ *
+ * This function is used to tell the places @sidebar about the ways in which the
+ * application can open new locations, so that the sidebar can display (or not)
+ * the “Open in new tab” and “Open in new window” menu items as appropriate.
+ *
+ * When the NautilusGtkPlacesSidebar::open-location signal is emitted, its flags
+ * argument will be set to one of the @flags that was passed in
+ * nautilus_gtk_places_sidebar_set_open_flags().
+ *
+ * Passing 0 for @flags will cause NAUTILUS_GTK_PLACES_OPEN_NORMAL to always be sent
+ * to callbacks for the “open-location” signal.
+ */
+void
+nautilus_gtk_places_sidebar_set_open_flags (NautilusGtkPlacesSidebar *sidebar,
+ NautilusGtkPlacesOpenFlags flags)
+{
+ g_return_if_fail (NAUTILUS_IS_GTK_PLACES_SIDEBAR (sidebar));
+
+ if (sidebar->open_flags != flags)
+ {
+ sidebar->open_flags = flags;
+ g_object_notify_by_pspec (G_OBJECT (sidebar), properties[PROP_OPEN_FLAGS]);
+ }
+}
+
+/*
+ * nautilus_gtk_places_sidebar_get_open_flags:
+ * @sidebar: a NautilusGtkPlacesSidebar
+ *
+ * Gets the open flags.
+ *
+ * Returns: the NautilusGtkPlacesOpenFlags of @sidebar
+ */
+NautilusGtkPlacesOpenFlags
+nautilus_gtk_places_sidebar_get_open_flags (NautilusGtkPlacesSidebar *sidebar)
+{
+ g_return_val_if_fail (NAUTILUS_IS_GTK_PLACES_SIDEBAR (sidebar), 0);
+
+ return sidebar->open_flags;
+}
+
+/*
+ * nautilus_gtk_places_sidebar_set_location:
+ * @sidebar: a places sidebar
+ * @location: (nullable): location to select, or %NULL for no current path
+ *
+ * Sets the location that is being shown in the widgets surrounding the
+ * @sidebar, for example, in a folder view in a file manager. In turn, the
+ * @sidebar will highlight that location if it is being shown in the list of
+ * places, or it will unhighlight everything if the @location is not among the
+ * places in the list.
+ */
+void
+nautilus_gtk_places_sidebar_set_location (NautilusGtkPlacesSidebar *sidebar,
+ GFile *location)
+{
+ GtkWidget *row;
+ char *row_uri;
+ char *uri;
+ gboolean found = FALSE;
+
+ g_return_if_fail (NAUTILUS_IS_GTK_PLACES_SIDEBAR (sidebar));
+
+ gtk_list_box_unselect_all (GTK_LIST_BOX (sidebar->list_box));
+
+ if (sidebar->current_location != NULL)
+ g_object_unref (sidebar->current_location);
+ sidebar->current_location = location;
+ if (sidebar->current_location != NULL)
+ g_object_ref (sidebar->current_location);
+
+ if (location == NULL)
+ goto out;
+
+ uri = g_file_get_uri (location);
+
+ for (row = gtk_widget_get_first_child (GTK_WIDGET (sidebar->list_box));
+ row != NULL && !found;
+ row = gtk_widget_get_next_sibling (row))
+ {
+ if (!GTK_IS_LIST_BOX_ROW (row))
+ continue;
+
+ g_object_get (row, "uri", &row_uri, NULL);
+ if (row_uri != NULL && g_strcmp0 (row_uri, uri) == 0)
+ {
+ gtk_list_box_select_row (GTK_LIST_BOX (sidebar->list_box),
+ GTK_LIST_BOX_ROW (row));
+ found = TRUE;
+ }
+
+ g_free (row_uri);
+ }
+
+ g_free (uri);
+
+ out:
+ g_object_notify_by_pspec (G_OBJECT (sidebar), properties[PROP_LOCATION]);
+}
+
+/*
+ * nautilus_gtk_places_sidebar_get_location:
+ * @sidebar: a places sidebar
+ *
+ * Gets the currently selected location in the @sidebar. This can be %NULL when
+ * nothing is selected, for example, when nautilus_gtk_places_sidebar_set_location() has
+ * been called with a location that is not among the sidebar’s list of places to
+ * show.
+ *
+ * You can use this function to get the selection in the @sidebar.
+ *
+ * Returns: (nullable) (transfer full): a GFile with the selected location, or
+ * %NULL if nothing is visually selected.
+ */
+GFile *
+nautilus_gtk_places_sidebar_get_location (NautilusGtkPlacesSidebar *sidebar)
+{
+ GtkListBoxRow *selected;
+ GFile *file;
+
+ g_return_val_if_fail (sidebar != NULL, NULL);
+
+ file = NULL;
+ selected = gtk_list_box_get_selected_row (GTK_LIST_BOX (sidebar->list_box));
+
+ if (selected)
+ {
+ char *uri;
+
+ g_object_get (selected, "uri", &uri, NULL);
+ file = g_file_new_for_uri (uri);
+ g_free (uri);
+ }
+
+ return file;
+}
+
+char *
+nautilus_gtk_places_sidebar_get_location_title (NautilusGtkPlacesSidebar *sidebar)
+{
+ GtkListBoxRow *selected;
+ char *title;
+
+ g_return_val_if_fail (sidebar != NULL, NULL);
+
+ title = NULL;
+ selected = gtk_list_box_get_selected_row (GTK_LIST_BOX (sidebar->list_box));
+
+ if (selected)
+ g_object_get (selected, "label", &title, NULL);
+
+ return title;
+}
+
+/*
+ * nautilus_gtk_places_sidebar_set_show_recent:
+ * @sidebar: a places sidebar
+ * @show_recent: whether to show an item for recent files
+ *
+ * Sets whether the @sidebar should show an item for recent files.
+ * The default value for this option is determined by the desktop
+ * environment, but this function can be used to override it on a
+ * per-application basis.
+ */
+void
+nautilus_gtk_places_sidebar_set_show_recent (NautilusGtkPlacesSidebar *sidebar,
+ gboolean show_recent)
+{
+ g_return_if_fail (NAUTILUS_IS_GTK_PLACES_SIDEBAR (sidebar));
+
+ sidebar->show_recent_set = TRUE;
+
+ show_recent = !!show_recent;
+ if (sidebar->show_recent != show_recent)
+ {
+ sidebar->show_recent = show_recent;
+ update_places (sidebar);
+ g_object_notify_by_pspec (G_OBJECT (sidebar), properties[PROP_SHOW_RECENT]);
+ }
+}
+
+/*
+ * nautilus_gtk_places_sidebar_get_show_recent:
+ * @sidebar: a places sidebar
+ *
+ * Returns the value previously set with nautilus_gtk_places_sidebar_set_show_recent()
+ *
+ * Returns: %TRUE if the sidebar will display a builtin shortcut for recent files
+ */
+gboolean
+nautilus_gtk_places_sidebar_get_show_recent (NautilusGtkPlacesSidebar *sidebar)
+{
+ g_return_val_if_fail (NAUTILUS_IS_GTK_PLACES_SIDEBAR (sidebar), FALSE);
+
+ return sidebar->show_recent;
+}
+
+/*
+ * nautilus_gtk_places_sidebar_set_show_desktop:
+ * @sidebar: a places sidebar
+ * @show_desktop: whether to show an item for the Desktop folder
+ *
+ * Sets whether the @sidebar should show an item for the Desktop folder.
+ * The default value for this option is determined by the desktop
+ * environment and the user’s configuration, but this function can be
+ * used to override it on a per-application basis.
+ */
+void
+nautilus_gtk_places_sidebar_set_show_desktop (NautilusGtkPlacesSidebar *sidebar,
+ gboolean show_desktop)
+{
+ g_return_if_fail (NAUTILUS_IS_GTK_PLACES_SIDEBAR (sidebar));
+
+ /* Don't bother disconnecting from the GtkSettings -- it will just
+ * complicate things. Besides, it's highly unlikely that this will
+ * change while we're running, but we can ignore it if it does.
+ */
+ sidebar->show_desktop_set = TRUE;
+
+ show_desktop = !!show_desktop;
+ if (sidebar->show_desktop != show_desktop)
+ {
+ sidebar->show_desktop = show_desktop;
+ update_places (sidebar);
+ g_object_notify_by_pspec (G_OBJECT (sidebar), properties[PROP_SHOW_DESKTOP]);
+ }
+}
+
+/*
+ * nautilus_gtk_places_sidebar_get_show_desktop:
+ * @sidebar: a places sidebar
+ *
+ * Returns the value previously set with nautilus_gtk_places_sidebar_set_show_desktop()
+ *
+ * Returns: %TRUE if the sidebar will display a builtin shortcut to the desktop folder.
+ */
+gboolean
+nautilus_gtk_places_sidebar_get_show_desktop (NautilusGtkPlacesSidebar *sidebar)
+{
+ g_return_val_if_fail (NAUTILUS_IS_GTK_PLACES_SIDEBAR (sidebar), FALSE);
+
+ return sidebar->show_desktop;
+}
+
+/*
+ * nautilus_gtk_places_sidebar_set_show_enter_location:
+ * @sidebar: a places sidebar
+ * @show_enter_location: whether to show an item to enter a location
+ *
+ * Sets whether the @sidebar should show an item for entering a location;
+ * this is off by default. An application may want to turn this on if manually
+ * entering URLs is an expected user action.
+ *
+ * If you enable this, you should connect to the
+ * NautilusGtkPlacesSidebar::show-enter-location signal.
+ */
+void
+nautilus_gtk_places_sidebar_set_show_enter_location (NautilusGtkPlacesSidebar *sidebar,
+ gboolean show_enter_location)
+{
+ g_return_if_fail (NAUTILUS_IS_GTK_PLACES_SIDEBAR (sidebar));
+
+ show_enter_location = !!show_enter_location;
+ if (sidebar->show_enter_location != show_enter_location)
+ {
+ sidebar->show_enter_location = show_enter_location;
+ update_places (sidebar);
+ g_object_notify_by_pspec (G_OBJECT (sidebar), properties[PROP_SHOW_ENTER_LOCATION]);
+ }
+}
+
+/*
+ * nautilus_gtk_places_sidebar_get_show_enter_location:
+ * @sidebar: a places sidebar
+ *
+ * Returns the value previously set with nautilus_gtk_places_sidebar_set_show_enter_location()
+ *
+ * Returns: %TRUE if the sidebar will display an “Enter Location” item.
+ */
+gboolean
+nautilus_gtk_places_sidebar_get_show_enter_location (NautilusGtkPlacesSidebar *sidebar)
+{
+ g_return_val_if_fail (NAUTILUS_IS_GTK_PLACES_SIDEBAR (sidebar), FALSE);
+
+ return sidebar->show_enter_location;
+}
+
+/*
+ * nautilus_gtk_places_sidebar_set_show_other_locations:
+ * @sidebar: a places sidebar
+ * @show_other_locations: whether to show an item for the Other Locations view
+ *
+ * Sets whether the @sidebar should show an item for the application to show
+ * an Other Locations view; this is off by default. When set to %TRUE, persistent
+ * devices such as hard drives are hidden, otherwise they are shown in the sidebar.
+ * An application may want to turn this on if it implements a way for the user to
+ * see and interact with drives and network servers directly.
+ *
+ * If you enable this, you should connect to the
+ * NautilusGtkPlacesSidebar::show-other-locations-with-flags signal.
+ */
+void
+nautilus_gtk_places_sidebar_set_show_other_locations (NautilusGtkPlacesSidebar *sidebar,
+ gboolean show_other_locations)
+{
+ g_return_if_fail (NAUTILUS_IS_GTK_PLACES_SIDEBAR (sidebar));
+
+ show_other_locations = !!show_other_locations;
+ if (sidebar->show_other_locations != show_other_locations)
+ {
+ sidebar->show_other_locations = show_other_locations;
+ update_places (sidebar);
+ g_object_notify_by_pspec (G_OBJECT (sidebar), properties[PROP_SHOW_OTHER_LOCATIONS]);
+ }
+ }
+
+/*
+ * nautilus_gtk_places_sidebar_get_show_other_locations:
+ * @sidebar: a places sidebar
+ *
+ * Returns the value previously set with nautilus_gtk_places_sidebar_set_show_other_locations()
+ *
+ * Returns: %TRUE if the sidebar will display an “Other Locations” item.
+ */
+gboolean
+nautilus_gtk_places_sidebar_get_show_other_locations (NautilusGtkPlacesSidebar *sidebar)
+{
+ g_return_val_if_fail (NAUTILUS_IS_GTK_PLACES_SIDEBAR (sidebar), FALSE);
+
+ return sidebar->show_other_locations;
+}
+
+/*
+ * nautilus_gtk_places_sidebar_set_show_trash:
+ * @sidebar: a places sidebar
+ * @show_trash: whether to show an item for the Trash location
+ *
+ * Sets whether the @sidebar should show an item for the Trash location.
+ */
+void
+nautilus_gtk_places_sidebar_set_show_trash (NautilusGtkPlacesSidebar *sidebar,
+ gboolean show_trash)
+{
+ g_return_if_fail (NAUTILUS_IS_GTK_PLACES_SIDEBAR (sidebar));
+
+ show_trash = !!show_trash;
+ if (sidebar->show_trash != show_trash)
+ {
+ sidebar->show_trash = show_trash;
+ update_places (sidebar);
+ g_object_notify_by_pspec (G_OBJECT (sidebar), properties[PROP_SHOW_TRASH]);
+ }
+}
+
+/*
+ * nautilus_gtk_places_sidebar_get_show_trash:
+ * @sidebar: a places sidebar
+ *
+ * Returns the value previously set with nautilus_gtk_places_sidebar_set_show_trash()
+ *
+ * Returns: %TRUE if the sidebar will display a “Trash” item.
+ */
+gboolean
+nautilus_gtk_places_sidebar_get_show_trash (NautilusGtkPlacesSidebar *sidebar)
+{
+ g_return_val_if_fail (NAUTILUS_IS_GTK_PLACES_SIDEBAR (sidebar), TRUE);
+
+ return sidebar->show_trash;
+}
+
+/*
+ * nautilus_gtk_places_sidebar_add_shortcut:
+ * @sidebar: a places sidebar
+ * @location: location to add as an application-specific shortcut
+ *
+ * Applications may want to present some folders in the places sidebar if
+ * they could be immediately useful to users. For example, a drawing
+ * program could add a “/usr/share/clipart” location when the sidebar is
+ * being used in an “Insert Clipart” dialog box.
+ *
+ * This function adds the specified @location to a special place for immutable
+ * shortcuts. The shortcuts are application-specific; they are not shared
+ * across applications, and they are not persistent. If this function
+ * is called multiple times with different locations, then they are added
+ * to the sidebar’s list in the same order as the function is called.
+ */
+void
+nautilus_gtk_places_sidebar_add_shortcut (NautilusGtkPlacesSidebar *sidebar,
+ GFile *location)
+{
+ g_return_if_fail (NAUTILUS_IS_GTK_PLACES_SIDEBAR (sidebar));
+ g_return_if_fail (G_IS_FILE (location));
+
+ g_list_store_append (sidebar->shortcuts, location);
+
+ update_places (sidebar);
+}
+
+/*
+ * nautilus_gtk_places_sidebar_remove_shortcut:
+ * @sidebar: a places sidebar
+ * @location: location to remove
+ *
+ * Removes an application-specific shortcut that has been previously been
+ * inserted with nautilus_gtk_places_sidebar_add_shortcut(). If the @location is not a
+ * shortcut in the sidebar, then nothing is done.
+ */
+void
+nautilus_gtk_places_sidebar_remove_shortcut (NautilusGtkPlacesSidebar *sidebar,
+ GFile *location)
+{
+ guint i, n;
+
+ g_return_if_fail (NAUTILUS_IS_GTK_PLACES_SIDEBAR (sidebar));
+ g_return_if_fail (G_IS_FILE (location));
+
+ n = g_list_model_get_n_items (G_LIST_MODEL (sidebar->shortcuts));
+ for (i = 0; i < n; i++)
+ {
+ GFile *shortcut = g_list_model_get_item (G_LIST_MODEL (sidebar->shortcuts), i);
+
+ if (shortcut == location)
+ {
+ g_list_store_remove (sidebar->shortcuts, i);
+ g_object_unref (shortcut);
+ update_places (sidebar);
+ return;
+ }
+
+ g_object_unref (shortcut);
+ }
+}
+
+/*
+ * nautilus_gtk_places_sidebar_list_shortcuts:
+ * @sidebar: a places sidebar
+ *
+ * Gets the list of shortcuts, as a list model containing GFile objects.
+ *
+ * You should not modify the returned list model. Future changes to
+ * @sidebar may or may not affect the returned model.
+ *
+ * Returns: (transfer full): a list model of GFiles that have been added as
+ * application-specific shortcuts with nautilus_gtk_places_sidebar_add_shortcut()
+ */
+GListModel *
+nautilus_gtk_places_sidebar_get_shortcuts (NautilusGtkPlacesSidebar *sidebar)
+{
+ g_return_val_if_fail (NAUTILUS_IS_GTK_PLACES_SIDEBAR (sidebar), NULL);
+
+ return G_LIST_MODEL (g_object_ref (sidebar->shortcuts));
+}
+
+/*
+ * nautilus_gtk_places_sidebar_get_nth_bookmark:
+ * @sidebar: a places sidebar
+ * @n: index of the bookmark to query
+ *
+ * This function queries the bookmarks added by the user to the places sidebar,
+ * and returns one of them. This function is used by GtkFileChooser to implement
+ * the “Alt-1”, “Alt-2”, etc. shortcuts, which activate the corresponding bookmark.
+ *
+ * Returns: (nullable) (transfer full): The bookmark specified by the index @n, or
+ * %NULL if no such index exist. Note that the indices start at 0, even though
+ * the file chooser starts them with the keyboard shortcut "Alt-1".
+ */
+GFile *
+nautilus_gtk_places_sidebar_get_nth_bookmark (NautilusGtkPlacesSidebar *sidebar,
+ int n)
+{
+ GtkWidget *row;
+ int k;
+ GFile *file;
+
+ g_return_val_if_fail (NAUTILUS_IS_GTK_PLACES_SIDEBAR (sidebar), NULL);
+
+ file = NULL;
+ k = 0;
+ for (row = gtk_widget_get_first_child (GTK_WIDGET (sidebar->list_box));
+ row != NULL;
+ row = gtk_widget_get_next_sibling (row))
+ {
+ NautilusGtkPlacesPlaceType place_type;
+ char *uri;
+
+ if (!GTK_IS_LIST_BOX_ROW (row))
+ continue;
+
+ g_object_get (row,
+ "place-type", &place_type,
+ "uri", &uri,
+ NULL);
+ if (place_type == NAUTILUS_GTK_PLACES_BOOKMARK)
+ {
+ if (k == n)
+ {
+ file = g_file_new_for_uri (uri);
+ g_free (uri);
+ break;
+ }
+ k++;
+ }
+ g_free (uri);
+ }
+
+ return file;
+}
+
+/*
+ * nautilus_gtk_places_sidebar_set_drop_targets_visible:
+ * @sidebar: a places sidebar.
+ * @visible: whether to show the valid targets or not.
+ *
+ * Make the NautilusGtkPlacesSidebar show drop targets, so it can show the available
+ * drop targets and a "new bookmark" row. This improves the Drag-and-Drop
+ * experience of the user and allows applications to show all available
+ * drop targets at once.
+ *
+ * This needs to be called when the application is aware of an ongoing drag
+ * that might target the sidebar. The drop-targets-visible state will be unset
+ * automatically if the drag finishes in the NautilusGtkPlacesSidebar. You only need
+ * to unset the state when the drag ends on some other widget on your application.
+ */
+void
+nautilus_gtk_places_sidebar_set_drop_targets_visible (NautilusGtkPlacesSidebar *sidebar,
+ gboolean visible)
+{
+ g_return_if_fail (NAUTILUS_IS_GTK_PLACES_SIDEBAR (sidebar));
+
+ if (visible)
+ {
+ sidebar->drop_state = DROP_STATE_NEW_BOOKMARK_ARMED_PERMANENT;
+ start_drop_feedback (sidebar, NULL);
+ }
+ else
+ {
+ if (sidebar->drop_state == DROP_STATE_NEW_BOOKMARK_ARMED_PERMANENT ||
+ sidebar->drop_state == DROP_STATE_NEW_BOOKMARK_ARMED)
+ {
+ if (!sidebar->dragging_over)
+ {
+ sidebar->drop_state = DROP_STATE_NORMAL;
+ stop_drop_feedback (sidebar);
+ }
+ else
+ {
+ /* In case this is called while we are dragging we need to mark the
+ * drop state as no permanent so the leave timeout can do its job.
+ * This will only happen in applications that call this in a wrong
+ * time */
+ sidebar->drop_state = DROP_STATE_NEW_BOOKMARK_ARMED;
+ }
+ }
+ }
+}
+
+/*
+ * nautilus_gtk_places_sidebar_set_show_starred_location:
+ * @sidebar: a places sidebar
+ * @show_starred_location: whether to show an item for Starred files
+ *
+ * If you enable this, you should connect to the
+ * NautilusGtkPlacesSidebar::show-starred-location signal.
+ */
+void
+nautilus_gtk_places_sidebar_set_show_starred_location (NautilusGtkPlacesSidebar *sidebar,
+ gboolean show_starred_location)
+{
+ g_return_if_fail (NAUTILUS_IS_GTK_PLACES_SIDEBAR (sidebar));
+
+ show_starred_location = !!show_starred_location;
+ if (sidebar->show_starred_location != show_starred_location)
+ {
+ sidebar->show_starred_location = show_starred_location;
+ update_places (sidebar);
+ g_object_notify_by_pspec (G_OBJECT (sidebar), properties[PROP_SHOW_STARRED_LOCATION]);
+ }
+}
+
+/*
+ * nautilus_gtk_places_sidebar_get_show_starred_location:
+ * @sidebar: a places sidebar
+ *
+ * Returns the value previously set with nautilus_gtk_places_sidebar_set_show_starred_location()
+ *
+ * Returns: %TRUE if the sidebar will display a Starred item.
+ */
+gboolean
+nautilus_gtk_places_sidebar_get_show_starred_location (NautilusGtkPlacesSidebar *sidebar)
+{
+ g_return_val_if_fail (NAUTILUS_IS_GTK_PLACES_SIDEBAR (sidebar), FALSE);
+
+ return sidebar->show_starred_location;
+}
diff --git a/src/gtk/nautilusgtkplacessidebarprivate.h b/src/gtk/nautilusgtkplacessidebarprivate.h
new file mode 100644
index 0000000..c1503ad
--- /dev/null
+++ b/src/gtk/nautilusgtkplacessidebarprivate.h
@@ -0,0 +1,148 @@
+/* nautilusgtkplacessidebarprivate.h
+ *
+ * Copyright (C) 2015 Red Hat
+ *
+ * This file is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of the
+ * License, or (at your option) any later version.
+ *
+ * This file 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Carlos Soriano <csoriano@gnome.org>
+ */
+
+#ifndef __NAUTILUS_GTK_PLACES_SIDEBAR_PRIVATE_H__
+#define __NAUTILUS_GTK_PLACES_SIDEBAR_PRIVATE_H__
+
+#include <glib.h>
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+#define NAUTILUS_TYPE_GTK_PLACES_SIDEBAR (nautilus_gtk_places_sidebar_get_type ())
+#define NAUTILUS_GTK_PLACES_SIDEBAR(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), NAUTILUS_TYPE_GTK_PLACES_SIDEBAR, NautilusGtkPlacesSidebar))
+#define NAUTILUS_GTK_PLACES_SIDEBAR_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), NAUTILUS_TYPE_GTK_PLACES_SIDEBAR, NautilusGtkPlacesSidebarClass))
+#define NAUTILUS_IS_GTK_PLACES_SIDEBAR(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), NAUTILUS_TYPE_GTK_PLACES_SIDEBAR))
+#define NAUTILUS_IS_GTK_PLACES_SIDEBAR_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), NAUTILUS_TYPE_GTK_PLACES_SIDEBAR))
+#define NAUTILUS_GTK_PLACES_SIDEBAR_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), NAUTILUS_TYPE_GTK_PLACES_SIDEBAR, NautilusGtkPlacesSidebarClass))
+
+typedef struct _NautilusGtkPlacesSidebar NautilusGtkPlacesSidebar;
+typedef struct _NautilusGtkPlacesSidebarClass NautilusGtkPlacesSidebarClass;
+
+/*
+ * NautilusGtkPlacesOpenFlags:
+ * @NAUTILUS_GTK_PLACES_OPEN_NORMAL: This is the default mode that NautilusGtkPlacesSidebar uses if no other flags
+ * are specified. It indicates that the calling application should open the selected location
+ * in the normal way, for example, in the folder view beside the sidebar.
+ * @NAUTILUS_GTK_PLACES_OPEN_NEW_TAB: When passed to nautilus_gtk_places_sidebar_set_open_flags(), this indicates
+ * that the application can open folders selected from the sidebar in new tabs. This value
+ * will be passed to the NautilusGtkPlacesSidebar::open-location signal when the user selects
+ * that a location be opened in a new tab instead of in the standard fashion.
+ * @NAUTILUS_GTK_PLACES_OPEN_NEW_WINDOW: Similar to @NAUTILUS_GTK_PLACES_OPEN_NEW_TAB, but indicates that the application
+ * can open folders in new windows.
+ *
+ * These flags serve two purposes. First, the application can call nautilus_gtk_places_sidebar_set_open_flags()
+ * using these flags as a bitmask. This tells the sidebar that the application is able to open
+ * folders selected from the sidebar in various ways, for example, in new tabs or in new windows in
+ * addition to the normal mode.
+ *
+ * Second, when one of these values gets passed back to the application in the
+ * NautilusGtkPlacesSidebar::open-location signal, it means that the application should
+ * open the selected location in the normal way, in a new tab, or in a new
+ * window. The sidebar takes care of determining the desired way to open the location,
+ * based on the modifier keys that the user is pressing at the time the selection is made.
+ *
+ * If the application never calls nautilus_gtk_places_sidebar_set_open_flags(), then the sidebar will only
+ * use NAUTILUS_GTK_PLACES_OPEN_NORMAL in the NautilusGtkPlacesSidebar::open-location signal. This is the
+ * default mode of operation.
+ */
+typedef enum {
+ NAUTILUS_GTK_PLACES_OPEN_NORMAL = 1 << 0,
+ NAUTILUS_GTK_PLACES_OPEN_NEW_TAB = 1 << 1,
+ NAUTILUS_GTK_PLACES_OPEN_NEW_WINDOW = 1 << 2
+} NautilusGtkPlacesOpenFlags;
+
+GType nautilus_gtk_places_sidebar_get_type (void) G_GNUC_CONST;
+GtkWidget * nautilus_gtk_places_sidebar_new (void);
+
+NautilusGtkPlacesOpenFlags nautilus_gtk_places_sidebar_get_open_flags (NautilusGtkPlacesSidebar *sidebar);
+void nautilus_gtk_places_sidebar_set_open_flags (NautilusGtkPlacesSidebar *sidebar,
+ NautilusGtkPlacesOpenFlags flags);
+
+GFile * nautilus_gtk_places_sidebar_get_location (NautilusGtkPlacesSidebar *sidebar);
+void nautilus_gtk_places_sidebar_set_location (NautilusGtkPlacesSidebar *sidebar,
+ GFile *location);
+
+gboolean nautilus_gtk_places_sidebar_get_show_recent (NautilusGtkPlacesSidebar *sidebar);
+void nautilus_gtk_places_sidebar_set_show_recent (NautilusGtkPlacesSidebar *sidebar,
+ gboolean show_recent);
+
+gboolean nautilus_gtk_places_sidebar_get_show_desktop (NautilusGtkPlacesSidebar *sidebar);
+void nautilus_gtk_places_sidebar_set_show_desktop (NautilusGtkPlacesSidebar *sidebar,
+ gboolean show_desktop);
+
+gboolean nautilus_gtk_places_sidebar_get_show_enter_location (NautilusGtkPlacesSidebar *sidebar);
+void nautilus_gtk_places_sidebar_set_show_enter_location (NautilusGtkPlacesSidebar *sidebar,
+ gboolean show_enter_location);
+
+void nautilus_gtk_places_sidebar_add_shortcut (NautilusGtkPlacesSidebar *sidebar,
+ GFile *location);
+void nautilus_gtk_places_sidebar_remove_shortcut (NautilusGtkPlacesSidebar *sidebar,
+ GFile *location);
+GListModel * nautilus_gtk_places_sidebar_get_shortcuts (NautilusGtkPlacesSidebar *sidebar);
+
+GFile * nautilus_gtk_places_sidebar_get_nth_bookmark (NautilusGtkPlacesSidebar *sidebar,
+ int n);
+void nautilus_gtk_places_sidebar_set_drop_targets_visible (NautilusGtkPlacesSidebar *sidebar,
+ gboolean visible);
+gboolean nautilus_gtk_places_sidebar_get_show_trash (NautilusGtkPlacesSidebar *sidebar);
+void nautilus_gtk_places_sidebar_set_show_trash (NautilusGtkPlacesSidebar *sidebar,
+ gboolean show_trash);
+
+void nautilus_gtk_places_sidebar_set_show_other_locations (NautilusGtkPlacesSidebar *sidebar,
+ gboolean show_other_locations);
+gboolean nautilus_gtk_places_sidebar_get_show_other_locations (NautilusGtkPlacesSidebar *sidebar);
+
+void nautilus_gtk_places_sidebar_set_show_starred_location (NautilusGtkPlacesSidebar *sidebar,
+ gboolean show_starred_location);
+gboolean nautilus_gtk_places_sidebar_get_show_starred_location (NautilusGtkPlacesSidebar *sidebar);
+
+/* Keep order, since it's used for the sort functions */
+typedef enum {
+ NAUTILUS_GTK_PLACES_SECTION_INVALID,
+ NAUTILUS_GTK_PLACES_SECTION_COMPUTER,
+ NAUTILUS_GTK_PLACES_SECTION_MOUNTS,
+ NAUTILUS_GTK_PLACES_SECTION_CLOUD,
+ NAUTILUS_GTK_PLACES_SECTION_BOOKMARKS,
+ NAUTILUS_GTK_PLACES_SECTION_OTHER_LOCATIONS,
+ NAUTILUS_GTK_PLACES_N_SECTIONS
+} NautilusGtkPlacesSectionType;
+
+typedef enum {
+ NAUTILUS_GTK_PLACES_INVALID,
+ NAUTILUS_GTK_PLACES_BUILT_IN,
+ NAUTILUS_GTK_PLACES_XDG_DIR,
+ NAUTILUS_GTK_PLACES_MOUNTED_VOLUME,
+ NAUTILUS_GTK_PLACES_BOOKMARK,
+ NAUTILUS_GTK_PLACES_HEADING,
+ NAUTILUS_GTK_PLACES_CONNECT_TO_SERVER,
+ NAUTILUS_GTK_PLACES_ENTER_LOCATION,
+ NAUTILUS_GTK_PLACES_DROP_FEEDBACK,
+ NAUTILUS_GTK_PLACES_BOOKMARK_PLACEHOLDER,
+ NAUTILUS_GTK_PLACES_OTHER_LOCATIONS,
+ NAUTILUS_GTK_PLACES_STARRED_LOCATION,
+ NAUTILUS_GTK_PLACES_N_PLACES
+} NautilusGtkPlacesPlaceType;
+
+char *nautilus_gtk_places_sidebar_get_location_title (NautilusGtkPlacesSidebar *sidebar);
+
+G_END_DECLS
+
+#endif /* __NAUTILUS_GTK_PLACES_SIDEBAR_PRIVATE_H__ */
diff --git a/src/gtk/nautilusgtkplacesview.c b/src/gtk/nautilusgtkplacesview.c
new file mode 100644
index 0000000..0d062c9
--- /dev/null
+++ b/src/gtk/nautilusgtkplacesview.c
@@ -0,0 +1,2635 @@
+/* nautilusgtkplacesview.c
+ *
+ * Copyright (C) 2015 Georges Basile Stavracas Neto <georges.stavracas@gmail.com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 2.1 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 Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+#include <glib/gi18n.h>
+#include <gtk/gtk.h>
+#include "nautilus-enum-types.h"
+
+#include <gio/gio.h>
+#include <gio/gvfs.h>
+#include <gtk/gtk.h>
+
+#include "nautilusgtkplacesviewprivate.h"
+#include "nautilusgtkplacesviewrowprivate.h"
+#include "nautilus-file.h"
+#include "nautilus-file-utilities.h"
+#include "nautilus-properties-window.h"
+
+/*< private >
+ * NautilusGtkPlacesView:
+ *
+ * NautilusGtkPlacesView is a widget that displays a list of persistent drives
+ * such as harddisk partitions and networks. NautilusGtkPlacesView does not monitor
+ * removable devices.
+ *
+ * The places view displays drives and networks, and will automatically mount
+ * them when the user activates. Network addresses are stored even if they fail
+ * to connect. When the connection is successful, the connected network is
+ * shown at the network list.
+ *
+ * To make use of the places view, an application at least needs to connect
+ * to the NautilusGtkPlacesView::open-location signal. This is emitted when the user
+ * selects a location to open in the view.
+ */
+
+struct _NautilusGtkPlacesViewClass
+{
+ GtkBoxClass parent_class;
+
+ void (* open_location) (NautilusGtkPlacesView *view,
+ GFile *location,
+ NautilusGtkPlacesOpenFlags open_flags);
+
+ void (* show_error_message) (NautilusGtkPlacesSidebar *sidebar,
+ const char *primary,
+ const char *secondary);
+};
+
+struct _NautilusGtkPlacesView
+{
+ GtkBox parent_instance;
+
+ GVolumeMonitor *volume_monitor;
+ NautilusGtkPlacesOpenFlags open_flags;
+ NautilusGtkPlacesOpenFlags current_open_flags;
+
+ GFile *server_list_file;
+ GFileMonitor *server_list_monitor;
+ GFileMonitor *network_monitor;
+
+ GCancellable *cancellable;
+
+ char *search_query;
+
+ GtkWidget *actionbar;
+ GtkWidget *address_entry;
+ GtkWidget *connect_button;
+ GtkWidget *listbox;
+ GtkWidget *popup_menu;
+ GtkWidget *recent_servers_listbox;
+ GtkWidget *recent_servers_popover;
+ GtkWidget *recent_servers_stack;
+ GtkWidget *stack;
+ GtkWidget *server_adresses_popover;
+ GtkWidget *available_protocols_grid;
+ GtkWidget *network_placeholder;
+ GtkWidget *network_placeholder_label;
+
+ GtkSizeGroup *path_size_group;
+ GtkSizeGroup *space_size_group;
+
+ GtkEntryCompletion *address_entry_completion;
+ GtkListStore *completion_store;
+
+ GCancellable *networks_fetching_cancellable;
+
+ NautilusGtkPlacesViewRow *row_for_action;
+
+ guint should_open_location : 1;
+ guint should_pulse_entry : 1;
+ guint entry_pulse_timeout_id;
+ guint connecting_to_server : 1;
+ guint mounting_volume : 1;
+ guint unmounting_mount : 1;
+ guint fetching_networks : 1;
+ guint loading : 1;
+ guint destroyed : 1;
+};
+
+static void mount_volume (NautilusGtkPlacesView *view,
+ GVolume *volume);
+
+static void on_eject_button_clicked (GtkWidget *widget,
+ NautilusGtkPlacesViewRow *row);
+
+static gboolean on_row_popup_menu (GtkWidget *widget,
+ GVariant *args,
+ gpointer user_data);
+
+static void click_cb (GtkGesture *gesture,
+ int n_press,
+ double x,
+ double y,
+ gpointer user_data);
+
+static void populate_servers (NautilusGtkPlacesView *view);
+
+static gboolean nautilus_gtk_places_view_get_fetching_networks (NautilusGtkPlacesView *view);
+
+static void nautilus_gtk_places_view_set_fetching_networks (NautilusGtkPlacesView *view,
+ gboolean fetching_networks);
+
+static void nautilus_gtk_places_view_set_loading (NautilusGtkPlacesView *view,
+ gboolean loading);
+
+static void update_loading (NautilusGtkPlacesView *view);
+
+G_DEFINE_TYPE (NautilusGtkPlacesView, nautilus_gtk_places_view, GTK_TYPE_BOX)
+
+/* NautilusGtkPlacesView properties & signals */
+enum {
+ PROP_0,
+ PROP_OPEN_FLAGS,
+ PROP_FETCHING_NETWORKS,
+ PROP_LOADING,
+ LAST_PROP
+};
+
+enum {
+ OPEN_LOCATION,
+ SHOW_ERROR_MESSAGE,
+ LAST_SIGNAL
+};
+
+const char *unsupported_protocols [] =
+{
+ "file", "afc", "obex", "http",
+ "trash", "burn", "computer",
+ "archive", "recent", "localtest",
+ NULL
+};
+
+static guint places_view_signals [LAST_SIGNAL] = { 0 };
+static GParamSpec *properties [LAST_PROP];
+
+static void
+emit_open_location (NautilusGtkPlacesView *view,
+ GFile *location,
+ NautilusGtkPlacesOpenFlags open_flags)
+{
+ if ((open_flags & view->open_flags) == 0)
+ open_flags = NAUTILUS_GTK_PLACES_OPEN_NORMAL;
+
+ g_signal_emit (view, places_view_signals[OPEN_LOCATION], 0, location, open_flags);
+}
+
+static void
+emit_show_error_message (NautilusGtkPlacesView *view,
+ char *primary_message,
+ char *secondary_message)
+{
+ g_signal_emit (view, places_view_signals[SHOW_ERROR_MESSAGE],
+ 0, primary_message, secondary_message);
+}
+
+static void
+server_file_changed_cb (NautilusGtkPlacesView *view)
+{
+ populate_servers (view);
+}
+
+static GBookmarkFile *
+server_list_load (NautilusGtkPlacesView *view)
+{
+ GBookmarkFile *bookmarks;
+ GError *error = NULL;
+ char *datadir;
+ char *filename;
+
+ bookmarks = g_bookmark_file_new ();
+ datadir = g_build_filename (g_get_user_config_dir (), "gtk-4.0", NULL);
+ filename = g_build_filename (datadir, "servers", NULL);
+
+ g_mkdir_with_parents (datadir, 0700);
+ g_bookmark_file_load_from_file (bookmarks, filename, &error);
+
+ if (error)
+ {
+ if (!g_error_matches (error, G_FILE_ERROR, G_FILE_ERROR_NOENT))
+ {
+ /* only warn if the file exists */
+ g_warning ("Unable to open server bookmarks: %s", error->message);
+ g_clear_pointer (&bookmarks, g_bookmark_file_free);
+ }
+
+ g_clear_error (&error);
+ }
+
+ /* Monitor the file in case it's modified outside this code */
+ if (!view->server_list_monitor)
+ {
+ view->server_list_file = g_file_new_for_path (filename);
+
+ if (view->server_list_file)
+ {
+ view->server_list_monitor = g_file_monitor_file (view->server_list_file,
+ G_FILE_MONITOR_NONE,
+ NULL,
+ &error);
+
+ if (error)
+ {
+ g_warning ("Cannot monitor server file: %s", error->message);
+ g_clear_error (&error);
+ }
+ else
+ {
+ g_signal_connect_swapped (view->server_list_monitor,
+ "changed",
+ G_CALLBACK (server_file_changed_cb),
+ view);
+ }
+ }
+
+ g_clear_object (&view->server_list_file);
+ }
+
+ g_free (datadir);
+ g_free (filename);
+
+ return bookmarks;
+}
+
+static void
+server_list_save (GBookmarkFile *bookmarks)
+{
+ char *filename;
+
+ filename = g_build_filename (g_get_user_config_dir (), "gtk-4.0", "servers", NULL);
+ g_bookmark_file_to_file (bookmarks, filename, NULL);
+ g_free (filename);
+}
+
+static void
+server_list_add_server (NautilusGtkPlacesView *view,
+ GFile *file)
+{
+ GBookmarkFile *bookmarks;
+ GFileInfo *info;
+ GError *error;
+ char *title;
+ char *uri;
+ GDateTime *now;
+
+ error = NULL;
+ bookmarks = server_list_load (view);
+
+ if (!bookmarks)
+ return;
+
+ uri = g_file_get_uri (file);
+
+ info = g_file_query_info (file,
+ G_FILE_ATTRIBUTE_STANDARD_DISPLAY_NAME,
+ G_FILE_QUERY_INFO_NONE,
+ NULL,
+ &error);
+ title = g_file_info_get_attribute_as_string (info, G_FILE_ATTRIBUTE_STANDARD_DISPLAY_NAME);
+
+ g_bookmark_file_set_title (bookmarks, uri, title);
+ now = g_date_time_new_now_utc ();
+ g_bookmark_file_set_visited_date_time (bookmarks, uri, now);
+ g_date_time_unref (now);
+ g_bookmark_file_add_application (bookmarks, uri, NULL, NULL);
+
+ server_list_save (bookmarks);
+
+ g_bookmark_file_free (bookmarks);
+ g_clear_object (&info);
+ g_free (title);
+ g_free (uri);
+}
+
+static void
+server_list_remove_server (NautilusGtkPlacesView *view,
+ const char *uri)
+{
+ GBookmarkFile *bookmarks;
+
+ bookmarks = server_list_load (view);
+
+ if (!bookmarks)
+ return;
+
+ g_bookmark_file_remove_item (bookmarks, uri, NULL);
+ server_list_save (bookmarks);
+
+ g_bookmark_file_free (bookmarks);
+}
+
+/* Returns a toplevel GtkWindow, or NULL if none */
+static GtkWindow *
+get_toplevel (GtkWidget *widget)
+{
+ GtkWidget *toplevel;
+
+ toplevel = GTK_WIDGET (gtk_widget_get_root (widget));
+ if (GTK_IS_WINDOW (toplevel))
+ return GTK_WINDOW (toplevel);
+ else
+ return NULL;
+}
+
+static void
+set_busy_cursor (NautilusGtkPlacesView *view,
+ gboolean busy)
+{
+ GtkWidget *widget;
+ GtkWindow *toplevel;
+
+ toplevel = get_toplevel (GTK_WIDGET (view));
+ widget = GTK_WIDGET (toplevel);
+ if (!toplevel || !gtk_widget_get_realized (widget))
+ return;
+
+ if (busy)
+ gtk_widget_set_cursor_from_name (widget, "progress");
+ else
+ gtk_widget_set_cursor (widget, NULL);
+}
+
+/* Activates the given row, with the given flags as parameter */
+static void
+activate_row (NautilusGtkPlacesView *view,
+ NautilusGtkPlacesViewRow *row,
+ NautilusGtkPlacesOpenFlags flags)
+{
+ GVolume *volume;
+ GMount *mount;
+ GFile *file;
+
+ mount = nautilus_gtk_places_view_row_get_mount (row);
+ volume = nautilus_gtk_places_view_row_get_volume (row);
+ file = nautilus_gtk_places_view_row_get_file (row);
+
+ if (file)
+ {
+ emit_open_location (view, file, flags);
+ }
+ else if (mount)
+ {
+ GFile *location = g_mount_get_default_location (mount);
+
+ emit_open_location (view, location, flags);
+
+ g_object_unref (location);
+ }
+ else if (volume && g_volume_can_mount (volume))
+ {
+ /*
+ * When the row is activated, the unmounted volume shall
+ * be mounted and opened right after.
+ */
+ view->should_open_location = TRUE;
+
+ nautilus_gtk_places_view_row_set_busy (row, TRUE);
+ mount_volume (view, volume);
+ }
+}
+
+static void update_places (NautilusGtkPlacesView *view);
+
+static void
+nautilus_gtk_places_view_finalize (GObject *object)
+{
+ NautilusGtkPlacesView *view = (NautilusGtkPlacesView *)object;
+
+ if (view->entry_pulse_timeout_id > 0)
+ g_source_remove (view->entry_pulse_timeout_id);
+
+ g_clear_pointer (&view->search_query, g_free);
+ g_clear_object (&view->server_list_file);
+ g_clear_object (&view->server_list_monitor);
+ g_clear_object (&view->volume_monitor);
+ g_clear_object (&view->network_monitor);
+ g_clear_object (&view->cancellable);
+ g_clear_object (&view->networks_fetching_cancellable);
+ g_clear_object (&view->path_size_group);
+ g_clear_object (&view->space_size_group);
+
+ G_OBJECT_CLASS (nautilus_gtk_places_view_parent_class)->finalize (object);
+}
+
+static void
+nautilus_gtk_places_view_dispose (GObject *object)
+{
+ NautilusGtkPlacesView *view = (NautilusGtkPlacesView *)object;
+
+ view->destroyed = 1;
+
+ g_signal_handlers_disconnect_by_func (view->volume_monitor, update_places, object);
+
+ if (view->network_monitor)
+ g_signal_handlers_disconnect_by_func (view->network_monitor, update_places, object);
+
+ if (view->server_list_monitor)
+ g_signal_handlers_disconnect_by_func (view->server_list_monitor, server_file_changed_cb, object);
+
+ g_cancellable_cancel (view->cancellable);
+ g_cancellable_cancel (view->networks_fetching_cancellable);
+ g_clear_pointer (&view->popup_menu, gtk_widget_unparent);
+
+ G_OBJECT_CLASS (nautilus_gtk_places_view_parent_class)->dispose (object);
+}
+
+static void
+nautilus_gtk_places_view_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ NautilusGtkPlacesView *self = NAUTILUS_GTK_PLACES_VIEW (object);
+
+ switch (prop_id)
+ {
+ case PROP_LOADING:
+ g_value_set_boolean (value, nautilus_gtk_places_view_get_loading (self));
+ break;
+
+ case PROP_OPEN_FLAGS:
+ g_value_set_flags (value, nautilus_gtk_places_view_get_open_flags (self));
+ break;
+
+ case PROP_FETCHING_NETWORKS:
+ g_value_set_boolean (value, nautilus_gtk_places_view_get_fetching_networks (self));
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+}
+
+static void
+nautilus_gtk_places_view_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ NautilusGtkPlacesView *self = NAUTILUS_GTK_PLACES_VIEW (object);
+
+ switch (prop_id)
+ {
+ case PROP_OPEN_FLAGS:
+ nautilus_gtk_places_view_set_open_flags (self, g_value_get_flags (value));
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+}
+
+static gboolean
+is_external_volume (GVolume *volume)
+{
+ gboolean is_external;
+ GDrive *drive;
+ char *id;
+
+ drive = g_volume_get_drive (volume);
+ id = g_volume_get_identifier (volume, G_VOLUME_IDENTIFIER_KIND_CLASS);
+
+ is_external = g_volume_can_eject (volume);
+
+ /* NULL volume identifier only happens on removable devices */
+ is_external |= !id;
+
+ if (drive)
+ is_external |= g_drive_is_removable (drive);
+
+ g_clear_object (&drive);
+ g_free (id);
+
+ return is_external;
+}
+
+typedef struct
+{
+ char *uri;
+ NautilusGtkPlacesView *view;
+} RemoveServerData;
+
+static void
+on_remove_server_button_clicked (RemoveServerData *data)
+{
+ server_list_remove_server (data->view, data->uri);
+
+ populate_servers (data->view);
+}
+
+static void
+populate_servers (NautilusGtkPlacesView *view)
+{
+ GBookmarkFile *server_list;
+ GtkWidget *child;
+ char **uris;
+ gsize num_uris;
+ int i;
+
+ server_list = server_list_load (view);
+
+ if (!server_list)
+ return;
+
+ uris = g_bookmark_file_get_uris (server_list, &num_uris);
+
+ gtk_stack_set_visible_child_name (GTK_STACK (view->recent_servers_stack),
+ num_uris > 0 ? "list" : "empty");
+
+ if (!uris)
+ {
+ g_bookmark_file_free (server_list);
+ return;
+ }
+
+ /* clear previous items */
+ while ((child = gtk_widget_get_first_child (GTK_WIDGET (view->recent_servers_listbox))))
+ gtk_list_box_remove (GTK_LIST_BOX (view->recent_servers_listbox), child);
+
+ gtk_list_store_clear (view->completion_store);
+
+ for (i = 0; i < num_uris; i++)
+ {
+ RemoveServerData *data;
+ GtkTreeIter iter;
+ GtkWidget *row;
+ GtkWidget *grid;
+ GtkWidget *button;
+ GtkWidget *label;
+ char *name;
+ char *dup_uri;
+
+ name = g_bookmark_file_get_title (server_list, uris[i], NULL);
+ dup_uri = g_strdup (uris[i]);
+
+ /* add to the completion list */
+ gtk_list_store_append (view->completion_store, &iter);
+ gtk_list_store_set (view->completion_store,
+ &iter,
+ 0, name,
+ 1, uris[i],
+ -1);
+
+ /* add to the recent servers listbox */
+ row = gtk_list_box_row_new ();
+
+ grid = g_object_new (GTK_TYPE_GRID,
+ "orientation", GTK_ORIENTATION_VERTICAL,
+ NULL);
+
+ /* name of the connected uri, if any */
+ label = gtk_label_new (name);
+ gtk_widget_set_hexpand (label, TRUE);
+ gtk_label_set_xalign (GTK_LABEL (label), 0.0);
+ gtk_label_set_ellipsize (GTK_LABEL (label), PANGO_ELLIPSIZE_END);
+ gtk_grid_attach (GTK_GRID (grid), label, 0, 0, 1, 1);
+
+ /* the uri itself */
+ label = gtk_label_new (uris[i]);
+ gtk_widget_set_hexpand (label, TRUE);
+ gtk_label_set_xalign (GTK_LABEL (label), 0.0);
+ gtk_label_set_ellipsize (GTK_LABEL (label), PANGO_ELLIPSIZE_END);
+ gtk_widget_add_css_class (label, "dim-label");
+ gtk_grid_attach (GTK_GRID (grid), label, 0, 1, 1, 1);
+
+ /* remove button */
+ button = gtk_button_new_from_icon_name ("window-close-symbolic");
+ gtk_widget_set_halign (button, GTK_ALIGN_END);
+ gtk_widget_set_valign (button, GTK_ALIGN_CENTER);
+ gtk_button_set_has_frame (GTK_BUTTON (button), FALSE);
+ gtk_widget_add_css_class (button, "sidebar-button");
+ gtk_grid_attach (GTK_GRID (grid), button, 1, 0, 1, 2);
+
+ gtk_list_box_row_set_child (GTK_LIST_BOX_ROW (row), grid);
+ gtk_list_box_insert (GTK_LIST_BOX (view->recent_servers_listbox), row, -1);
+
+ /* custom data */
+ data = g_new0 (RemoveServerData, 1);
+ data->view = view;
+ data->uri = dup_uri;
+
+ g_object_set_data_full (G_OBJECT (row), "uri", dup_uri, g_free);
+ g_object_set_data_full (G_OBJECT (row), "remove-server-data", data, g_free);
+
+ g_signal_connect_swapped (button,
+ "clicked",
+ G_CALLBACK (on_remove_server_button_clicked),
+ data);
+
+ g_free (name);
+ }
+
+ g_strfreev (uris);
+ g_bookmark_file_free (server_list);
+}
+
+static void
+update_view_mode (NautilusGtkPlacesView *view)
+{
+ GtkWidget *child;
+ gboolean show_listbox;
+
+ show_listbox = FALSE;
+
+ /* drives */
+ for (child = gtk_widget_get_first_child (GTK_WIDGET (view->listbox));
+ child != NULL;
+ child = gtk_widget_get_next_sibling (child))
+ {
+ /* GtkListBox filter rows by changing their GtkWidget::child-visible property */
+ if (gtk_widget_get_child_visible (child))
+ {
+ show_listbox = TRUE;
+ break;
+ }
+ }
+
+ if (!show_listbox &&
+ view->search_query &&
+ view->search_query[0] != '\0')
+ {
+ gtk_stack_set_visible_child_name (GTK_STACK (view->stack), "empty-search");
+ }
+ else
+ {
+ gtk_stack_set_visible_child_name (GTK_STACK (view->stack), "browse");
+ }
+}
+
+static void
+insert_row (NautilusGtkPlacesView *view,
+ GtkWidget *row,
+ gboolean is_network)
+{
+ GtkEventController *controller;
+ GtkShortcutTrigger *trigger;
+ GtkShortcutAction *action;
+ GtkShortcut *shortcut;
+ GtkGesture *gesture;
+
+ g_object_set_data (G_OBJECT (row), "is-network", GINT_TO_POINTER (is_network));
+
+ controller = gtk_shortcut_controller_new ();
+ trigger = gtk_alternative_trigger_new (gtk_keyval_trigger_new (GDK_KEY_F10, GDK_SHIFT_MASK),
+ gtk_keyval_trigger_new (GDK_KEY_Menu, 0));
+ action = gtk_callback_action_new (on_row_popup_menu, row, NULL);
+ shortcut = gtk_shortcut_new (trigger, action);
+ gtk_shortcut_controller_add_shortcut (GTK_SHORTCUT_CONTROLLER (controller), shortcut);
+ gtk_widget_add_controller (GTK_WIDGET (row), controller);
+
+ gesture = gtk_gesture_click_new ();
+ gtk_gesture_single_set_button (GTK_GESTURE_SINGLE (gesture), GDK_BUTTON_SECONDARY);
+ g_signal_connect (gesture, "pressed", G_CALLBACK (click_cb), row);
+ gtk_widget_add_controller (row, GTK_EVENT_CONTROLLER (gesture));
+
+ g_signal_connect (nautilus_gtk_places_view_row_get_eject_button (NAUTILUS_GTK_PLACES_VIEW_ROW (row)),
+ "clicked",
+ G_CALLBACK (on_eject_button_clicked),
+ row);
+
+ nautilus_gtk_places_view_row_set_path_size_group (NAUTILUS_GTK_PLACES_VIEW_ROW (row), view->path_size_group);
+ nautilus_gtk_places_view_row_set_space_size_group (NAUTILUS_GTK_PLACES_VIEW_ROW (row), view->space_size_group);
+
+ gtk_list_box_insert (GTK_LIST_BOX (view->listbox), row, -1);
+}
+
+static void
+add_volume (NautilusGtkPlacesView *view,
+ GVolume *volume)
+{
+ gboolean is_network;
+ GMount *mount;
+ GFile *root;
+ GIcon *icon;
+ char *identifier;
+ char *name;
+ char *path;
+
+ if (is_external_volume (volume))
+ return;
+
+ identifier = g_volume_get_identifier (volume, G_VOLUME_IDENTIFIER_KIND_CLASS);
+ is_network = g_strcmp0 (identifier, "network") == 0;
+
+ mount = g_volume_get_mount (volume);
+ root = mount ? g_mount_get_default_location (mount) : NULL;
+ icon = g_volume_get_icon (volume);
+ name = g_volume_get_name (volume);
+ path = !is_network ? g_volume_get_identifier (volume, G_VOLUME_IDENTIFIER_KIND_UNIX_DEVICE) : NULL;
+
+ if (!mount || !g_mount_is_shadowed (mount))
+ {
+ GtkWidget *row;
+
+ row = g_object_new (NAUTILUS_TYPE_GTK_PLACES_VIEW_ROW,
+ "icon", icon,
+ "name", name,
+ "path", path ? path : "",
+ "volume", volume,
+ "mount", mount,
+ "file", NULL,
+ "is-network", is_network,
+ NULL);
+
+ insert_row (view, row, is_network);
+ }
+
+ g_clear_object (&root);
+ g_clear_object (&icon);
+ g_clear_object (&mount);
+ g_free (identifier);
+ g_free (name);
+ g_free (path);
+}
+
+static void
+add_mount (NautilusGtkPlacesView *view,
+ GMount *mount)
+{
+ gboolean is_network;
+ GFile *root;
+ GIcon *icon;
+ char *name;
+ char *path;
+ char *uri;
+ char *schema;
+
+ icon = g_mount_get_icon (mount);
+ name = g_mount_get_name (mount);
+ root = g_mount_get_default_location (mount);
+ path = root ? g_file_get_parse_name (root) : NULL;
+ uri = g_file_get_uri (root);
+ schema = g_uri_parse_scheme (uri);
+ is_network = g_strcmp0 (schema, "file") != 0;
+
+ if (is_network)
+ g_clear_pointer (&path, g_free);
+
+ if (!g_mount_is_shadowed (mount))
+ {
+ GtkWidget *row;
+
+ row = g_object_new (NAUTILUS_TYPE_GTK_PLACES_VIEW_ROW,
+ "icon", icon,
+ "name", name,
+ "path", path ? path : "",
+ "volume", NULL,
+ "mount", mount,
+ "file", NULL,
+ "is-network", is_network,
+ NULL);
+
+ insert_row (view, row, is_network);
+ }
+
+ g_clear_object (&root);
+ g_clear_object (&icon);
+ g_free (name);
+ g_free (path);
+ g_free (uri);
+ g_free (schema);
+}
+
+static void
+add_drive (NautilusGtkPlacesView *view,
+ GDrive *drive)
+{
+ GList *volumes;
+ GList *l;
+
+ volumes = g_drive_get_volumes (drive);
+
+ for (l = volumes; l != NULL; l = l->next)
+ add_volume (view, l->data);
+
+ g_list_free_full (volumes, g_object_unref);
+}
+
+static void
+add_file (NautilusGtkPlacesView *view,
+ GFile *file,
+ GIcon *icon,
+ const char *display_name,
+ const char *path,
+ gboolean is_network)
+{
+ GtkWidget *row;
+ row = g_object_new (NAUTILUS_TYPE_GTK_PLACES_VIEW_ROW,
+ "icon", icon,
+ "name", display_name,
+ "path", path,
+ "volume", NULL,
+ "mount", NULL,
+ "file", file,
+ "is_network", is_network,
+ NULL);
+
+ insert_row (view, row, is_network);
+}
+
+static gboolean
+has_networks (NautilusGtkPlacesView *view)
+{
+ GtkWidget *child;
+ gboolean has_network = FALSE;
+
+ for (child = gtk_widget_get_first_child (GTK_WIDGET (view->listbox));
+ child != NULL;
+ child = gtk_widget_get_next_sibling (child))
+ {
+ if (GPOINTER_TO_INT (g_object_get_data (G_OBJECT (child), "is-network")) &&
+ g_object_get_data (G_OBJECT (child), "is-placeholder") == NULL)
+ {
+ has_network = TRUE;
+ break;
+ }
+ }
+
+ return has_network;
+}
+
+static void
+update_network_state (NautilusGtkPlacesView *view)
+{
+ if (view->network_placeholder == NULL)
+ {
+ view->network_placeholder = gtk_list_box_row_new ();
+ view->network_placeholder_label = gtk_label_new ("");
+ gtk_label_set_xalign (GTK_LABEL (view->network_placeholder_label), 0.0);
+ gtk_widget_set_margin_start (view->network_placeholder_label, 12);
+ gtk_widget_set_margin_end (view->network_placeholder_label, 12);
+ gtk_widget_set_margin_top (view->network_placeholder_label, 6);
+ gtk_widget_set_margin_bottom (view->network_placeholder_label, 6);
+ gtk_widget_set_hexpand (view->network_placeholder_label, TRUE);
+ gtk_widget_set_sensitive (view->network_placeholder, FALSE);
+ gtk_list_box_row_set_child (GTK_LIST_BOX_ROW (view->network_placeholder),
+ view->network_placeholder_label);
+ g_object_set_data (G_OBJECT (view->network_placeholder),
+ "is-network", GINT_TO_POINTER (TRUE));
+ /* mark the row as placeholder, so it always goes first */
+ g_object_set_data (G_OBJECT (view->network_placeholder),
+ "is-placeholder", GINT_TO_POINTER (TRUE));
+ gtk_list_box_insert (GTK_LIST_BOX (view->listbox), view->network_placeholder, -1);
+ }
+
+ if (nautilus_gtk_places_view_get_fetching_networks (view))
+ {
+ /* only show a placeholder with a message if the list is empty.
+ * otherwise just show the spinner in the header */
+ if (!has_networks (view))
+ {
+ gtk_widget_show (view->network_placeholder);
+ gtk_label_set_text (GTK_LABEL (view->network_placeholder_label),
+ _("Searching for network locations"));
+ }
+ }
+ else if (!has_networks (view))
+ {
+ gtk_widget_show (view->network_placeholder);
+ gtk_label_set_text (GTK_LABEL (view->network_placeholder_label),
+ _("No network locations found"));
+ }
+ else
+ {
+ gtk_widget_hide (view->network_placeholder);
+ }
+}
+
+static void
+monitor_network (NautilusGtkPlacesView *view)
+{
+ GFile *network_file;
+ GError *error;
+
+ if (view->network_monitor)
+ return;
+
+ error = NULL;
+ network_file = g_file_new_for_uri ("network:///");
+ view->network_monitor = g_file_monitor (network_file,
+ G_FILE_MONITOR_NONE,
+ NULL,
+ &error);
+
+ g_clear_object (&network_file);
+
+ if (error)
+ {
+ g_warning ("Error monitoring network: %s", error->message);
+ g_clear_error (&error);
+ return;
+ }
+
+ g_signal_connect_swapped (view->network_monitor,
+ "changed",
+ G_CALLBACK (update_places),
+ view);
+}
+
+static void
+populate_networks (NautilusGtkPlacesView *view,
+ GFileEnumerator *enumerator,
+ GList *detected_networks)
+{
+ GList *l;
+ GFile *file;
+ GFile *activatable_file;
+ char *uri;
+ GFileType type;
+ GIcon *icon;
+ char *display_name;
+
+ for (l = detected_networks; l != NULL; l = l->next)
+ {
+ file = g_file_enumerator_get_child (enumerator, l->data);
+ type = g_file_info_get_file_type (l->data);
+ if (type == G_FILE_TYPE_SHORTCUT || type == G_FILE_TYPE_MOUNTABLE)
+ uri = g_file_info_get_attribute_as_string (l->data, G_FILE_ATTRIBUTE_STANDARD_TARGET_URI);
+ else
+ uri = g_file_get_uri (file);
+ activatable_file = g_file_new_for_uri (uri);
+ display_name = g_file_info_get_attribute_as_string (l->data, G_FILE_ATTRIBUTE_STANDARD_DISPLAY_NAME);
+ icon = g_file_info_get_icon (l->data);
+
+ add_file (view, activatable_file, icon, display_name, NULL, TRUE);
+
+ g_free (uri);
+ g_free (display_name);
+ g_clear_object (&file);
+ g_clear_object (&activatable_file);
+ }
+}
+
+static void
+network_enumeration_next_files_finished (GObject *source_object,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ NautilusGtkPlacesView *view;
+ GList *detected_networks;
+ GError *error;
+
+ view = NAUTILUS_GTK_PLACES_VIEW (user_data);
+ error = NULL;
+
+ detected_networks = g_file_enumerator_next_files_finish (G_FILE_ENUMERATOR (source_object),
+ res, &error);
+
+ if (error)
+ {
+ if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+ {
+ g_clear_error (&error);
+ g_object_unref (view);
+ return;
+ }
+
+ g_warning ("Failed to fetch network locations: %s", error->message);
+ g_clear_error (&error);
+ }
+ else
+ {
+ nautilus_gtk_places_view_set_fetching_networks (view, FALSE);
+ populate_networks (view, G_FILE_ENUMERATOR (source_object), detected_networks);
+
+ g_list_free_full (detected_networks, g_object_unref);
+ }
+
+ update_network_state (view);
+ monitor_network (view);
+ update_loading (view);
+
+ g_object_unref (view);
+}
+
+static void
+network_enumeration_finished (GObject *source_object,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ NautilusGtkPlacesView *view = NAUTILUS_GTK_PLACES_VIEW (user_data);
+ GFileEnumerator *enumerator;
+ GError *error;
+
+ error = NULL;
+ enumerator = g_file_enumerate_children_finish (G_FILE (source_object), res, &error);
+
+ if (error)
+ {
+ if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED) &&
+ !g_error_matches (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED))
+ g_warning ("Failed to fetch network locations: %s", error->message);
+
+ g_clear_error (&error);
+ g_object_unref (view);
+ }
+ else
+ {
+ g_file_enumerator_next_files_async (enumerator,
+ G_MAXINT32,
+ G_PRIORITY_DEFAULT,
+ view->networks_fetching_cancellable,
+ network_enumeration_next_files_finished,
+ user_data);
+ g_object_unref (enumerator);
+ }
+}
+
+static void
+fetch_networks (NautilusGtkPlacesView *view)
+{
+ GFile *network_file;
+ const char * const *supported_uris;
+ gboolean found;
+
+ supported_uris = g_vfs_get_supported_uri_schemes (g_vfs_get_default ());
+
+ for (found = FALSE; !found && supported_uris && supported_uris[0]; supported_uris++)
+ if (g_strcmp0 (supported_uris[0], "network") == 0)
+ found = TRUE;
+
+ if (!found)
+ return;
+
+ network_file = g_file_new_for_uri ("network:///");
+
+ g_cancellable_cancel (view->networks_fetching_cancellable);
+ g_clear_object (&view->networks_fetching_cancellable);
+ view->networks_fetching_cancellable = g_cancellable_new ();
+ nautilus_gtk_places_view_set_fetching_networks (view, TRUE);
+ update_network_state (view);
+
+ g_object_ref (view);
+ g_file_enumerate_children_async (network_file,
+ "standard::type,standard::target-uri,standard::name,standard::display-name,standard::icon",
+ G_FILE_QUERY_INFO_NONE,
+ G_PRIORITY_DEFAULT,
+ view->networks_fetching_cancellable,
+ network_enumeration_finished,
+ view);
+
+ g_clear_object (&network_file);
+}
+
+static void
+update_places (NautilusGtkPlacesView *view)
+{
+ GList *mounts;
+ GList *volumes;
+ GList *drives;
+ GList *l;
+ GIcon *icon;
+ GFile *file;
+ GtkWidget *child;
+ gchar *osname;
+
+ /* Clear all previously added items */
+ while ((child = gtk_widget_get_first_child (GTK_WIDGET (view->listbox))))
+ gtk_list_box_remove (GTK_LIST_BOX (view->listbox), child);
+
+ view->network_placeholder = NULL;
+ /* Inform clients that we started loading */
+ nautilus_gtk_places_view_set_loading (view, TRUE);
+
+ /* Add "Computer" row */
+ file = g_file_new_for_path ("/");
+ icon = g_themed_icon_new_with_default_fallbacks ("drive-harddisk");
+ osname = g_get_os_info (G_OS_INFO_KEY_NAME);
+
+ add_file (view, file, icon, ((osname != NULL) ? osname : _("Operating System")), "/", FALSE);
+
+ g_clear_object (&file);
+ g_clear_object (&icon);
+ g_free (osname);
+
+ /* Add currently connected drives */
+ drives = g_volume_monitor_get_connected_drives (view->volume_monitor);
+
+ for (l = drives; l != NULL; l = l->next)
+ add_drive (view, l->data);
+
+ g_list_free_full (drives, g_object_unref);
+
+ /*
+ * Since all volumes with an associated GDrive were already added with
+ * add_drive before, add all volumes that aren't associated with a
+ * drive.
+ */
+ volumes = g_volume_monitor_get_volumes (view->volume_monitor);
+
+ for (l = volumes; l != NULL; l = l->next)
+ {
+ GVolume *volume;
+ GDrive *drive;
+
+ volume = l->data;
+ drive = g_volume_get_drive (volume);
+
+ if (drive)
+ {
+ g_object_unref (drive);
+ continue;
+ }
+
+ add_volume (view, volume);
+ }
+
+ g_list_free_full (volumes, g_object_unref);
+
+ /*
+ * Now that all necessary drives and volumes were already added, add mounts
+ * that have no volume, such as /etc/mtab mounts, ftp, sftp, etc.
+ */
+ mounts = g_volume_monitor_get_mounts (view->volume_monitor);
+
+ for (l = mounts; l != NULL; l = l->next)
+ {
+ GMount *mount;
+ GVolume *volume;
+
+ mount = l->data;
+ volume = g_mount_get_volume (mount);
+
+ if (volume)
+ {
+ g_object_unref (volume);
+ continue;
+ }
+
+ add_mount (view, mount);
+ }
+
+ g_list_free_full (mounts, g_object_unref);
+
+ /* load saved servers */
+ populate_servers (view);
+
+ /* fetch networks and add them asynchronously */
+ fetch_networks (view);
+
+ update_view_mode (view);
+ /* Check whether we still are in a loading state */
+ update_loading (view);
+}
+
+static void
+server_mount_ready_cb (GObject *source_file,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ NautilusGtkPlacesView *view = NAUTILUS_GTK_PLACES_VIEW (user_data);
+ gboolean should_show;
+ GError *error;
+ GFile *location;
+
+ location = G_FILE (source_file);
+ should_show = TRUE;
+ error = NULL;
+
+ g_file_mount_enclosing_volume_finish (location, res, &error);
+ if (error)
+ {
+ should_show = FALSE;
+
+ if (error->code == G_IO_ERROR_ALREADY_MOUNTED)
+ {
+ /*
+ * Already mounted volume is not a critical error
+ * and we can still continue with the operation.
+ */
+ should_show = TRUE;
+ }
+ else if (error->domain != G_IO_ERROR ||
+ (error->code != G_IO_ERROR_CANCELLED &&
+ error->code != G_IO_ERROR_FAILED_HANDLED))
+ {
+ /* if it wasn't cancelled show a dialog */
+ emit_show_error_message (view, _("Unable to access location"), error->message);
+ }
+
+ /* The operation got cancelled by the user and or the error
+ has been handled already. */
+ g_clear_error (&error);
+ }
+
+ if (view->destroyed)
+ {
+ g_object_unref (view);
+ return;
+ }
+
+ view->should_pulse_entry = FALSE;
+ gtk_entry_set_progress_fraction (GTK_ENTRY (view->address_entry), 0);
+
+ /* Restore from Cancel to Connect */
+ gtk_button_set_label (GTK_BUTTON (view->connect_button), _("Con_nect"));
+ gtk_widget_set_sensitive (view->address_entry, TRUE);
+ view->connecting_to_server = FALSE;
+
+ if (should_show)
+ {
+ server_list_add_server (view, location);
+
+ /*
+ * Only clear the entry if it successfully connects to the server.
+ * Otherwise, the user would lost the typed address even if it fails
+ * to connect.
+ */
+ gtk_editable_set_text (GTK_EDITABLE (view->address_entry), "");
+
+ if (view->should_open_location)
+ {
+ GMount *mount;
+ GFile *root;
+
+ /*
+ * If the mount is not found at this point, it is probably user-
+ * invisible, which happens e.g for smb-browse, but the location
+ * should be opened anyway...
+ */
+ mount = g_file_find_enclosing_mount (location, view->cancellable, NULL);
+ if (mount)
+ {
+ root = g_mount_get_default_location (mount);
+
+ emit_open_location (view, root, view->open_flags);
+
+ g_object_unref (root);
+ g_object_unref (mount);
+ }
+ else
+ {
+ emit_open_location (view, location, view->open_flags);
+ }
+ }
+ }
+
+ update_places (view);
+ g_object_unref (view);
+}
+
+static void
+volume_mount_ready_cb (GObject *source_volume,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ NautilusGtkPlacesView *view = NAUTILUS_GTK_PLACES_VIEW (user_data);
+ gboolean should_show;
+ GVolume *volume;
+ GError *error;
+
+ volume = G_VOLUME (source_volume);
+ should_show = TRUE;
+ error = NULL;
+
+ g_volume_mount_finish (volume, res, &error);
+
+ if (error)
+ {
+ should_show = FALSE;
+
+ if (error->code == G_IO_ERROR_ALREADY_MOUNTED)
+ {
+ /*
+ * If the volume was already mounted, it's not a hard error
+ * and we can still continue with the operation.
+ */
+ should_show = TRUE;
+ }
+ else if (error->domain != G_IO_ERROR ||
+ (error->code != G_IO_ERROR_CANCELLED &&
+ error->code != G_IO_ERROR_FAILED_HANDLED))
+ {
+ /* if it wasn't cancelled show a dialog */
+ emit_show_error_message (NAUTILUS_GTK_PLACES_VIEW (user_data), _("Unable to access location"), error->message);
+ should_show = FALSE;
+ }
+
+ /* The operation got cancelled by the user and or the error
+ has been handled already. */
+ g_clear_error (&error);
+ }
+
+ if (view->destroyed)
+ {
+ g_object_unref(view);
+ return;
+ }
+
+ view->mounting_volume = FALSE;
+ update_loading (view);
+
+ if (should_show)
+ {
+ GMount *mount;
+ GFile *root;
+
+ mount = g_volume_get_mount (volume);
+ root = g_mount_get_default_location (mount);
+
+ if (view->should_open_location)
+ emit_open_location (NAUTILUS_GTK_PLACES_VIEW (user_data), root, view->open_flags);
+
+ g_object_unref (mount);
+ g_object_unref (root);
+ }
+
+ update_places (view);
+ g_object_unref (view);
+}
+
+static void
+unmount_ready_cb (GObject *source_mount,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ NautilusGtkPlacesView *view;
+ GMount *mount;
+ GError *error;
+
+ view = NAUTILUS_GTK_PLACES_VIEW (user_data);
+ mount = G_MOUNT (source_mount);
+ error = NULL;
+
+ g_mount_unmount_with_operation_finish (mount, res, &error);
+
+ if (error)
+ {
+ if (error->domain != G_IO_ERROR ||
+ (error->code != G_IO_ERROR_CANCELLED &&
+ error->code != G_IO_ERROR_FAILED_HANDLED))
+ {
+ /* if it wasn't cancelled show a dialog */
+ emit_show_error_message (view, _("Unable to unmount volume"), error->message);
+ }
+
+ g_clear_error (&error);
+ }
+
+ if (view->destroyed) {
+ g_object_unref (view);
+ return;
+ }
+
+ view->row_for_action = NULL;
+ view->unmounting_mount = FALSE;
+ update_loading (view);
+
+ g_object_unref (view);
+}
+
+static gboolean
+pulse_entry_cb (gpointer user_data)
+{
+ NautilusGtkPlacesView *view = NAUTILUS_GTK_PLACES_VIEW (user_data);
+
+ if (view->destroyed)
+ {
+ view->entry_pulse_timeout_id = 0;
+
+ return G_SOURCE_REMOVE;
+ }
+ else if (view->should_pulse_entry)
+ {
+ gtk_entry_progress_pulse (GTK_ENTRY (view->address_entry));
+
+ return G_SOURCE_CONTINUE;
+ }
+ else
+ {
+ gtk_entry_set_progress_fraction (GTK_ENTRY (view->address_entry), 0);
+ view->entry_pulse_timeout_id = 0;
+
+ return G_SOURCE_REMOVE;
+ }
+}
+
+static void
+unmount_mount (NautilusGtkPlacesView *view,
+ GMount *mount)
+{
+ GMountOperation *operation;
+ GtkWidget *toplevel;
+
+ toplevel = GTK_WIDGET (gtk_widget_get_root (GTK_WIDGET (view)));
+
+ g_cancellable_cancel (view->cancellable);
+ g_clear_object (&view->cancellable);
+ view->cancellable = g_cancellable_new ();
+
+ view->unmounting_mount = TRUE;
+ update_loading (view);
+
+ g_object_ref (view);
+
+ operation = gtk_mount_operation_new (GTK_WINDOW (toplevel));
+ g_mount_unmount_with_operation (mount,
+ 0,
+ operation,
+ view->cancellable,
+ unmount_ready_cb,
+ view);
+ g_object_unref (operation);
+}
+
+static void
+mount_server (NautilusGtkPlacesView *view,
+ GFile *location)
+{
+ GMountOperation *operation;
+ GtkWidget *toplevel;
+
+ g_cancellable_cancel (view->cancellable);
+ g_clear_object (&view->cancellable);
+ /* User cliked when the operation was ongoing, so wanted to cancel it */
+ if (view->connecting_to_server)
+ return;
+
+ view->cancellable = g_cancellable_new ();
+ toplevel = GTK_WIDGET (gtk_widget_get_root (GTK_WIDGET (view)));
+ operation = gtk_mount_operation_new (GTK_WINDOW (toplevel));
+
+ view->should_pulse_entry = TRUE;
+ gtk_entry_set_progress_pulse_step (GTK_ENTRY (view->address_entry), 0.1);
+ gtk_entry_set_progress_fraction (GTK_ENTRY (view->address_entry), 0.1);
+ /* Allow to cancel the operation */
+ gtk_button_set_label (GTK_BUTTON (view->connect_button), _("Cance_l"));
+ gtk_widget_set_sensitive (view->address_entry, FALSE);
+ view->connecting_to_server = TRUE;
+ update_loading (view);
+
+ if (view->entry_pulse_timeout_id == 0)
+ view->entry_pulse_timeout_id = g_timeout_add (100, (GSourceFunc) pulse_entry_cb, view);
+
+ g_mount_operation_set_password_save (operation, G_PASSWORD_SAVE_FOR_SESSION);
+
+ /* make sure we keep the view around for as long as we are running */
+ g_object_ref (view);
+
+ g_file_mount_enclosing_volume (location,
+ 0,
+ operation,
+ view->cancellable,
+ server_mount_ready_cb,
+ view);
+
+ /* unref operation here - g_file_mount_enclosing_volume() does ref for itself */
+ g_object_unref (operation);
+}
+
+static void
+mount_volume (NautilusGtkPlacesView *view,
+ GVolume *volume)
+{
+ GMountOperation *operation;
+ GtkWidget *toplevel;
+
+ toplevel = GTK_WIDGET (gtk_widget_get_root (GTK_WIDGET (view)));
+ operation = gtk_mount_operation_new (GTK_WINDOW (toplevel));
+
+ g_cancellable_cancel (view->cancellable);
+ g_clear_object (&view->cancellable);
+ view->cancellable = g_cancellable_new ();
+
+ view->mounting_volume = TRUE;
+ update_loading (view);
+
+ g_mount_operation_set_password_save (operation, G_PASSWORD_SAVE_FOR_SESSION);
+
+ /* make sure we keep the view around for as long as we are running */
+ g_object_ref (view);
+
+ g_volume_mount (volume,
+ 0,
+ operation,
+ view->cancellable,
+ volume_mount_ready_cb,
+ view);
+
+ /* unref operation here - g_file_mount_enclosing_volume() does ref for itself */
+ g_object_unref (operation);
+}
+
+static void
+open_cb (GtkWidget *widget,
+ const char *action_name,
+ GVariant *parameter)
+{
+ NautilusGtkPlacesView *view = NAUTILUS_GTK_PLACES_VIEW (widget);
+ NautilusGtkPlacesOpenFlags flags = NAUTILUS_GTK_PLACES_OPEN_NORMAL;
+
+ if (view->row_for_action == NULL)
+ return;
+
+ if (strcmp (action_name, "location.open") == 0)
+ flags = NAUTILUS_GTK_PLACES_OPEN_NORMAL;
+ else if (strcmp (action_name, "location.open-tab") == 0)
+ flags = NAUTILUS_GTK_PLACES_OPEN_NEW_TAB;
+ else if (strcmp (action_name, "location.open-window") == 0)
+ flags = NAUTILUS_GTK_PLACES_OPEN_NEW_WINDOW;
+
+ activate_row (view, view->row_for_action, flags);
+}
+
+static void
+properties_cb (GtkWidget *widget,
+ const char *action_name,
+ GVariant *parameter)
+{
+ NautilusGtkPlacesView *view = NAUTILUS_GTK_PLACES_VIEW (widget);
+ GList *list;
+ GMount *mount;
+ g_autoptr (GFile) location = NULL;
+ NautilusFile *file;
+
+ if (view->row_for_action == NULL)
+ return;
+
+ file = NULL;
+ mount = nautilus_gtk_places_view_row_get_mount (view->row_for_action);
+
+ if (mount)
+ {
+ location = g_mount_get_root (mount);
+ file = nautilus_file_get (location);
+ }
+ else
+ file = nautilus_file_get (nautilus_gtk_places_view_row_get_file (view->row_for_action));
+
+ if (file)
+ {
+ list = g_list_append (NULL, file);
+ nautilus_properties_window_present (list, widget, NULL, NULL, NULL);
+
+ nautilus_file_list_unref (list);
+ }
+}
+
+static void
+mount_cb (GtkWidget *widget,
+ const char *action_name,
+ GVariant *parameter)
+{
+ NautilusGtkPlacesView *view = NAUTILUS_GTK_PLACES_VIEW (widget);
+ GVolume *volume;
+
+ if (view->row_for_action == NULL)
+ return;
+
+ volume = nautilus_gtk_places_view_row_get_volume (view->row_for_action);
+
+ /*
+ * When the mount item is activated, it's expected that
+ * the volume only gets mounted, without opening it after
+ * the operation is complete.
+ */
+ view->should_open_location = FALSE;
+
+ nautilus_gtk_places_view_row_set_busy (view->row_for_action, TRUE);
+ mount_volume (view, volume);
+}
+
+static void
+unmount_cb (GtkWidget *widget,
+ const char *action_name,
+ GVariant *parameter)
+{
+ NautilusGtkPlacesView *view = NAUTILUS_GTK_PLACES_VIEW (widget);
+ GMount *mount;
+
+ if (view->row_for_action == NULL)
+ return;
+
+ mount = nautilus_gtk_places_view_row_get_mount (view->row_for_action);
+
+ nautilus_gtk_places_view_row_set_busy (view->row_for_action, TRUE);
+
+ unmount_mount (view, mount);
+}
+
+static void
+attach_protocol_row_to_grid (GtkGrid *grid,
+ const char *protocol_name,
+ const char *protocol_prefix)
+{
+ GtkWidget *name_label;
+ GtkWidget *prefix_label;
+
+ name_label = gtk_label_new (protocol_name);
+ gtk_widget_set_halign (name_label, GTK_ALIGN_START);
+ gtk_grid_attach_next_to (grid, name_label, NULL, GTK_POS_BOTTOM, 1, 1);
+
+ prefix_label = gtk_label_new (protocol_prefix);
+ gtk_widget_set_halign (prefix_label, GTK_ALIGN_START);
+ gtk_grid_attach_next_to (grid, prefix_label, name_label, GTK_POS_RIGHT, 1, 1);
+}
+
+static void
+populate_available_protocols_grid (GtkGrid *grid)
+{
+ const char * const *supported_protocols;
+ gboolean has_any = FALSE;
+
+ supported_protocols = g_vfs_get_supported_uri_schemes (g_vfs_get_default ());
+
+ if (g_strv_contains (supported_protocols, "afp"))
+ {
+ attach_protocol_row_to_grid (grid, _("AppleTalk"), "afp://");
+ has_any = TRUE;
+ }
+
+ if (g_strv_contains (supported_protocols, "ftp"))
+ {
+ attach_protocol_row_to_grid (grid, _("File Transfer Protocol"),
+ /* Translators: do not translate ftp:// and ftps:// */
+ _("ftp:// or ftps://"));
+ has_any = TRUE;
+ }
+
+ if (g_strv_contains (supported_protocols, "nfs"))
+ {
+ attach_protocol_row_to_grid (grid, _("Network File System"), "nfs://");
+ has_any = TRUE;
+ }
+
+ if (g_strv_contains (supported_protocols, "smb"))
+ {
+ attach_protocol_row_to_grid (grid, _("Samba"), "smb://");
+ has_any = TRUE;
+ }
+
+ if (g_strv_contains (supported_protocols, "ssh"))
+ {
+ attach_protocol_row_to_grid (grid, _("SSH File Transfer Protocol"),
+ /* Translators: do not translate sftp:// and ssh:// */
+ _("sftp:// or ssh://"));
+ has_any = TRUE;
+ }
+
+ if (g_strv_contains (supported_protocols, "dav"))
+ {
+ attach_protocol_row_to_grid (grid, _("WebDAV"),
+ /* Translators: do not translate dav:// and davs:// */
+ _("dav:// or davs://"));
+ has_any = TRUE;
+ }
+
+ if (!has_any)
+ gtk_widget_hide (GTK_WIDGET (grid));
+}
+
+static GMenuModel *
+get_menu_model (void)
+{
+ GMenu *menu;
+ GMenu *section;
+ GMenuItem *item;
+
+ menu = g_menu_new ();
+ section = g_menu_new ();
+ item = g_menu_item_new (_("_Open"), "location.open");
+ g_menu_append_item (section, item);
+ g_object_unref (item);
+
+ item = g_menu_item_new (_("Open in New _Tab"), "location.open-tab");
+ g_menu_item_set_attribute (item, "hidden-when", "s", "action-disabled");
+ g_menu_append_item (section, item);
+ g_object_unref (item);
+
+ item = g_menu_item_new (_("Open in New _Window"), "location.open-window");
+ g_menu_item_set_attribute (item, "hidden-when", "s", "action-disabled");
+ g_menu_append_item (section, item);
+ g_object_unref (item);
+
+ g_menu_append_section (menu, NULL, G_MENU_MODEL (section));
+ g_object_unref (section);
+
+ section = g_menu_new ();
+ item = g_menu_item_new (_("_Disconnect"), "location.disconnect");
+ g_menu_item_set_attribute (item, "hidden-when", "s", "action-disabled");
+ g_menu_append_item (section, item);
+ g_object_unref (item);
+
+ item = g_menu_item_new (_("_Unmount"), "location.unmount");
+ g_menu_item_set_attribute (item, "hidden-when", "s", "action-disabled");
+ g_menu_append_item (section, item);
+ g_object_unref (item);
+
+
+ item = g_menu_item_new (_("_Connect"), "location.connect");
+ g_menu_item_set_attribute (item, "hidden-when", "s", "action-disabled");
+ g_menu_append_item (section, item);
+ g_object_unref (item);
+
+ item = g_menu_item_new (_("_Mount"), "location.mount");
+ g_menu_item_set_attribute (item, "hidden-when", "s", "action-disabled");
+ g_menu_append_item (section, item);
+ g_object_unref (item);
+
+ item = g_menu_item_new (_("_Properties"), "location.properties");
+ g_menu_item_set_attribute (item, "hidden-when", "s", "action-disabled");
+ g_menu_append_item (section, item);
+ g_object_unref (item);
+
+ g_menu_append_section (menu, NULL, G_MENU_MODEL (section));
+ g_object_unref (section);
+
+ return G_MENU_MODEL (menu);
+}
+
+static void
+_popover_set_pointing_to_widget (GtkPopover *popover,
+ GtkWidget *target)
+{
+ GtkWidget *parent;
+ double x, y, w, h;
+
+ parent = gtk_widget_get_parent (GTK_WIDGET (popover));
+
+ if (!gtk_widget_translate_coordinates (target, parent, 0, 0, &x, &y))
+ return;
+
+ w = gtk_widget_get_allocated_width (GTK_WIDGET (target));
+ h = gtk_widget_get_allocated_height (GTK_WIDGET (target));
+
+ gtk_popover_set_pointing_to (popover, &(GdkRectangle){x, y, w, h});
+}
+
+static gboolean
+real_popup_menu (GtkWidget *widget,
+ double x,
+ double y)
+{
+ NautilusGtkPlacesViewRow *row = NAUTILUS_GTK_PLACES_VIEW_ROW (widget);
+ NautilusGtkPlacesView *view;
+ GMount *mount;
+ GFile *file;
+ gboolean is_root, is_network;
+ double x_in_view, y_in_view;
+
+ view = NAUTILUS_GTK_PLACES_VIEW (gtk_widget_get_ancestor (GTK_WIDGET (row), NAUTILUS_TYPE_GTK_PLACES_VIEW));
+
+ mount = nautilus_gtk_places_view_row_get_mount (row);
+ file = nautilus_gtk_places_view_row_get_file (row);
+ is_root = file && nautilus_is_root_directory (file);
+ is_network = nautilus_gtk_places_view_row_get_is_network (row);
+
+ gtk_widget_action_set_enabled (GTK_WIDGET (view), "location.disconnect",
+ !file && mount && is_network);
+ gtk_widget_action_set_enabled (GTK_WIDGET (view), "location.unmount",
+ !file && mount && !is_network);
+ gtk_widget_action_set_enabled (GTK_WIDGET (view), "location.connect",
+ !file && !mount && is_network);
+ gtk_widget_action_set_enabled (GTK_WIDGET (view), "location.mount",
+ !file && !mount && !is_network);
+ gtk_widget_action_set_enabled (GTK_WIDGET (view), "location.properties",
+ (is_root ||
+ (mount && !(file && is_network))));
+
+ if (!view->popup_menu)
+ {
+ GMenuModel *model = get_menu_model ();
+
+ view->popup_menu = gtk_popover_menu_new_from_model (model);
+
+ gtk_popover_set_has_arrow (GTK_POPOVER (view->popup_menu), FALSE);
+ gtk_widget_set_parent (view->popup_menu, GTK_WIDGET (view));
+ gtk_widget_set_halign (view->popup_menu, GTK_ALIGN_START);
+
+ g_object_unref (model);
+ }
+
+ if (view->row_for_action)
+ g_object_set_data (G_OBJECT (view->row_for_action), "menu", NULL);
+
+ if (x == -1 && y == -1)
+ _popover_set_pointing_to_widget (GTK_POPOVER (view->popup_menu), widget);
+ else
+ {
+ gtk_widget_translate_coordinates (widget, GTK_WIDGET (view),
+ x, y, &x_in_view, &y_in_view);
+ gtk_popover_set_pointing_to (GTK_POPOVER (view->popup_menu),
+ &(GdkRectangle){x_in_view, y_in_view, 0, 0});
+ }
+
+ view->row_for_action = row;
+ if (view->row_for_action)
+ g_object_set_data (G_OBJECT (view->row_for_action), "menu", view->popup_menu);
+
+ gtk_popover_popup (GTK_POPOVER (view->popup_menu));
+
+ return TRUE;
+}
+
+static gboolean
+on_row_popup_menu (GtkWidget *widget,
+ GVariant *args,
+ gpointer user_data)
+{
+ return real_popup_menu (widget, -1, -1);
+}
+
+static void
+click_cb (GtkGesture *gesture,
+ int n_press,
+ double x,
+ double y,
+ gpointer user_data)
+{
+ real_popup_menu (GTK_WIDGET (user_data), x, y);
+}
+
+static gboolean
+on_key_press_event (GtkEventController *controller,
+ guint keyval,
+ guint keycode,
+ GdkModifierType state,
+ NautilusGtkPlacesView *view)
+{
+ GdkModifierType modifiers;
+
+ modifiers = gtk_accelerator_get_default_mod_mask ();
+
+ if (keyval == GDK_KEY_Return ||
+ keyval == GDK_KEY_KP_Enter ||
+ keyval == GDK_KEY_ISO_Enter ||
+ keyval == GDK_KEY_space)
+ {
+ GtkWidget *focus_widget;
+ GtkWindow *toplevel;
+
+ view->current_open_flags = NAUTILUS_GTK_PLACES_OPEN_NORMAL;
+ toplevel = get_toplevel (GTK_WIDGET (view));
+
+ if (!toplevel)
+ return FALSE;
+
+ focus_widget = gtk_root_get_focus (GTK_ROOT (toplevel));
+
+ if (!NAUTILUS_IS_GTK_PLACES_VIEW_ROW (focus_widget))
+ return FALSE;
+
+ if ((state & modifiers) == GDK_SHIFT_MASK)
+ view->current_open_flags = NAUTILUS_GTK_PLACES_OPEN_NEW_TAB;
+ else if ((state & modifiers) == GDK_CONTROL_MASK)
+ view->current_open_flags = NAUTILUS_GTK_PLACES_OPEN_NEW_WINDOW;
+
+ activate_row (view, NAUTILUS_GTK_PLACES_VIEW_ROW (focus_widget), view->current_open_flags);
+
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static void
+on_middle_click_row_event (GtkGestureClick *gesture,
+ guint n_press,
+ double x,
+ double y,
+ NautilusGtkPlacesView *view)
+{
+ GtkListBoxRow *row;
+
+ if (n_press != 1)
+ return;
+
+ row = gtk_list_box_get_row_at_y (GTK_LIST_BOX (view->listbox), y);
+ if (row != NULL && gtk_widget_is_sensitive (GTK_WIDGET (row)))
+ activate_row (view, NAUTILUS_GTK_PLACES_VIEW_ROW (row), NAUTILUS_GTK_PLACES_OPEN_NEW_TAB);
+}
+
+
+static void
+on_eject_button_clicked (GtkWidget *widget,
+ NautilusGtkPlacesViewRow *row)
+{
+ if (row)
+ {
+ GtkWidget *view = gtk_widget_get_ancestor (GTK_WIDGET (row), NAUTILUS_TYPE_GTK_PLACES_VIEW);
+
+ unmount_mount (NAUTILUS_GTK_PLACES_VIEW (view), nautilus_gtk_places_view_row_get_mount (row));
+ }
+}
+
+static void
+on_connect_button_clicked (NautilusGtkPlacesView *view)
+{
+ const char *uri;
+ GFile *file;
+
+ file = NULL;
+
+ /*
+ * Since the 'Connect' button is updated whenever the typed
+ * address changes, it is sufficient to check if it's sensitive
+ * or not, in order to determine if the given address is valid.
+ */
+ if (!gtk_widget_get_sensitive (view->connect_button))
+ return;
+
+ uri = gtk_editable_get_text (GTK_EDITABLE (view->address_entry));
+
+ if (uri != NULL && uri[0] != '\0')
+ file = g_file_new_for_commandline_arg (uri);
+
+ if (file)
+ {
+ view->should_open_location = TRUE;
+
+ mount_server (view, file);
+ }
+ else
+ {
+ emit_show_error_message (view, _("Unable to get remote server location"), NULL);
+ }
+}
+
+static void
+on_address_entry_text_changed (NautilusGtkPlacesView *view)
+{
+ const char * const *supported_protocols;
+ char *address, *scheme;
+ gboolean supported;
+
+ supported = FALSE;
+ supported_protocols = g_vfs_get_supported_uri_schemes (g_vfs_get_default ());
+ address = g_strdup (gtk_editable_get_text (GTK_EDITABLE (view->address_entry)));
+ scheme = g_uri_parse_scheme (address);
+
+ if (!supported_protocols)
+ goto out;
+
+ if (!scheme)
+ goto out;
+
+ supported = g_strv_contains (supported_protocols, scheme) &&
+ !g_strv_contains (unsupported_protocols, scheme);
+
+out:
+ gtk_widget_set_sensitive (view->connect_button, supported);
+ if (scheme && !supported)
+ gtk_widget_add_css_class (view->address_entry, "error");
+ else
+ gtk_widget_remove_css_class (view->address_entry, "error");
+
+ g_free (address);
+ g_free (scheme);
+}
+
+static void
+on_address_entry_show_help_pressed (NautilusGtkPlacesView *view,
+ GtkEntryIconPosition icon_pos,
+ GtkEntry *entry)
+{
+ GdkRectangle rect;
+ double x, y;
+
+ /* Setup the auxiliary popover's rectangle */
+ gtk_entry_get_icon_area (GTK_ENTRY (view->address_entry),
+ GTK_ENTRY_ICON_SECONDARY,
+ &rect);
+ gtk_widget_translate_coordinates (view->address_entry, GTK_WIDGET (view),
+ rect.x, rect.y, &x, &y);
+
+ rect.x = x;
+ rect.y = y;
+ gtk_popover_set_pointing_to (GTK_POPOVER (view->server_adresses_popover), &rect);
+ gtk_widget_set_visible (view->server_adresses_popover, TRUE);
+}
+
+static void
+on_recent_servers_listbox_row_activated (NautilusGtkPlacesView *view,
+ NautilusGtkPlacesViewRow *row,
+ GtkWidget *listbox)
+{
+ char *uri;
+
+ uri = g_object_get_data (G_OBJECT (row), "uri");
+
+ gtk_editable_set_text (GTK_EDITABLE (view->address_entry), uri);
+
+ gtk_widget_hide (view->recent_servers_popover);
+}
+
+static void
+on_listbox_row_activated (NautilusGtkPlacesView *view,
+ NautilusGtkPlacesViewRow *row,
+ GtkWidget *listbox)
+{
+ activate_row (view, row, view->current_open_flags);
+}
+
+static gboolean
+listbox_filter_func (GtkListBoxRow *row,
+ gpointer user_data)
+{
+ NautilusGtkPlacesView *view = NAUTILUS_GTK_PLACES_VIEW (user_data);
+ gboolean is_placeholder;
+ gboolean retval;
+ gboolean searching;
+ char *name;
+ char *path;
+
+ retval = FALSE;
+ searching = view->search_query && view->search_query[0] != '\0';
+
+ is_placeholder = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (row), "is-placeholder"));
+
+ if (is_placeholder && searching)
+ return FALSE;
+
+ if (!searching)
+ return TRUE;
+
+ g_object_get (row,
+ "name", &name,
+ "path", &path,
+ NULL);
+
+ if (name)
+ {
+ char *lowercase_name = g_utf8_strdown (name, -1);
+
+ retval |= strstr (lowercase_name, view->search_query) != NULL;
+
+ g_free (lowercase_name);
+ }
+
+ if (path)
+ {
+ char *lowercase_path = g_utf8_strdown (path, -1);
+
+ retval |= strstr (lowercase_path, view->search_query) != NULL;
+
+ g_free (lowercase_path);
+ }
+
+ g_free (name);
+ g_free (path);
+
+ return retval;
+}
+
+static void
+listbox_header_func (GtkListBoxRow *row,
+ GtkListBoxRow *before,
+ gpointer user_data)
+{
+ gboolean row_is_network;
+ char *text;
+
+ text = NULL;
+ row_is_network = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (row), "is-network"));
+
+ if (!before)
+ {
+ text = g_strdup_printf ("<b>%s</b>", row_is_network ? _("Networks") : _("On This Computer"));
+ }
+ else
+ {
+ gboolean before_is_network;
+
+ before_is_network = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (before), "is-network"));
+
+ if (before_is_network != row_is_network)
+ text = g_strdup_printf ("<b>%s</b>", row_is_network ? _("Networks") : _("On This Computer"));
+ }
+
+ if (text)
+ {
+ GtkWidget *header;
+ GtkWidget *label;
+ GtkWidget *separator;
+
+ header = gtk_box_new (GTK_ORIENTATION_VERTICAL, 6);
+ gtk_widget_set_margin_top (header, 6);
+
+ separator = gtk_separator_new (GTK_ORIENTATION_HORIZONTAL);
+
+ label = g_object_new (GTK_TYPE_LABEL,
+ "use_markup", TRUE,
+ "margin-start", 12,
+ "label", text,
+ "xalign", 0.0f,
+ NULL);
+ if (row_is_network)
+ {
+ GtkWidget *header_name;
+ GtkWidget *network_header_spinner;
+
+ gtk_widget_set_margin_end (label, 6);
+
+ header_name = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
+ network_header_spinner = gtk_spinner_new ();
+ gtk_widget_set_margin_end (network_header_spinner, 12);
+ g_object_bind_property (NAUTILUS_GTK_PLACES_VIEW (user_data),
+ "fetching-networks",
+ network_header_spinner,
+ "spinning",
+ G_BINDING_SYNC_CREATE);
+
+ gtk_box_append (GTK_BOX (header_name), label);
+ gtk_box_append (GTK_BOX (header_name), network_header_spinner);
+ gtk_box_append (GTK_BOX (header), header_name);
+ }
+ else
+ {
+ gtk_widget_set_hexpand (label, TRUE);
+ gtk_widget_set_margin_end (label, 12);
+ gtk_box_append (GTK_BOX (header), label);
+ }
+
+ gtk_box_append (GTK_BOX (header), separator);
+
+ gtk_list_box_row_set_header (row, header);
+
+ g_free (text);
+ }
+ else
+ {
+ gtk_list_box_row_set_header (row, NULL);
+ }
+}
+
+static int
+listbox_sort_func (GtkListBoxRow *row1,
+ GtkListBoxRow *row2,
+ gpointer user_data)
+{
+ gboolean row1_is_network;
+ gboolean row2_is_network;
+ char *path1;
+ char *path2;
+ gboolean *is_placeholder1;
+ gboolean *is_placeholder2;
+ int retval;
+
+ row1_is_network = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (row1), "is-network"));
+ row2_is_network = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (row2), "is-network"));
+
+ retval = row1_is_network - row2_is_network;
+
+ if (retval != 0)
+ return retval;
+
+ is_placeholder1 = g_object_get_data (G_OBJECT (row1), "is-placeholder");
+ is_placeholder2 = g_object_get_data (G_OBJECT (row2), "is-placeholder");
+
+ /* we can't have two placeholders for the same section */
+ g_assert (!(is_placeholder1 != NULL && is_placeholder2 != NULL));
+
+ if (is_placeholder1)
+ return -1;
+ if (is_placeholder2)
+ return 1;
+
+ g_object_get (row1, "path", &path1, NULL);
+ g_object_get (row2, "path", &path2, NULL);
+
+ retval = g_utf8_collate (path1, path2);
+
+ g_free (path1);
+ g_free (path2);
+
+ return retval;
+}
+
+static void
+nautilus_gtk_places_view_constructed (GObject *object)
+{
+ NautilusGtkPlacesView *view = NAUTILUS_GTK_PLACES_VIEW (object);
+
+ G_OBJECT_CLASS (nautilus_gtk_places_view_parent_class)->constructed (object);
+
+ gtk_list_box_set_sort_func (GTK_LIST_BOX (view->listbox),
+ listbox_sort_func,
+ object,
+ NULL);
+ gtk_list_box_set_filter_func (GTK_LIST_BOX (view->listbox),
+ listbox_filter_func,
+ object,
+ NULL);
+ gtk_list_box_set_header_func (GTK_LIST_BOX (view->listbox),
+ listbox_header_func,
+ object,
+ NULL);
+
+ /* load drives */
+ update_places (view);
+
+ g_signal_connect_swapped (view->volume_monitor,
+ "mount-added",
+ G_CALLBACK (update_places),
+ object);
+ g_signal_connect_swapped (view->volume_monitor,
+ "mount-changed",
+ G_CALLBACK (update_places),
+ object);
+ g_signal_connect_swapped (view->volume_monitor,
+ "mount-removed",
+ G_CALLBACK (update_places),
+ object);
+ g_signal_connect_swapped (view->volume_monitor,
+ "volume-added",
+ G_CALLBACK (update_places),
+ object);
+ g_signal_connect_swapped (view->volume_monitor,
+ "volume-changed",
+ G_CALLBACK (update_places),
+ object);
+ g_signal_connect_swapped (view->volume_monitor,
+ "volume-removed",
+ G_CALLBACK (update_places),
+ object);
+}
+
+static void
+nautilus_gtk_places_view_map (GtkWidget *widget)
+{
+ NautilusGtkPlacesView *view = NAUTILUS_GTK_PLACES_VIEW (widget);
+
+ gtk_editable_set_text (GTK_EDITABLE (view->address_entry), "");
+
+ GTK_WIDGET_CLASS (nautilus_gtk_places_view_parent_class)->map (widget);
+}
+
+static void
+nautilus_gtk_places_view_class_init (NautilusGtkPlacesViewClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+
+ object_class->finalize = nautilus_gtk_places_view_finalize;
+ object_class->dispose = nautilus_gtk_places_view_dispose;
+ object_class->constructed = nautilus_gtk_places_view_constructed;
+ object_class->get_property = nautilus_gtk_places_view_get_property;
+ object_class->set_property = nautilus_gtk_places_view_set_property;
+
+ widget_class->map = nautilus_gtk_places_view_map;
+
+ /*
+ * NautilusGtkPlacesView::open-location:
+ * @view: the object which received the signal.
+ * @location: (type Gio.File): GFile to which the caller should switch.
+ * @open_flags: a single value from NautilusGtkPlacesOpenFlags specifying how the @location
+ * should be opened.
+ *
+ * The places view emits this signal when the user selects a location
+ * in it. The calling application should display the contents of that
+ * location; for example, a file manager should show a list of files in
+ * the specified location.
+ */
+ places_view_signals [OPEN_LOCATION] =
+ g_signal_new ("open-location",
+ G_OBJECT_CLASS_TYPE (object_class),
+ G_SIGNAL_RUN_FIRST,
+ G_STRUCT_OFFSET (NautilusGtkPlacesViewClass, open_location),
+ NULL, NULL,
+ NULL,
+ G_TYPE_NONE, 2,
+ G_TYPE_OBJECT,
+ NAUTILUS_TYPE_OPEN_FLAGS);
+
+ /*
+ * NautilusGtkPlacesView::show-error-message:
+ * @view: the object which received the signal.
+ * @primary: primary message with a summary of the error to show.
+ * @secondary: secondary message with details of the error to show.
+ *
+ * The places view emits this signal when it needs the calling
+ * application to present an error message. Most of these messages
+ * refer to mounting or unmounting media, for example, when a drive
+ * cannot be started for some reason.
+ */
+ places_view_signals [SHOW_ERROR_MESSAGE] =
+ g_signal_new ("show-error-message",
+ G_OBJECT_CLASS_TYPE (object_class),
+ G_SIGNAL_RUN_FIRST,
+ G_STRUCT_OFFSET (NautilusGtkPlacesViewClass, show_error_message),
+ NULL, NULL,
+ NULL,
+ G_TYPE_NONE, 2,
+ G_TYPE_STRING,
+ G_TYPE_STRING);
+
+ properties[PROP_LOADING] =
+ g_param_spec_boolean ("loading",
+ "Loading",
+ "Whether the view is loading locations",
+ FALSE,
+ G_PARAM_READABLE|G_PARAM_STATIC_NAME|G_PARAM_STATIC_NICK|G_PARAM_STATIC_BLURB);
+
+ properties[PROP_FETCHING_NETWORKS] =
+ g_param_spec_boolean ("fetching-networks",
+ "Fetching networks",
+ "Whether the view is fetching networks",
+ FALSE,
+ G_PARAM_READABLE|G_PARAM_STATIC_NAME|G_PARAM_STATIC_NICK|G_PARAM_STATIC_BLURB);
+
+ properties[PROP_OPEN_FLAGS] =
+ g_param_spec_flags ("open-flags",
+ "Open Flags",
+ "Modes in which the calling application can open locations selected in the sidebar",
+ NAUTILUS_TYPE_OPEN_FLAGS,
+ NAUTILUS_GTK_PLACES_OPEN_NORMAL,
+ G_PARAM_READWRITE|G_PARAM_STATIC_NAME|G_PARAM_STATIC_NICK|G_PARAM_STATIC_BLURB);
+
+ g_object_class_install_properties (object_class, LAST_PROP, properties);
+
+ /* Bind class to template */
+ gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/nautilus/gtk/ui/nautilusgtkplacesview.ui");
+
+ gtk_widget_class_bind_template_child (widget_class, NautilusGtkPlacesView, actionbar);
+ gtk_widget_class_bind_template_child (widget_class, NautilusGtkPlacesView, address_entry);
+ gtk_widget_class_bind_template_child (widget_class, NautilusGtkPlacesView, address_entry_completion);
+ gtk_widget_class_bind_template_child (widget_class, NautilusGtkPlacesView, completion_store);
+ gtk_widget_class_bind_template_child (widget_class, NautilusGtkPlacesView, connect_button);
+ gtk_widget_class_bind_template_child (widget_class, NautilusGtkPlacesView, listbox);
+ gtk_widget_class_bind_template_child (widget_class, NautilusGtkPlacesView, recent_servers_listbox);
+ gtk_widget_class_bind_template_child (widget_class, NautilusGtkPlacesView, recent_servers_popover);
+ gtk_widget_class_bind_template_child (widget_class, NautilusGtkPlacesView, recent_servers_stack);
+ gtk_widget_class_bind_template_child (widget_class, NautilusGtkPlacesView, stack);
+ gtk_widget_class_bind_template_child (widget_class, NautilusGtkPlacesView, server_adresses_popover);
+ gtk_widget_class_bind_template_child (widget_class, NautilusGtkPlacesView, available_protocols_grid);
+
+ gtk_widget_class_bind_template_callback (widget_class, on_address_entry_text_changed);
+ gtk_widget_class_bind_template_callback (widget_class, on_address_entry_show_help_pressed);
+ gtk_widget_class_bind_template_callback (widget_class, on_connect_button_clicked);
+ gtk_widget_class_bind_template_callback (widget_class, on_listbox_row_activated);
+ gtk_widget_class_bind_template_callback (widget_class, on_recent_servers_listbox_row_activated);
+
+ /**
+ * NautilusGtkPlacesView|location.open:
+ *
+ * Opens the location in the current window.
+ */
+ gtk_widget_class_install_action (widget_class, "location.open", NULL, open_cb);
+
+ /**
+ * NautilusGtkPlacesView|location.open-tab:
+ *
+ * Opens the location in a new tab.
+ */
+ gtk_widget_class_install_action (widget_class, "location.open-tab", NULL, open_cb);
+
+ /**
+ * NautilusGtkPlacesView|location.open-window:
+ *
+ * Opens the location in a new window.
+ */
+ gtk_widget_class_install_action (widget_class, "location.open-window", NULL, open_cb);
+
+ /**
+ * NautilusGtkPlacesView|location.mount:
+ *
+ * Mount the location.
+ */
+ gtk_widget_class_install_action (widget_class, "location.mount", NULL, mount_cb);
+
+ /**
+ * NautilusGtkPlacesView|location.connect:
+ *
+ * Connect the location.
+ */
+ gtk_widget_class_install_action (widget_class, "location.connect", NULL, mount_cb);
+
+ /**
+ * NautilusGtkPlacesView|location.unmount:
+ *
+ * Unmount the location.
+ */
+ gtk_widget_class_install_action (widget_class, "location.unmount", NULL, unmount_cb);
+
+ /**
+ * NautilusGtkPlacesView|location.disconnect:
+ *
+ * Disconnect the location.
+ */
+ gtk_widget_class_install_action (widget_class, "location.disconnect", NULL, unmount_cb);
+
+ /**
+ * NautilusGtkPlacesView|location.properties:
+ *
+ * Show location properties.
+ */
+ gtk_widget_class_install_action (widget_class, "location.properties", NULL, properties_cb);
+
+ gtk_widget_class_set_css_name (widget_class, "placesview");
+ gtk_widget_class_set_accessible_role (widget_class, GTK_ACCESSIBLE_ROLE_LIST);
+}
+
+static void
+nautilus_gtk_places_view_init (NautilusGtkPlacesView *self)
+{
+ GtkEventController *controller;
+
+ self->volume_monitor = g_volume_monitor_get ();
+ self->open_flags = NAUTILUS_GTK_PLACES_OPEN_NORMAL;
+ self->path_size_group = gtk_size_group_new (GTK_SIZE_GROUP_HORIZONTAL);
+ self->space_size_group = gtk_size_group_new (GTK_SIZE_GROUP_HORIZONTAL);
+
+ gtk_widget_action_set_enabled (GTK_WIDGET (self), "location.open-tab", FALSE);
+ gtk_widget_action_set_enabled (GTK_WIDGET (self), "location.open-window", FALSE);
+
+ gtk_widget_init_template (GTK_WIDGET (self));
+
+ gtk_widget_set_parent (self->server_adresses_popover, GTK_WIDGET (self));
+ controller = gtk_event_controller_key_new ();
+ g_signal_connect (controller, "key-pressed", G_CALLBACK (on_key_press_event), self);
+ gtk_widget_add_controller (GTK_WIDGET (self), controller);
+
+ /* We need an additional controller because GtkListBox only
+ * activates rows for GDK_BUTTON_PRIMARY clicks
+ */
+ controller = (GtkEventController *) gtk_gesture_click_new ();
+ gtk_event_controller_set_propagation_phase (controller, GTK_PHASE_BUBBLE);
+ gtk_gesture_single_set_button (GTK_GESTURE_SINGLE (controller), GDK_BUTTON_MIDDLE);
+ g_signal_connect (controller, "released",
+ G_CALLBACK (on_middle_click_row_event), self);
+ gtk_widget_add_controller (self->listbox, controller);
+
+ populate_available_protocols_grid (GTK_GRID (self->available_protocols_grid));
+}
+
+/*
+ * nautilus_gtk_places_view_new:
+ *
+ * Creates a new NautilusGtkPlacesView widget.
+ *
+ * The application should connect to at least the
+ * NautilusGtkPlacesView::open-location signal to be notified
+ * when the user makes a selection in the view.
+ *
+ * Returns: a newly created NautilusGtkPlacesView
+ */
+GtkWidget *
+nautilus_gtk_places_view_new (void)
+{
+ return g_object_new (NAUTILUS_TYPE_GTK_PLACES_VIEW, NULL);
+}
+
+/*
+ * nautilus_gtk_places_view_set_open_flags:
+ * @view: a NautilusGtkPlacesView
+ * @flags: Bitmask of modes in which the calling application can open locations
+ *
+ * Sets the way in which the calling application can open new locations from
+ * the places view. For example, some applications only open locations
+ * “directly” into their main view, while others may support opening locations
+ * in a new notebook tab or a new window.
+ *
+ * This function is used to tell the places @view about the ways in which the
+ * application can open new locations, so that the view can display (or not)
+ * the “Open in new tab” and “Open in new window” menu items as appropriate.
+ *
+ * When the NautilusGtkPlacesView::open-location signal is emitted, its flags
+ * argument will be set to one of the @flags that was passed in
+ * nautilus_gtk_places_view_set_open_flags().
+ *
+ * Passing 0 for @flags will cause NAUTILUS_GTK_PLACES_OPEN_NORMAL to always be sent
+ * to callbacks for the “open-location” signal.
+ */
+void
+nautilus_gtk_places_view_set_open_flags (NautilusGtkPlacesView *view,
+ NautilusGtkPlacesOpenFlags flags)
+{
+ g_return_if_fail (NAUTILUS_IS_GTK_PLACES_VIEW (view));
+
+ if (view->open_flags == flags)
+ return;
+
+ view->open_flags = flags;
+
+ gtk_widget_action_set_enabled (GTK_WIDGET (view), "location.open-tab",
+ (flags & NAUTILUS_GTK_PLACES_OPEN_NEW_TAB) != 0);
+ gtk_widget_action_set_enabled (GTK_WIDGET (view), "location.open-window",
+ (flags & NAUTILUS_GTK_PLACES_OPEN_NEW_WINDOW) != 0);
+
+ g_object_notify_by_pspec (G_OBJECT (view), properties[PROP_OPEN_FLAGS]);
+}
+
+/*
+ * nautilus_gtk_places_view_get_open_flags:
+ * @view: a NautilusGtkPlacesSidebar
+ *
+ * Gets the open flags.
+ *
+ * Returns: the NautilusGtkPlacesOpenFlags of @view
+ */
+NautilusGtkPlacesOpenFlags
+nautilus_gtk_places_view_get_open_flags (NautilusGtkPlacesView *view)
+{
+ g_return_val_if_fail (NAUTILUS_IS_GTK_PLACES_VIEW (view), 0);
+
+ return view->open_flags;
+}
+
+/*
+ * nautilus_gtk_places_view_get_search_query:
+ * @view: a NautilusGtkPlacesView
+ *
+ * Retrieves the current search query from @view.
+ *
+ * Returns: (transfer none): the current search query.
+ */
+const char *
+nautilus_gtk_places_view_get_search_query (NautilusGtkPlacesView *view)
+{
+ g_return_val_if_fail (NAUTILUS_IS_GTK_PLACES_VIEW (view), NULL);
+
+ return view->search_query;
+}
+
+/*
+ * nautilus_gtk_places_view_set_search_query:
+ * @view: a NautilusGtkPlacesView
+ * @query_text: the query, or NULL.
+ *
+ * Sets the search query of @view. The search is immediately performed
+ * once the query is set.
+ */
+void
+nautilus_gtk_places_view_set_search_query (NautilusGtkPlacesView *view,
+ const char *query_text)
+{
+ g_return_if_fail (NAUTILUS_IS_GTK_PLACES_VIEW (view));
+
+ if (g_strcmp0 (view->search_query, query_text) != 0)
+ {
+ g_clear_pointer (&view->search_query, g_free);
+ view->search_query = g_utf8_strdown (query_text, -1);
+
+ gtk_list_box_invalidate_filter (GTK_LIST_BOX (view->listbox));
+ gtk_list_box_invalidate_headers (GTK_LIST_BOX (view->listbox));
+
+ update_view_mode (view);
+ }
+}
+
+/*
+ * nautilus_gtk_places_view_get_loading:
+ * @view: a NautilusGtkPlacesView
+ *
+ * Returns %TRUE if the view is loading locations.
+ */
+gboolean
+nautilus_gtk_places_view_get_loading (NautilusGtkPlacesView *view)
+{
+ g_return_val_if_fail (NAUTILUS_IS_GTK_PLACES_VIEW (view), FALSE);
+
+ return view->loading;
+}
+
+static void
+update_loading (NautilusGtkPlacesView *view)
+{
+ gboolean loading;
+
+ g_return_if_fail (NAUTILUS_IS_GTK_PLACES_VIEW (view));
+
+ loading = view->fetching_networks || view->connecting_to_server ||
+ view->mounting_volume || view->unmounting_mount;
+
+ set_busy_cursor (view, loading);
+ nautilus_gtk_places_view_set_loading (view, loading);
+}
+
+static void
+nautilus_gtk_places_view_set_loading (NautilusGtkPlacesView *view,
+ gboolean loading)
+{
+ g_return_if_fail (NAUTILUS_IS_GTK_PLACES_VIEW (view));
+
+ if (view->loading != loading)
+ {
+ view->loading = loading;
+ g_object_notify_by_pspec (G_OBJECT (view), properties [PROP_LOADING]);
+ }
+}
+
+static gboolean
+nautilus_gtk_places_view_get_fetching_networks (NautilusGtkPlacesView *view)
+{
+ g_return_val_if_fail (NAUTILUS_IS_GTK_PLACES_VIEW (view), FALSE);
+
+ return view->fetching_networks;
+}
+
+static void
+nautilus_gtk_places_view_set_fetching_networks (NautilusGtkPlacesView *view,
+ gboolean fetching_networks)
+{
+ g_return_if_fail (NAUTILUS_IS_GTK_PLACES_VIEW (view));
+
+ if (view->fetching_networks != fetching_networks)
+ {
+ view->fetching_networks = fetching_networks;
+ g_object_notify_by_pspec (G_OBJECT (view), properties [PROP_FETCHING_NETWORKS]);
+ }
+}
diff --git a/src/gtk/nautilusgtkplacesview.ui b/src/gtk/nautilusgtkplacesview.ui
new file mode 100644
index 0000000..fbedf70
--- /dev/null
+++ b/src/gtk/nautilusgtkplacesview.ui
@@ -0,0 +1,261 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface domain="gtk40">
+ <object class="GtkListStore" id="completion_store">
+ <columns>
+ <column type="gchararray"/>
+ <column type="gchararray"/>
+ </columns>
+ </object>
+ <object class="GtkEntryCompletion" id="address_entry_completion">
+ <property name="model">completion_store</property>
+ <property name="text-column">1</property>
+ <property name="inline-completion">1</property>
+ <property name="popup-completion">0</property>
+ </object>
+ <object class="GtkPopover" id="server_adresses_popover">
+ <property name="position">2</property>
+ <child>
+ <object class="GtkBox">
+ <property name="orientation">1</property>
+ <property name="spacing">6</property>
+ <property name="margin-start">18</property>
+ <property name="margin-end">18</property>
+ <property name="margin-top">18</property>
+ <property name="margin-bottom">18</property>
+ <child>
+ <object class="GtkLabel">
+ <property name="hexpand">1</property>
+ <property name="label" translatable="yes">Server Addresses</property>
+ <attributes>
+ <attribute name="weight" value="bold"></attribute>
+ </attributes>
+ <style>
+ <class name="dim-label"/>
+ </style>
+ </object>
+ </child>
+ <child>
+ <object class="GtkLabel">
+ <property name="hexpand">1</property>
+ <property name="label" translatable="yes">Server addresses are made up of a protocol prefix and an address. Examples:</property>
+ <property name="wrap">1</property>
+ <property name="width-chars">40</property>
+ <property name="max-width-chars">40</property>
+ <property name="xalign">0</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkLabel">
+ <property name="hexpand">1</property>
+ <property name="label">smb://gnome.org, ssh://192.168.0.1, ftp://[2001:db8::1]</property>
+ <property name="wrap">1</property>
+ <property name="width-chars">40</property>
+ <property name="max-width-chars">40</property>
+ <property name="xalign">0</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkGrid" id="available_protocols_grid">
+ <property name="margin-top">12</property>
+ <property name="hexpand">1</property>
+ <property name="row-spacing">6</property>
+ <property name="column-spacing">12</property>
+ <child>
+ <object class="GtkLabel">
+ <property name="hexpand">1</property>
+ <property name="label" translatable="yes">Available Protocols</property>
+ <property name="xalign">0</property>
+ <attributes>
+ <attribute name="weight" value="bold"></attribute>
+ </attributes>
+ <layout>
+ <property name="column">0</property>
+ <property name="row">0</property>
+ </layout>
+ </object>
+ </child>
+ <child>
+ <object class="GtkLabel">
+ <property name="label" translatable="yes">Prefix</property>
+ <property name="xalign">0</property>
+ <attributes>
+ <attribute name="weight" value="bold"></attribute>
+ </attributes>
+ <layout>
+ <property name="column">1</property>
+ <property name="row">0</property>
+ </layout>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ <object class="GtkPopover" id="recent_servers_popover">
+ <child>
+ <object class="GtkStack" id="recent_servers_stack">
+ <child>
+ <object class="GtkStackPage">
+ <property name="name">empty</property>
+ <property name="child">
+ <object class="AdwStatusPage">
+ <property name="icon-name">network-server-symbolic</property>
+ <property name="title" translatable="yes" comments="Translators: Server as any successfully connected network address">No Recent Servers</property>
+ <style>
+ <class name="compact"/>
+ </style>
+ </object>
+ </property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkStackPage">
+ <property name="name">list</property>
+ <property name="child">
+ <object class="GtkBox">
+ <property name="orientation">1</property>
+ <property name="spacing">12</property>
+ <property name="margin-start">12</property>
+ <property name="margin-end">12</property>
+ <property name="margin-top">12</property>
+ <property name="margin-bottom">12</property>
+ <child>
+ <object class="GtkLabel">
+ <property name="label" translatable="yes">Recent Servers</property>
+ <attributes>
+ <attribute name="weight" value="bold"></attribute>
+ </attributes>
+ </object>
+ </child>
+ <child>
+ <object class="GtkScrolledWindow">
+ <property name="vexpand">1</property>
+ <property name="has-frame">1</property>
+ <property name="min-content-width">250</property>
+ <property name="min-content-height">200</property>
+ <child>
+ <object class="GtkViewport">
+ <child>
+ <object class="GtkListBox" id="recent_servers_listbox">
+ <property name="selection-mode">0</property>
+ <signal name="row-activated" handler="on_recent_servers_listbox_row_activated" object="NautilusGtkPlacesView" swapped="yes"/>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ </property>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ <template class="NautilusGtkPlacesView" parent="GtkBox">
+ <accessibility>
+ <property name="label" translatable="yes">Other Locations</property>
+ <property name="description" translatable="yes">List of common local and remote mountpoints.</property>
+ </accessibility>
+ <property name="orientation">1</property>
+ <child>
+ <object class="GtkStack" id="stack">
+ <property name="vhomogeneous">0</property>
+ <property name="transition-type">1</property>
+ <child>
+ <object class="GtkStackPage">
+ <property name="name">browse</property>
+ <property name="child">
+ <object class="GtkScrolledWindow">
+ <property name="hexpand">1</property>
+ <property name="vexpand">1</property>
+ <child>
+ <object class="GtkViewport">
+ <child>
+ <object class="GtkListBox" id="listbox">
+ <property name="selection-mode">0</property>
+ <signal name="row-activated" handler="on_listbox_row_activated" object="NautilusGtkPlacesView" swapped="yes"/>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ </property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkStackPage">
+ <property name="name">empty-search</property>
+ <property name="child">
+ <object class="AdwStatusPage">
+ <property name="icon-name">edit-find-symbolic</property>
+ <property name="title" translatable="yes">No Results Found</property>
+ <property name="description" translatable="yes">Try a different search.</property>
+ </object>
+ </property>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child>
+ <object class="GtkActionBar" id="actionbar">
+ <property name="hexpand">1</property>
+ <style>
+ <class name="background"/>
+ </style>
+ <child>
+ <object class="GtkLabel">
+ <property name="hexpand">1</property>
+ <property name="xalign">0</property>
+ <property name="label" translatable="yes">Connect to _Server</property>
+ <property name="mnemonic-widget">address_entry</property>
+ <property name="use-underline">1</property>
+ <attributes>
+ <attribute name="weight" value="bold"></attribute>
+ </attributes>
+ </object>
+ </child>
+ <child type="end">
+ <object class="GtkButton" id="connect_button">
+ <property name="label" translatable="yes">Con_nect</property>
+ <property name="use-underline">1</property>
+ <property name="sensitive">0</property>
+ <property name="valign">3</property>
+ <signal name="clicked" handler="on_connect_button_clicked" object="NautilusGtkPlacesView" swapped="yes"/>
+ </object>
+ </child>
+ <child type="end">
+ <object class="GtkBox">
+ <property name="hexpand">1</property>
+ <child>
+ <object class="GtkEntry" id="address_entry">
+ <property name="hexpand">1</property>
+ <property name="width-chars">20</property>
+ <property name="placeholder-text" translatable="yes">Enter server address…</property>
+ <property name="secondary-icon-name">dialog-question-symbolic</property>
+ <property name="completion">address_entry_completion</property>
+ <signal name="notify::text" handler="on_address_entry_text_changed" object="NautilusGtkPlacesView" swapped="yes"/>
+ <signal name="activate" handler="on_connect_button_clicked" object="NautilusGtkPlacesView" swapped="yes"/>
+ <signal name="icon-press" handler="on_address_entry_show_help_pressed" object="NautilusGtkPlacesView" swapped="yes"/>
+ </object>
+ </child>
+ <child>
+ <object class="GtkMenuButton" id="server_list_button">
+ <property name="direction">0</property>
+ <property name="popover">recent_servers_popover</property>
+ <property name="icon-name">pan-down-symbolic</property>
+ <style>
+ <class name="server-list-button"/>
+ </style>
+ </object>
+ </child>
+ <style>
+ <class name="linked"/>
+ </style>
+ </object>
+ </child>
+ </object>
+ </child>
+ </template>
+</interface>
diff --git a/src/gtk/nautilusgtkplacesviewprivate.h b/src/gtk/nautilusgtkplacesviewprivate.h
new file mode 100644
index 0000000..4cf6e3e
--- /dev/null
+++ b/src/gtk/nautilusgtkplacesviewprivate.h
@@ -0,0 +1,55 @@
+/* nautilusgtkplacesview.h
+ *
+ * Copyright (C) 2015 Georges Basile Stavracas Neto <georges.stavracas@gmail.com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation, either version 2.1 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 Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef NAUTILUS_GTK_PLACES_VIEW_H
+#define NAUTILUS_GTK_PLACES_VIEW_H
+
+#if !defined (__GTK_H_INSIDE__) && !defined (GTK_COMPILATION)
+#endif
+
+#include "nautilusgtkplacessidebarprivate.h"
+
+G_BEGIN_DECLS
+
+#define NAUTILUS_TYPE_GTK_PLACES_VIEW (nautilus_gtk_places_view_get_type ())
+#define NAUTILUS_GTK_PLACES_VIEW(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), NAUTILUS_TYPE_GTK_PLACES_VIEW, NautilusGtkPlacesView))
+#define NAUTILUS_GTK_PLACES_VIEW_CLASS(klass)(G_TYPE_CHECK_CLASS_CAST ((klass), NAUTILUS_TYPE_GTK_PLACES_VIEW, NautilusGtkPlacesViewClass))
+#define NAUTILUS_IS_GTK_PLACES_VIEW(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), NAUTILUS_TYPE_GTK_PLACES_VIEW))
+#define NAUTILUS_IS_GTK_PLACES_VIEW_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), NAUTILUS_TYPE_GTK_PLACES_VIEW))
+#define NAUTILUS_GTK_PLACES_VIEW_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), NAUTILUS_TYPE_GTK_PLACES_VIEW, NautilusGtkPlacesViewClass))
+
+typedef struct _NautilusGtkPlacesView NautilusGtkPlacesView;
+typedef struct _NautilusGtkPlacesViewClass NautilusGtkPlacesViewClass;
+
+GType nautilus_gtk_places_view_get_type (void) G_GNUC_CONST;
+
+NautilusGtkPlacesOpenFlags nautilus_gtk_places_view_get_open_flags (NautilusGtkPlacesView *view);
+void nautilus_gtk_places_view_set_open_flags (NautilusGtkPlacesView *view,
+ NautilusGtkPlacesOpenFlags flags);
+
+const char * nautilus_gtk_places_view_get_search_query (NautilusGtkPlacesView *view);
+void nautilus_gtk_places_view_set_search_query (NautilusGtkPlacesView *view,
+ const char *query_text);
+
+gboolean nautilus_gtk_places_view_get_loading (NautilusGtkPlacesView *view);
+
+GtkWidget * nautilus_gtk_places_view_new (void);
+
+G_END_DECLS
+
+#endif /* NAUTILUS_GTK_PLACES_VIEW_H */
diff --git a/src/gtk/nautilusgtkplacesviewrow.c b/src/gtk/nautilusgtkplacesviewrow.c
new file mode 100644
index 0000000..64d8896
--- /dev/null
+++ b/src/gtk/nautilusgtkplacesviewrow.c
@@ -0,0 +1,508 @@
+/* nautilusgtkplacesviewrow.c
+ *
+ * Copyright (C) 2015 Georges Basile Stavracas Neto <georges.stavracas@gmail.com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 2.1 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 Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+#include <glib/gi18n.h>
+#include <gtk/gtk.h>
+#include "nautilus-application.h"
+#include "nautilus-enum-types.h"
+
+#include <gio/gio.h>
+
+#include "nautilusgtkplacesviewrowprivate.h"
+
+/* As this widget is shared with Nautilus, we use this guard to
+ * ensure that internally we only include the files that we need
+ * instead of including gtk.h
+ */
+#ifdef GTK_COMPILATION
+#else
+#include <gtk/gtk.h>
+#endif
+
+struct _NautilusGtkPlacesViewRow
+{
+ GtkListBoxRow parent_instance;
+
+ GtkLabel *available_space_label;
+ GtkStack *mount_stack;
+ GtkSpinner *busy_spinner;
+ GtkButton *eject_button;
+ GtkImage *eject_icon;
+ GtkImage *icon_image;
+ GtkLabel *name_label;
+ GtkLabel *path_label;
+
+ GVolume *volume;
+ GMount *mount;
+ GFile *file;
+
+ GCancellable *cancellable;
+
+ int is_network : 1;
+};
+
+G_DEFINE_TYPE (NautilusGtkPlacesViewRow, nautilus_gtk_places_view_row, GTK_TYPE_LIST_BOX_ROW)
+
+enum {
+ PROP_0,
+ PROP_ICON,
+ PROP_NAME,
+ PROP_PATH,
+ PROP_VOLUME,
+ PROP_MOUNT,
+ PROP_FILE,
+ PROP_IS_NETWORK,
+ LAST_PROP
+};
+
+static GParamSpec *properties [LAST_PROP];
+
+static void
+measure_available_space_finished (GObject *object,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ NautilusGtkPlacesViewRow *row = user_data;
+ GFileInfo *info;
+ GError *error;
+ guint64 free_space;
+ guint64 total_space;
+ char *formatted_free_size;
+ char *formatted_total_size;
+ char *label;
+ guint plural_form;
+
+ error = NULL;
+
+ info = g_file_query_filesystem_info_finish (G_FILE (object),
+ res,
+ &error);
+
+ if (error)
+ {
+ if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED) &&
+ !g_error_matches (error, G_IO_ERROR, G_IO_ERROR_NOT_MOUNTED))
+ {
+ g_warning ("Failed to measure available space: %s", error->message);
+ }
+
+ g_clear_error (&error);
+ goto out;
+ }
+
+ if (!g_file_info_has_attribute (info, G_FILE_ATTRIBUTE_FILESYSTEM_FREE) ||
+ !g_file_info_has_attribute (info, G_FILE_ATTRIBUTE_FILESYSTEM_SIZE))
+ {
+ g_object_unref (info);
+ goto out;
+ }
+
+ free_space = g_file_info_get_attribute_uint64 (info, G_FILE_ATTRIBUTE_FILESYSTEM_FREE);
+ total_space = g_file_info_get_attribute_uint64 (info, G_FILE_ATTRIBUTE_FILESYSTEM_SIZE);
+
+ formatted_free_size = g_format_size (free_space);
+ formatted_total_size = g_format_size (total_space);
+
+ /* read g_format_size code in glib for further understanding */
+ plural_form = free_space < 1000 ? free_space : free_space % 1000 + 1000;
+
+ /* Translators: respectively, free and total space of the drive. The plural form
+ * should be based on the free space available.
+ * i.e. 1 GB / 24 GB available.
+ */
+ label = g_strdup_printf (dngettext (GETTEXT_PACKAGE, "%s / %s available", "%s / %s available", plural_form),
+ formatted_free_size, formatted_total_size);
+
+ gtk_label_set_label (row->available_space_label, label);
+
+ g_object_unref (info);
+ g_free (formatted_total_size);
+ g_free (formatted_free_size);
+ g_free (label);
+out:
+ g_object_unref (object);
+}
+
+static void
+measure_available_space (NautilusGtkPlacesViewRow *row)
+{
+ gboolean skip_measure;
+ gboolean should_measure;
+ g_autoptr (GFile) root = NULL;
+
+ skip_measure = FALSE;
+ if (nautilus_application_is_sandboxed ())
+ {
+ root = g_file_new_for_uri ("file:///");
+ if (row->file != NULL)
+ skip_measure = g_file_equal (root, row->file);
+ }
+
+ should_measure = ((row->volume || row->mount || row->file) &&
+ !row->is_network && !skip_measure);
+
+ gtk_label_set_label (row->available_space_label, "");
+ gtk_widget_set_visible (GTK_WIDGET (row->available_space_label), should_measure);
+
+ if (should_measure)
+ {
+ GFile *file = NULL;
+
+ if (row->file)
+ {
+ file = g_object_ref (row->file);
+ }
+ else if (row->mount)
+ {
+ file = g_mount_get_root (row->mount);
+ }
+ else if (row->volume)
+ {
+ GMount *mount;
+
+ mount = g_volume_get_mount (row->volume);
+
+ if (mount)
+ file = g_mount_get_root (row->mount);
+
+ g_clear_object (&mount);
+ }
+
+ if (file)
+ {
+ g_cancellable_cancel (row->cancellable);
+ g_clear_object (&row->cancellable);
+ row->cancellable = g_cancellable_new ();
+
+ g_file_query_filesystem_info_async (file,
+ G_FILE_ATTRIBUTE_FILESYSTEM_FREE "," G_FILE_ATTRIBUTE_FILESYSTEM_SIZE,
+ G_PRIORITY_DEFAULT,
+ row->cancellable,
+ measure_available_space_finished,
+ row);
+ }
+ }
+}
+
+static void
+nautilus_gtk_places_view_row_finalize (GObject *object)
+{
+ NautilusGtkPlacesViewRow *self = NAUTILUS_GTK_PLACES_VIEW_ROW (object);
+
+ g_cancellable_cancel (self->cancellable);
+
+ g_clear_object (&self->volume);
+ g_clear_object (&self->mount);
+ g_clear_object (&self->file);
+ g_clear_object (&self->cancellable);
+
+ G_OBJECT_CLASS (nautilus_gtk_places_view_row_parent_class)->finalize (object);
+}
+
+static void
+nautilus_gtk_places_view_row_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ NautilusGtkPlacesViewRow *self;
+
+ self = NAUTILUS_GTK_PLACES_VIEW_ROW (object);
+
+ switch (prop_id)
+ {
+ case PROP_ICON:
+ g_value_set_object (value, gtk_image_get_gicon (self->icon_image));
+ break;
+
+ case PROP_NAME:
+ g_value_set_string (value, gtk_label_get_label (self->name_label));
+ break;
+
+ case PROP_PATH:
+ g_value_set_string (value, gtk_label_get_label (self->path_label));
+ break;
+
+ case PROP_VOLUME:
+ g_value_set_object (value, self->volume);
+ break;
+
+ case PROP_MOUNT:
+ g_value_set_object (value, self->mount);
+ break;
+
+ case PROP_FILE:
+ g_value_set_object (value, self->file);
+ break;
+
+ case PROP_IS_NETWORK:
+ g_value_set_boolean (value, self->is_network);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+}
+
+static void
+nautilus_gtk_places_view_row_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ NautilusGtkPlacesViewRow *self = NAUTILUS_GTK_PLACES_VIEW_ROW (object);
+
+ switch (prop_id)
+ {
+ case PROP_ICON:
+ gtk_image_set_from_gicon (self->icon_image, g_value_get_object (value));
+ break;
+
+ case PROP_NAME:
+ gtk_label_set_label (self->name_label, g_value_get_string (value));
+ break;
+
+ case PROP_PATH:
+ gtk_label_set_label (self->path_label, g_value_get_string (value));
+ break;
+
+ case PROP_VOLUME:
+ g_set_object (&self->volume, g_value_get_object (value));
+ break;
+
+ case PROP_MOUNT:
+ g_set_object (&self->mount, g_value_get_object (value));
+ if (self->mount != NULL)
+ {
+ gtk_stack_set_visible_child (self->mount_stack, GTK_WIDGET (self->eject_button));
+ gtk_widget_set_child_visible (GTK_WIDGET (self->mount_stack), TRUE);
+ }
+ else
+ {
+ gtk_widget_set_child_visible (GTK_WIDGET (self->mount_stack), FALSE);
+ }
+ measure_available_space (self);
+ break;
+
+ case PROP_FILE:
+ g_set_object (&self->file, g_value_get_object (value));
+ measure_available_space (self);
+ break;
+
+ case PROP_IS_NETWORK:
+ nautilus_gtk_places_view_row_set_is_network (self, g_value_get_boolean (value));
+ measure_available_space (self);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+}
+
+static void
+nautilus_gtk_places_view_row_size_allocate (GtkWidget *widget,
+ int width,
+ int height,
+ int baseline)
+{
+ GtkWidget *menu = GTK_WIDGET (g_object_get_data (G_OBJECT (widget), "menu"));
+
+ GTK_WIDGET_CLASS (nautilus_gtk_places_view_row_parent_class)->size_allocate (widget, width, height, baseline);
+ if (menu)
+ gtk_popover_present (GTK_POPOVER (menu));
+}
+
+static void
+nautilus_gtk_places_view_row_class_init (NautilusGtkPlacesViewRowClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+
+ object_class->finalize = nautilus_gtk_places_view_row_finalize;
+ object_class->get_property = nautilus_gtk_places_view_row_get_property;
+ object_class->set_property = nautilus_gtk_places_view_row_set_property;
+
+ widget_class->size_allocate = nautilus_gtk_places_view_row_size_allocate;
+
+ properties[PROP_ICON] =
+ g_param_spec_object ("icon",
+ "Icon of the row",
+ "The icon representing the volume",
+ G_TYPE_ICON,
+ G_PARAM_READWRITE);
+
+ properties[PROP_NAME] =
+ g_param_spec_string ("name",
+ "Name of the volume",
+ "The name of the volume",
+ "",
+ G_PARAM_READWRITE);
+
+ properties[PROP_PATH] =
+ g_param_spec_string ("path",
+ "Path of the volume",
+ "The path of the volume",
+ "",
+ G_PARAM_READWRITE);
+
+ properties[PROP_VOLUME] =
+ g_param_spec_object ("volume",
+ "Volume represented by the row",
+ "The volume represented by the row",
+ G_TYPE_VOLUME,
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY);
+
+ properties[PROP_MOUNT] =
+ g_param_spec_object ("mount",
+ "Mount represented by the row",
+ "The mount point represented by the row, if any",
+ G_TYPE_MOUNT,
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY);
+
+ properties[PROP_FILE] =
+ g_param_spec_object ("file",
+ "File represented by the row",
+ "The file represented by the row, if any",
+ G_TYPE_FILE,
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY);
+
+ properties[PROP_IS_NETWORK] =
+ g_param_spec_boolean ("is-network",
+ "Whether the row represents a network location",
+ "Whether the row represents a network location",
+ FALSE,
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY);
+
+ g_object_class_install_properties (object_class, LAST_PROP, properties);
+
+ gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/nautilus/gtk/ui/nautilusgtkplacesviewrow.ui");
+
+ gtk_widget_class_bind_template_child (widget_class, NautilusGtkPlacesViewRow, available_space_label);
+ gtk_widget_class_bind_template_child (widget_class, NautilusGtkPlacesViewRow, mount_stack);
+ gtk_widget_class_bind_template_child (widget_class, NautilusGtkPlacesViewRow, busy_spinner);
+ gtk_widget_class_bind_template_child (widget_class, NautilusGtkPlacesViewRow, eject_button);
+ gtk_widget_class_bind_template_child (widget_class, NautilusGtkPlacesViewRow, eject_icon);
+ gtk_widget_class_bind_template_child (widget_class, NautilusGtkPlacesViewRow, icon_image);
+ gtk_widget_class_bind_template_child (widget_class, NautilusGtkPlacesViewRow, name_label);
+ gtk_widget_class_bind_template_child (widget_class, NautilusGtkPlacesViewRow, path_label);
+}
+
+static void
+nautilus_gtk_places_view_row_init (NautilusGtkPlacesViewRow *self)
+{
+ gtk_widget_init_template (GTK_WIDGET (self));
+}
+
+GtkWidget*
+nautilus_gtk_places_view_row_new (GVolume *volume,
+ GMount *mount)
+{
+ return g_object_new (NAUTILUS_TYPE_GTK_PLACES_VIEW_ROW,
+ "volume", volume,
+ "mount", mount,
+ NULL);
+}
+
+GMount*
+nautilus_gtk_places_view_row_get_mount (NautilusGtkPlacesViewRow *row)
+{
+ g_return_val_if_fail (NAUTILUS_IS_GTK_PLACES_VIEW_ROW (row), NULL);
+
+ return row->mount;
+}
+
+GVolume*
+nautilus_gtk_places_view_row_get_volume (NautilusGtkPlacesViewRow *row)
+{
+ g_return_val_if_fail (NAUTILUS_IS_GTK_PLACES_VIEW_ROW (row), NULL);
+
+ return row->volume;
+}
+
+GFile*
+nautilus_gtk_places_view_row_get_file (NautilusGtkPlacesViewRow *row)
+{
+ g_return_val_if_fail (NAUTILUS_IS_GTK_PLACES_VIEW_ROW (row), NULL);
+
+ return row->file;
+}
+
+GtkWidget*
+nautilus_gtk_places_view_row_get_eject_button (NautilusGtkPlacesViewRow *row)
+{
+ g_return_val_if_fail (NAUTILUS_IS_GTK_PLACES_VIEW_ROW (row), NULL);
+
+ return GTK_WIDGET (row->eject_button);
+}
+
+void
+nautilus_gtk_places_view_row_set_busy (NautilusGtkPlacesViewRow *row,
+ gboolean is_busy)
+{
+ g_return_if_fail (NAUTILUS_IS_GTK_PLACES_VIEW_ROW (row));
+
+ if (is_busy)
+ {
+ gtk_stack_set_visible_child (row->mount_stack, GTK_WIDGET (row->busy_spinner));
+ gtk_widget_set_child_visible (GTK_WIDGET (row->mount_stack), TRUE);
+ gtk_spinner_start (row->busy_spinner);
+ }
+ else
+ {
+ gtk_widget_set_child_visible (GTK_WIDGET (row->mount_stack), FALSE);
+ gtk_spinner_stop (row->busy_spinner);
+ }
+}
+
+gboolean
+nautilus_gtk_places_view_row_get_is_network (NautilusGtkPlacesViewRow *row)
+{
+ g_return_val_if_fail (NAUTILUS_IS_GTK_PLACES_VIEW_ROW (row), FALSE);
+
+ return row->is_network;
+}
+
+void
+nautilus_gtk_places_view_row_set_is_network (NautilusGtkPlacesViewRow *row,
+ gboolean is_network)
+{
+ if (row->is_network != is_network)
+ {
+ row->is_network = is_network;
+
+ gtk_image_set_from_icon_name (row->eject_icon, "media-eject-symbolic");
+ gtk_widget_set_tooltip_text (GTK_WIDGET (row->eject_button), is_network ? _("Disconnect") : _("Unmount"));
+ }
+}
+
+void
+nautilus_gtk_places_view_row_set_path_size_group (NautilusGtkPlacesViewRow *row,
+ GtkSizeGroup *group)
+{
+ if (group)
+ gtk_size_group_add_widget (group, GTK_WIDGET (row->path_label));
+}
+
+void
+nautilus_gtk_places_view_row_set_space_size_group (NautilusGtkPlacesViewRow *row,
+ GtkSizeGroup *group)
+{
+ if (group)
+ gtk_size_group_add_widget (group, GTK_WIDGET (row->available_space_label));
+}
diff --git a/src/gtk/nautilusgtkplacesviewrow.ui b/src/gtk/nautilusgtkplacesviewrow.ui
new file mode 100644
index 0000000..06c8041
--- /dev/null
+++ b/src/gtk/nautilusgtkplacesviewrow.ui
@@ -0,0 +1,83 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface domain="gtk40">
+ <template class="NautilusGtkPlacesViewRow" parent="GtkListBoxRow">
+ <property name="width-request">100</property>
+ <property name="child">
+ <object class="GtkBox" id="box">
+ <property name="margin-start">12</property>
+ <property name="margin-end">12</property>
+ <property name="margin-top">6</property>
+ <property name="margin-bottom">6</property>
+ <property name="spacing">18</property>
+ <child>
+ <object class="GtkImage" id="icon_image">
+ <property name="pixel-size">32</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkLabel" id="name_label">
+ <property name="hexpand">1</property>
+ <property name="xalign">0</property>
+ <property name="ellipsize">3</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkLabel" id="available_space_label">
+ <property name="xalign">1</property>
+ <style>
+ <class name="dim-label"/>
+ </style>
+ </object>
+ </child>
+ <child>
+ <object class="GtkLabel" id="path_label">
+ <property name="justify">1</property>
+ <property name="ellipsize">2</property>
+ <property name="xalign">0</property>
+ <property name="max-width-chars">15</property>
+ <style>
+ <class name="dim-label"/>
+ </style>
+ </object>
+ </child>
+ <child>
+ <object class="GtkStack" id="mount_stack">
+ <child>
+ <object class="GtkStackPage">
+ <property name="name">button</property>
+ <property name="child">
+ <object class="GtkButton" id="eject_button">
+ <property name="visible">0</property>
+ <property name="halign">3</property>
+ <property name="valign">3</property>
+ <property name="tooltip-text" translatable="yes">Unmount</property>
+ <child>
+ <object class="GtkImage" id="eject_icon">
+ <property name="icon-name">media-eject-symbolic</property>
+ </object>
+ </child>
+ <style>
+ <class name="image-button"/>
+ <class name="sidebar-button"/>
+ </style>
+ </object>
+ </property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkStackPage">
+ <property name="name">spinner</property>
+ <property name="child">
+ <object class="GtkSpinner" id="busy_spinner">
+ <property name="halign">3</property>
+ <property name="valign">3</property>
+ </object>
+ </property>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ </property>
+ </template>
+</interface>
diff --git a/src/gtk/nautilusgtkplacesviewrowprivate.h b/src/gtk/nautilusgtkplacesviewrowprivate.h
new file mode 100644
index 0000000..d54b918
--- /dev/null
+++ b/src/gtk/nautilusgtkplacesviewrowprivate.h
@@ -0,0 +1,59 @@
+/* nautilusgtkplacesviewrow.h
+ *
+ * Copyright (C) 2015 Georges Basile Stavracas Neto <georges.stavracas@gmail.com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 2.1 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 Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef NAUTILUS_GTK_PLACES_VIEW_ROW_H
+#define NAUTILUS_GTK_PLACES_VIEW_ROW_H
+
+#if !defined (__GTK_H_INSIDE__) && !defined (GTK_COMPILATION)
+#endif
+
+
+G_BEGIN_DECLS
+
+#define NAUTILUS_TYPE_GTK_PLACES_VIEW_ROW (nautilus_gtk_places_view_row_get_type())
+
+ G_DECLARE_FINAL_TYPE (NautilusGtkPlacesViewRow, nautilus_gtk_places_view_row, NAUTILUS, GTK_PLACES_VIEW_ROW, GtkListBoxRow)
+
+GtkWidget* nautilus_gtk_places_view_row_new (GVolume *volume,
+ GMount *mount);
+
+GtkWidget* nautilus_gtk_places_view_row_get_eject_button (NautilusGtkPlacesViewRow *row);
+
+GMount* nautilus_gtk_places_view_row_get_mount (NautilusGtkPlacesViewRow *row);
+
+GVolume* nautilus_gtk_places_view_row_get_volume (NautilusGtkPlacesViewRow *row);
+
+GFile* nautilus_gtk_places_view_row_get_file (NautilusGtkPlacesViewRow *row);
+
+void nautilus_gtk_places_view_row_set_busy (NautilusGtkPlacesViewRow *row,
+ gboolean is_busy);
+
+gboolean nautilus_gtk_places_view_row_get_is_network (NautilusGtkPlacesViewRow *row);
+
+void nautilus_gtk_places_view_row_set_is_network (NautilusGtkPlacesViewRow *row,
+ gboolean is_network);
+
+void nautilus_gtk_places_view_row_set_path_size_group (NautilusGtkPlacesViewRow *row,
+ GtkSizeGroup *group);
+
+void nautilus_gtk_places_view_row_set_space_size_group (NautilusGtkPlacesViewRow *row,
+ GtkSizeGroup *group);
+
+G_END_DECLS
+
+#endif /* NAUTILUS_GTK_PLACES_VIEW_ROW_H */
diff --git a/src/gtk/nautilusgtksidebarrow.c b/src/gtk/nautilusgtksidebarrow.c
new file mode 100644
index 0000000..9b6ebaf
--- /dev/null
+++ b/src/gtk/nautilusgtksidebarrow.c
@@ -0,0 +1,692 @@
+/* nautilusgtksidebarrow.c
+ *
+ * Copyright (C) 2015 Carlos Soriano <csoriano@gnome.org>
+ *
+ * This file is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of the
+ * License, or (at your option) any later version.
+ *
+ * This file 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+#include <glib/gi18n.h>
+#include <gtk/gtk.h>
+#include "nautilus-enum-types.h"
+#include "nautilus-file.h"
+
+#include "nautilusgtksidebarrowprivate.h"
+/* For section and place type enums */
+#include "nautilusgtkplacessidebarprivate.h"
+
+#include <cloudproviders.h>
+
+struct _NautilusGtkSidebarRow
+{
+ GtkListBoxRow parent_instance;
+ GIcon *start_icon;
+ GIcon *end_icon;
+ GtkWidget *start_icon_widget;
+ GtkWidget *end_icon_widget;
+ char *label;
+ char *tooltip;
+ char *eject_tooltip;
+ GtkWidget *label_widget;
+ gboolean ejectable;
+ GtkWidget *eject_button;
+ int order_index;
+ NautilusGtkPlacesSectionType section_type;
+ NautilusGtkPlacesPlaceType place_type;
+ char *uri;
+ NautilusFile *file;
+ GDrive *drive;
+ GVolume *volume;
+ GMount *mount;
+ GObject *cloud_provider_account;
+ gboolean placeholder;
+ NautilusGtkPlacesSidebar *sidebar;
+ GtkWidget *revealer;
+ GtkWidget *busy_spinner;
+};
+
+G_DEFINE_TYPE (NautilusGtkSidebarRow, nautilus_gtk_sidebar_row, GTK_TYPE_LIST_BOX_ROW)
+
+enum
+{
+ PROP_0,
+ PROP_START_ICON,
+ PROP_END_ICON,
+ PROP_LABEL,
+ PROP_TOOLTIP,
+ PROP_EJECT_TOOLTIP,
+ PROP_EJECTABLE,
+ PROP_SIDEBAR,
+ PROP_ORDER_INDEX,
+ PROP_SECTION_TYPE,
+ PROP_PLACE_TYPE,
+ PROP_URI,
+ PROP_NAUTILUS_FILE,
+ PROP_DRIVE,
+ PROP_VOLUME,
+ PROP_MOUNT,
+ PROP_CLOUD_PROVIDER_ACCOUNT,
+ PROP_PLACEHOLDER,
+ LAST_PROP
+};
+
+static GParamSpec *properties [LAST_PROP];
+
+static void
+cloud_row_update (NautilusGtkSidebarRow *self)
+{
+ CloudProvidersAccount *account;
+ GIcon *end_icon;
+ int provider_status;
+
+ account = CLOUD_PROVIDERS_ACCOUNT (self->cloud_provider_account);
+ provider_status = cloud_providers_account_get_status (account);
+ switch (provider_status)
+ {
+ case CLOUD_PROVIDERS_ACCOUNT_STATUS_IDLE:
+ end_icon = NULL;
+ break;
+
+ case CLOUD_PROVIDERS_ACCOUNT_STATUS_SYNCING:
+ end_icon = g_themed_icon_new ("emblem-synchronizing-symbolic");
+ break;
+
+ case CLOUD_PROVIDERS_ACCOUNT_STATUS_ERROR:
+ end_icon = g_themed_icon_new ("dialog-warning-symbolic");
+ break;
+
+ default:
+ return;
+ }
+
+ g_object_set (self,
+ "label", cloud_providers_account_get_name (account),
+ NULL);
+ g_object_set (self,
+ "tooltip", cloud_providers_account_get_status_details (account),
+ NULL);
+ g_object_set (self,
+ "end-icon", end_icon,
+ NULL);
+
+ if (end_icon != NULL)
+ g_object_unref (end_icon);
+}
+
+static void
+nautilus_gtk_sidebar_row_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ NautilusGtkSidebarRow *self = NAUTILUS_GTK_SIDEBAR_ROW (object);
+
+ switch (prop_id)
+ {
+ case PROP_SIDEBAR:
+ g_value_set_object (value, self->sidebar);
+ break;
+
+ case PROP_START_ICON:
+ g_value_set_object (value, self->start_icon);
+ break;
+
+ case PROP_END_ICON:
+ g_value_set_object (value, self->end_icon);
+ break;
+
+ case PROP_LABEL:
+ g_value_set_string (value, self->label);
+ break;
+
+ case PROP_TOOLTIP:
+ g_value_set_string (value, self->tooltip);
+ break;
+
+ case PROP_EJECT_TOOLTIP:
+ g_value_set_string (value, self->eject_tooltip);
+ break;
+
+ case PROP_EJECTABLE:
+ g_value_set_boolean (value, self->ejectable);
+ break;
+
+ case PROP_ORDER_INDEX:
+ g_value_set_int (value, self->order_index);
+ break;
+
+ case PROP_SECTION_TYPE:
+ g_value_set_enum (value, self->section_type);
+ break;
+
+ case PROP_PLACE_TYPE:
+ g_value_set_enum (value, self->place_type);
+ break;
+
+ case PROP_URI:
+ g_value_set_string (value, self->uri);
+ break;
+
+ case PROP_NAUTILUS_FILE:
+ g_value_set_object (value, self->file);
+ break;
+
+ case PROP_DRIVE:
+ g_value_set_object (value, self->drive);
+ break;
+
+ case PROP_VOLUME:
+ g_value_set_object (value, self->volume);
+ break;
+
+ case PROP_MOUNT:
+ g_value_set_object (value, self->mount);
+ break;
+
+ case PROP_CLOUD_PROVIDER_ACCOUNT:
+ g_value_set_object (value, self->cloud_provider_account);
+ break;
+
+ case PROP_PLACEHOLDER:
+ g_value_set_boolean (value, self->placeholder);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+}
+
+static void
+nautilus_gtk_sidebar_row_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ NautilusGtkSidebarRow *self = NAUTILUS_GTK_SIDEBAR_ROW (object);
+
+ switch (prop_id)
+ {
+ case PROP_SIDEBAR:
+ self->sidebar = g_value_get_object (value);
+ break;
+
+ case PROP_START_ICON:
+ {
+ g_clear_object (&self->start_icon);
+ object = g_value_get_object (value);
+ if (object != NULL)
+ {
+ self->start_icon = G_ICON (g_object_ref (object));
+ gtk_image_set_from_gicon (GTK_IMAGE (self->start_icon_widget), self->start_icon);
+ }
+ else
+ {
+ gtk_image_clear (GTK_IMAGE (self->start_icon_widget));
+ }
+ break;
+ }
+
+ case PROP_END_ICON:
+ {
+ g_clear_object (&self->end_icon);
+ object = g_value_get_object (value);
+ if (object != NULL)
+ {
+ self->end_icon = G_ICON (g_object_ref (object));
+ gtk_image_set_from_gicon (GTK_IMAGE (self->end_icon_widget), self->end_icon);
+ gtk_widget_show (self->end_icon_widget);
+ }
+ else
+ {
+ gtk_image_clear (GTK_IMAGE (self->end_icon_widget));
+ gtk_widget_hide (self->end_icon_widget);
+ }
+ break;
+ }
+
+ case PROP_LABEL:
+ g_free (self->label);
+ self->label = g_strdup (g_value_get_string (value));
+ gtk_label_set_text (GTK_LABEL (self->label_widget), self->label);
+ break;
+
+ case PROP_TOOLTIP:
+ g_free (self->tooltip);
+ self->tooltip = g_strdup (g_value_get_string (value));
+ gtk_widget_set_tooltip_text (GTK_WIDGET (self), self->tooltip);
+ break;
+
+ case PROP_EJECT_TOOLTIP:
+ g_free (self->eject_tooltip);
+ self->eject_tooltip = g_strdup (g_value_get_string (value));
+ gtk_widget_set_tooltip_text (GTK_WIDGET (self->eject_button), self->eject_tooltip);
+ break;
+
+ case PROP_EJECTABLE:
+ self->ejectable = g_value_get_boolean (value);
+ if (self->ejectable)
+ gtk_widget_show (self->eject_button);
+ else
+ gtk_widget_hide (self->eject_button);
+ break;
+
+ case PROP_ORDER_INDEX:
+ self->order_index = g_value_get_int (value);
+ break;
+
+ case PROP_SECTION_TYPE:
+ self->section_type = g_value_get_enum (value);
+ if (self->section_type == NAUTILUS_GTK_PLACES_SECTION_COMPUTER ||
+ self->section_type == NAUTILUS_GTK_PLACES_SECTION_OTHER_LOCATIONS)
+ gtk_label_set_ellipsize (GTK_LABEL (self->label_widget), PANGO_ELLIPSIZE_NONE);
+ else
+ gtk_label_set_ellipsize (GTK_LABEL (self->label_widget), PANGO_ELLIPSIZE_END);
+ break;
+
+ case PROP_PLACE_TYPE:
+ self->place_type = g_value_get_enum (value);
+ break;
+
+ case PROP_URI:
+ g_free (self->uri);
+ self->uri = g_strdup (g_value_get_string (value));
+ if (self->uri != NULL)
+ {
+ self->file = nautilus_file_get_by_uri (self->uri);
+ if (self->file != NULL)
+ nautilus_file_call_when_ready (self->file, NAUTILUS_FILE_ATTRIBUTE_MOUNT, NULL, NULL);
+ }
+ break;
+
+ case PROP_DRIVE:
+ g_set_object (&self->drive, g_value_get_object (value));
+ break;
+
+ case PROP_VOLUME:
+ g_set_object (&self->volume, g_value_get_object (value));
+ break;
+
+ case PROP_MOUNT:
+ g_set_object (&self->mount, g_value_get_object (value));
+ break;
+
+ case PROP_CLOUD_PROVIDER_ACCOUNT:
+ if (self->cloud_provider_account != NULL)
+ g_signal_handlers_disconnect_by_data (self->cloud_provider_account, self);
+
+ self->cloud_provider_account = g_value_dup_object (value);
+
+ if (self->cloud_provider_account != NULL)
+ {
+ g_signal_connect_swapped (self->cloud_provider_account, "notify::name",
+ G_CALLBACK (cloud_row_update), self);
+ g_signal_connect_swapped (self->cloud_provider_account, "notify::status",
+ G_CALLBACK (cloud_row_update), self);
+ g_signal_connect_swapped (self->cloud_provider_account, "notify::status-details",
+ G_CALLBACK (cloud_row_update), self);
+ }
+ break;
+
+ case PROP_PLACEHOLDER:
+ {
+ self->placeholder = g_value_get_boolean (value);
+ if (self->placeholder)
+ {
+ g_clear_object (&self->start_icon);
+ g_clear_object (&self->end_icon);
+ g_free (self->label);
+ self->label = NULL;
+ g_free (self->tooltip);
+ self->tooltip = NULL;
+ self->eject_tooltip = NULL;
+ gtk_widget_set_tooltip_text (GTK_WIDGET (self), NULL);
+ self->ejectable = FALSE;
+ self->section_type = NAUTILUS_GTK_PLACES_SECTION_BOOKMARKS;
+ self->place_type = NAUTILUS_GTK_PLACES_BOOKMARK_PLACEHOLDER;
+ g_free (self->uri);
+ self->uri = NULL;
+ g_clear_object (&self->drive);
+ g_clear_object (&self->volume);
+ g_clear_object (&self->mount);
+ g_clear_object (&self->cloud_provider_account);
+
+ gtk_list_box_row_set_child (GTK_LIST_BOX_ROW (self), NULL);
+
+ gtk_widget_add_css_class (GTK_WIDGET (self), "sidebar-placeholder-row");
+ }
+
+ break;
+ }
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+}
+
+static void
+on_child_revealed (GObject *self,
+ GParamSpec *pspec,
+ gpointer user_data)
+{
+ /* We need to hide the actual widget because if not the GtkListBoxRow will
+ * still allocate the paddings, even if the revealer is not revealed, and
+ * therefore the row will be still somewhat visible. */
+ if (!gtk_revealer_get_reveal_child (GTK_REVEALER (self)))
+ gtk_widget_hide (GTK_WIDGET (NAUTILUS_GTK_SIDEBAR_ROW (user_data)));
+}
+
+void
+nautilus_gtk_sidebar_row_reveal (NautilusGtkSidebarRow *self)
+{
+ gtk_widget_show (GTK_WIDGET (self));
+ gtk_revealer_set_reveal_child (GTK_REVEALER (self->revealer), TRUE);
+}
+
+void
+nautilus_gtk_sidebar_row_hide (NautilusGtkSidebarRow *self,
+ gboolean immediate)
+{
+ guint transition_duration;
+
+ transition_duration = gtk_revealer_get_transition_duration (GTK_REVEALER (self->revealer));
+ if (immediate)
+ gtk_revealer_set_transition_duration (GTK_REVEALER (self->revealer), 0);
+
+ gtk_revealer_set_reveal_child (GTK_REVEALER (self->revealer), FALSE);
+
+ gtk_revealer_set_transition_duration (GTK_REVEALER (self->revealer), transition_duration);
+}
+
+void
+nautilus_gtk_sidebar_row_set_start_icon (NautilusGtkSidebarRow *self,
+ GIcon *icon)
+{
+ g_return_if_fail (NAUTILUS_IS_GTK_SIDEBAR_ROW (self));
+
+ if (self->start_icon != icon)
+ {
+ g_set_object (&self->start_icon, icon);
+ if (self->start_icon != NULL)
+ gtk_image_set_from_gicon (GTK_IMAGE (self->start_icon_widget), self->start_icon);
+ else
+ gtk_image_clear (GTK_IMAGE (self->start_icon_widget));
+
+ g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_START_ICON]);
+ }
+}
+
+void
+nautilus_gtk_sidebar_row_set_end_icon (NautilusGtkSidebarRow *self,
+ GIcon *icon)
+{
+ g_return_if_fail (NAUTILUS_IS_GTK_SIDEBAR_ROW (self));
+
+ if (self->end_icon != icon)
+ {
+ g_set_object (&self->end_icon, icon);
+ if (self->end_icon != NULL)
+ gtk_image_set_from_gicon (GTK_IMAGE (self->end_icon_widget), self->end_icon);
+ else
+ if (self->end_icon_widget != NULL)
+ gtk_image_clear (GTK_IMAGE (self->end_icon_widget));
+
+ g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_END_ICON]);
+ }
+}
+
+static void
+nautilus_gtk_sidebar_row_finalize (GObject *object)
+{
+ NautilusGtkSidebarRow *self = NAUTILUS_GTK_SIDEBAR_ROW (object);
+
+ g_clear_object (&self->start_icon);
+ g_clear_object (&self->end_icon);
+ g_free (self->label);
+ self->label = NULL;
+ g_free (self->tooltip);
+ self->tooltip = NULL;
+ g_free (self->eject_tooltip);
+ self->eject_tooltip = NULL;
+ g_free (self->uri);
+ self->uri = NULL;
+ nautilus_file_unref (self->file);
+ g_clear_object (&self->drive);
+ g_clear_object (&self->volume);
+ g_clear_object (&self->mount);
+ if (self->cloud_provider_account != NULL)
+ g_signal_handlers_disconnect_by_data (self->cloud_provider_account, self);
+ g_clear_object (&self->cloud_provider_account);
+
+ G_OBJECT_CLASS (nautilus_gtk_sidebar_row_parent_class)->finalize (object);
+}
+
+static void
+nautilus_gtk_sidebar_row_init (NautilusGtkSidebarRow *self)
+{
+ gtk_widget_init_template (GTK_WIDGET (self));
+
+ gtk_widget_set_focus_on_click (GTK_WIDGET (self), FALSE);
+
+ self->file = NULL;
+}
+
+static void
+nautilus_gtk_sidebar_row_class_init (NautilusGtkSidebarRowClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+
+ object_class->get_property = nautilus_gtk_sidebar_row_get_property;
+ object_class->set_property = nautilus_gtk_sidebar_row_set_property;
+ object_class->finalize = nautilus_gtk_sidebar_row_finalize;
+
+ properties [PROP_SIDEBAR] =
+ g_param_spec_object ("sidebar",
+ "Sidebar",
+ "Sidebar",
+ NAUTILUS_TYPE_GTK_PLACES_SIDEBAR,
+ (G_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT_ONLY |
+ G_PARAM_STATIC_STRINGS));
+
+ properties [PROP_START_ICON] =
+ g_param_spec_object ("start-icon",
+ "start-icon",
+ "The start icon.",
+ G_TYPE_ICON,
+ (G_PARAM_READWRITE |
+ G_PARAM_STATIC_STRINGS));
+
+ properties [PROP_END_ICON] =
+ g_param_spec_object ("end-icon",
+ "end-icon",
+ "The end icon.",
+ G_TYPE_ICON,
+ (G_PARAM_READWRITE |
+ G_PARAM_STATIC_STRINGS));
+
+ properties [PROP_LABEL] =
+ g_param_spec_string ("label",
+ "label",
+ "The label text.",
+ NULL,
+ (G_PARAM_READWRITE |
+ G_PARAM_STATIC_STRINGS));
+
+ properties [PROP_TOOLTIP] =
+ g_param_spec_string ("tooltip",
+ "Tooltip",
+ "Tooltip",
+ NULL,
+ (G_PARAM_READWRITE |
+ G_PARAM_STATIC_STRINGS));
+
+ properties [PROP_EJECT_TOOLTIP] =
+ g_param_spec_string ("eject-tooltip",
+ "Eject Tooltip",
+ "Eject Tooltip",
+ NULL,
+ (G_PARAM_READWRITE |
+ G_PARAM_STATIC_STRINGS));
+
+ properties [PROP_EJECTABLE] =
+ g_param_spec_boolean ("ejectable",
+ "Ejectable",
+ "Ejectable",
+ FALSE,
+ (G_PARAM_READWRITE |
+ G_PARAM_STATIC_STRINGS));
+
+ properties [PROP_ORDER_INDEX] =
+ g_param_spec_int ("order-index",
+ "OrderIndex",
+ "Order Index",
+ 0, G_MAXINT, 0,
+ (G_PARAM_READWRITE |
+ G_PARAM_STATIC_STRINGS));
+
+ properties [PROP_SECTION_TYPE] =
+ g_param_spec_enum ("section-type",
+ "section type",
+ "The section type.",
+ NAUTILUS_TYPE_GTK_PLACES_SECTION_TYPE,
+ NAUTILUS_GTK_PLACES_SECTION_INVALID,
+ (G_PARAM_READWRITE |
+ G_PARAM_STATIC_STRINGS |
+ G_PARAM_CONSTRUCT_ONLY));
+
+ properties [PROP_PLACE_TYPE] =
+ g_param_spec_enum ("place-type",
+ "place type",
+ "The place type.",
+ NAUTILUS_TYPE_GTK_PLACES_PLACE_TYPE,
+ NAUTILUS_GTK_PLACES_INVALID,
+ (G_PARAM_READWRITE |
+ G_PARAM_STATIC_STRINGS |
+ G_PARAM_CONSTRUCT_ONLY));
+
+ properties [PROP_URI] =
+ g_param_spec_string ("uri",
+ "Uri",
+ "Uri",
+ NULL,
+ (G_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT_ONLY |
+ G_PARAM_STATIC_STRINGS));
+
+ properties [PROP_NAUTILUS_FILE] =
+ g_param_spec_object ("file",
+ "File",
+ "Nautilus File",
+ NAUTILUS_TYPE_FILE,
+ (G_PARAM_READABLE |
+ G_PARAM_STATIC_STRINGS ));
+
+ properties [PROP_DRIVE] =
+ g_param_spec_object ("drive",
+ "Drive",
+ "Drive",
+ G_TYPE_DRIVE,
+ (G_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT_ONLY |
+ G_PARAM_STATIC_STRINGS));
+
+ properties [PROP_VOLUME] =
+ g_param_spec_object ("volume",
+ "Volume",
+ "Volume",
+ G_TYPE_VOLUME,
+ (G_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT_ONLY |
+ G_PARAM_STATIC_STRINGS));
+
+ properties [PROP_MOUNT] =
+ g_param_spec_object ("mount",
+ "Mount",
+ "Mount",
+ G_TYPE_MOUNT,
+ (G_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT_ONLY |
+ G_PARAM_STATIC_STRINGS));
+
+ properties [PROP_CLOUD_PROVIDER_ACCOUNT] =
+ g_param_spec_object ("cloud-provider-account",
+ "CloudProvidersAccount",
+ "CloudProvidersAccount",
+ G_TYPE_OBJECT,
+ (G_PARAM_READWRITE |
+ G_PARAM_STATIC_STRINGS));
+
+ properties [PROP_PLACEHOLDER] =
+ g_param_spec_boolean ("placeholder",
+ "Placeholder",
+ "Placeholder",
+ FALSE,
+ (G_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT_ONLY |
+ G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_properties (object_class, LAST_PROP, properties);
+
+ gtk_widget_class_set_template_from_resource (widget_class,
+ "/org/gnome/nautilus/gtk/ui/nautilusgtksidebarrow.ui");
+
+ gtk_widget_class_bind_template_child (widget_class, NautilusGtkSidebarRow, start_icon_widget);
+ gtk_widget_class_bind_template_child (widget_class, NautilusGtkSidebarRow, end_icon_widget);
+ gtk_widget_class_bind_template_child (widget_class, NautilusGtkSidebarRow, label_widget);
+ gtk_widget_class_bind_template_child (widget_class, NautilusGtkSidebarRow, eject_button);
+ gtk_widget_class_bind_template_child (widget_class, NautilusGtkSidebarRow, revealer);
+ gtk_widget_class_bind_template_child (widget_class, NautilusGtkSidebarRow, busy_spinner);
+
+ gtk_widget_class_bind_template_callback (widget_class, on_child_revealed);
+ gtk_widget_class_set_css_name (widget_class, "row");
+}
+
+NautilusGtkSidebarRow*
+nautilus_gtk_sidebar_row_clone (NautilusGtkSidebarRow *self)
+{
+ return g_object_new (NAUTILUS_TYPE_GTK_SIDEBAR_ROW,
+ "sidebar", self->sidebar,
+ "start-icon", self->start_icon,
+ "end-icon", self->end_icon,
+ "label", self->label,
+ "tooltip", self->tooltip,
+ "eject-tooltip", self->eject_tooltip,
+ "ejectable", self->ejectable,
+ "order-index", self->order_index,
+ "section-type", self->section_type,
+ "place-type", self->place_type,
+ "uri", self->uri,
+ "file", self->file,
+ "drive", self->drive,
+ "volume", self->volume,
+ "mount", self->mount,
+ "cloud-provider-account", self->cloud_provider_account,
+ NULL);
+}
+
+GtkWidget*
+nautilus_gtk_sidebar_row_get_eject_button (NautilusGtkSidebarRow *self)
+{
+ return self->eject_button;
+}
+
+void
+nautilus_gtk_sidebar_row_set_busy (NautilusGtkSidebarRow *row,
+ gboolean is_busy)
+{
+ g_return_if_fail (NAUTILUS_IS_GTK_SIDEBAR_ROW (row));
+
+ gtk_widget_set_visible (row->busy_spinner, is_busy);
+}
diff --git a/src/gtk/nautilusgtksidebarrow.ui b/src/gtk/nautilusgtksidebarrow.ui
new file mode 100644
index 0000000..95d91f9
--- /dev/null
+++ b/src/gtk/nautilusgtksidebarrow.ui
@@ -0,0 +1,69 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface domain="gtk40">
+ <template class="NautilusGtkSidebarRow" parent="GtkListBoxRow">
+ <property name="focus-on-click">0</property>
+ <style>
+ <class name="sidebar-row"/>
+ </style>
+ <property name="child">
+ <object class="GtkRevealer" id="revealer">
+ <property name="reveal-child">1</property>
+ <signal name="notify::child-revealed" handler="on_child_revealed"/>
+ <style>
+ <class name="sidebar-revealer"/>
+ </style>
+ <child>
+ <object class="GtkBox">
+ <child>
+ <object class="GtkImage" id="start_icon_widget">
+ <style>
+ <class name="sidebar-icon"/>
+ </style>
+ </object>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label_widget">
+ <property name="hexpand">1</property>
+ <property name="xalign">0</property>
+ <style>
+ <class name="sidebar-label"/>
+ </style>
+ </object>
+ </child>
+ <child>
+ <object class="GtkImage" id="end_icon_widget">
+ <property name="visible">0</property>
+ <property name="hexpand">1</property>
+ <property name="halign">2</property>
+ <property name="valign">3</property>
+ <style>
+ <class name="sidebar-icon"/>
+ </style>
+ </object>
+ </child>
+ <child>
+ <object class="GtkButton" id="eject_button">
+ <property name="halign">3</property>
+ <property name="valign">3</property>
+ <property name="margin-start">4px</property>
+ <property name="icon-name">media-eject-symbolic</property>
+ <style>
+ <class name="sidebar-button"/>
+ </style>
+ </object>
+ </child>
+ <child>
+ <object class="GtkSpinner" id="busy_spinner">
+ <property name="spinning">1</property>
+ <property name="halign">3</property>
+ <property name="valign">3</property>
+ <property name="margin-start">4px</property>
+ <property name="visible">0</property>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ </property>
+ </template>
+</interface>
diff --git a/src/gtk/nautilusgtksidebarrowprivate.h b/src/gtk/nautilusgtksidebarrowprivate.h
new file mode 100644
index 0000000..0bd9355
--- /dev/null
+++ b/src/gtk/nautilusgtksidebarrowprivate.h
@@ -0,0 +1,60 @@
+/* nautilusgtksidebarrowprivate.h
+ *
+ * Copyright (C) 2015 Carlos Soriano <csoriano@gnome.org>
+ *
+ * This file is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of the
+ * License, or (at your option) any later version.
+ *
+ * This file 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+#ifndef NAUTILUS_GTK_SIDEBAR_ROW_PRIVATE_H
+#define NAUTILUS_GTK_SIDEBAR_ROW_PRIVATE_H
+
+#include <glib.h>
+
+G_BEGIN_DECLS
+
+#define NAUTILUS_TYPE_GTK_SIDEBAR_ROW (nautilus_gtk_sidebar_row_get_type())
+#define NAUTILUS_GTK_SIDEBAR_ROW(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), NAUTILUS_TYPE_GTK_SIDEBAR_ROW, NautilusGtkSidebarRow))
+#define NAUTILUS_GTK_SIDEBAR_ROW_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), NAUTILUS_TYPE_GTK_SIDEBAR_ROW, NautilusGtkSidebarRowClass))
+#define NAUTILUS_IS_GTK_SIDEBAR_ROW(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), NAUTILUS_TYPE_GTK_SIDEBAR_ROW))
+#define NAUTILUS_IS_GTK_SIDEBAR_ROW_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), NAUTILUS_TYPE_GTK_SIDEBAR_ROW))
+#define NAUTILUS_GTK_SIDEBAR_ROW_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), NAUTILUS_TYPE_GTK_SIDEBAR_ROW, NautilusGtkSidebarRowClass))
+
+typedef struct _NautilusGtkSidebarRow NautilusGtkSidebarRow;
+typedef struct _NautilusGtkSidebarRowClass NautilusGtkSidebarRowClass;
+
+struct _NautilusGtkSidebarRowClass
+{
+ GtkListBoxRowClass parent;
+};
+
+GType nautilus_gtk_sidebar_row_get_type (void) G_GNUC_CONST;
+
+NautilusGtkSidebarRow *nautilus_gtk_sidebar_row_new (void);
+NautilusGtkSidebarRow *nautilus_gtk_sidebar_row_clone (NautilusGtkSidebarRow *self);
+
+/* Use these methods instead of gtk_widget_hide/show to use an animation */
+void nautilus_gtk_sidebar_row_hide (NautilusGtkSidebarRow *self,
+ gboolean immediate);
+void nautilus_gtk_sidebar_row_reveal (NautilusGtkSidebarRow *self);
+
+GtkWidget *nautilus_gtk_sidebar_row_get_eject_button (NautilusGtkSidebarRow *self);
+void nautilus_gtk_sidebar_row_set_start_icon (NautilusGtkSidebarRow *self,
+ GIcon *icon);
+void nautilus_gtk_sidebar_row_set_end_icon (NautilusGtkSidebarRow *self,
+ GIcon *icon);
+void nautilus_gtk_sidebar_row_set_busy (NautilusGtkSidebarRow *row,
+ gboolean is_busy);
+
+G_END_DECLS
+
+#endif /* NAUTILUS_GTK_SIDEBAR_ROW_PRIVATE_H */
diff --git a/src/meson.build b/src/meson.build
new file mode 100644
index 0000000..383920c
--- /dev/null
+++ b/src/meson.build
@@ -0,0 +1,345 @@
+resources = gnome.compile_resources(
+ 'nautilus-resources',
+ join_paths(
+ 'resources', 'nautilus.gresource.xml'
+ ),
+ source_dir: 'resources',
+ c_name: 'nautilus',
+ extra_args: '--manual-register'
+)
+
+libnautilus_sources = [
+ gnome.mkenums(
+ 'nautilus-enum-types',
+ c_template: 'nautilus-enum-types.c.template',
+ h_template: 'nautilus-enum-types.h.template',
+ sources: [
+ 'gtk/nautilusgtkplacessidebarprivate.h',
+ 'nautilus-enums.h',
+ 'nautilus-search-popover.h',
+ 'nautilus-special-location-bar.h',
+ 'nautilus-query.h',
+ 'nautilus-search-provider.h'
+ ]
+ ),
+ resources,
+ gnome.gdbus_codegen(
+ 'nautilus-freedesktop-generated',
+ join_paths(
+ meson.project_source_root(), 'data', 'freedesktop-dbus-interfaces.xml'
+ ),
+ interface_prefix: 'org.freedesktop',
+ namespace: 'NautilusFreedesktop'
+ ),
+ gnome.gdbus_codegen(
+ 'nautilus-generated',
+ join_paths(
+ meson.project_source_root(), 'data', 'dbus-interfaces.xml'
+ ),
+ interface_prefix: 'org.gnome.Nautilus',
+ namespace: 'NautilusDBus'
+ ),
+ gnome.gdbus_codegen(
+ 'nautilus-generated2',
+ join_paths(
+ meson.project_source_root(), 'data', 'dbus-interfaces2.xml'
+ ),
+ interface_prefix: 'org.gnome.Nautilus',
+ namespace: 'NautilusDBus'
+ ),
+ gnome.gdbus_codegen(
+ 'nautilus-shell-search-provider-generated',
+ join_paths(
+ meson.project_source_root(), 'data', 'shell-search-provider-dbus-interfaces.xml'
+ ),
+ interface_prefix: 'org.gnome',
+ namespace: 'Nautilus'
+ ),
+ 'gtk/nautilusgtkbookmarksmanager.c',
+ 'gtk/nautilusgtkbookmarksmanagerprivate.h',
+ 'gtk/nautilusgtkplacessidebar.c',
+ 'gtk/nautilusgtkplacessidebarprivate.h',
+ 'gtk/nautilusgtksidebarrow.c',
+ 'gtk/nautilusgtksidebarrowprivate.h',
+ 'gtk/nautilusgtkplacesview.c',
+ 'gtk/nautilusgtkplacesviewprivate.h',
+ 'gtk/nautilusgtkplacesviewrow.c',
+ 'gtk/nautilusgtkplacesviewrowprivate.h',
+ 'nautilus-application.c',
+ 'nautilus-application.h',
+ 'nautilus-app-chooser.c',
+ 'nautilus-app-chooser.h',
+ 'nautilus-bookmark-list.c',
+ 'nautilus-bookmark-list.h',
+ 'nautilus-dbus-manager.c',
+ 'nautilus-dbus-manager.h',
+ 'nautilus-error-reporting.c',
+ 'nautilus-error-reporting.h',
+ 'nautilus-preferences-window.c',
+ 'nautilus-preferences-window.h',
+ 'nautilus-files-view.c',
+ 'nautilus-files-view.h',
+ 'nautilus-files-view-dnd.c',
+ 'nautilus-files-view-dnd.h',
+ 'nautilus-floating-bar.c',
+ 'nautilus-floating-bar.h',
+ 'nautilus-freedesktop-dbus.c',
+ 'nautilus-freedesktop-dbus.h',
+ 'nautilus-grid-cell.c',
+ 'nautilus-grid-cell.h',
+ 'nautilus-grid-view.c',
+ 'nautilus-grid-view.h',
+ 'nautilus-history-controls.c',
+ 'nautilus-history-controls.h',
+ 'nautilus-label-cell.c',
+ 'nautilus-label-cell.h',
+ 'nautilus-list-base.c',
+ 'nautilus-list-base.h',
+ 'nautilus-list-view.c',
+ 'nautilus-list-view.h',
+ 'nautilus-location-entry.c',
+ 'nautilus-location-entry.h',
+ 'nautilus-mime-actions.c',
+ 'nautilus-mime-actions.h',
+ 'nautilus-name-cell.c',
+ 'nautilus-name-cell.h',
+ 'nautilus-pathbar.c',
+ 'nautilus-pathbar.h',
+ 'nautilus-places-view.c',
+ 'nautilus-places-view.h',
+ 'nautilus-previewer.c',
+ 'nautilus-previewer.h',
+ 'nautilus-progress-indicator.c',
+ 'nautilus-progress-indicator.h',
+ 'nautilus-progress-info-widget.c',
+ 'nautilus-progress-info-widget.h',
+ 'nautilus-progress-persistence-handler.c',
+ 'nautilus-progress-persistence-handler.h',
+ 'nautilus-properties-window.c',
+ 'nautilus-properties-window.h',
+ 'nautilus-query-editor.c',
+ 'nautilus-query-editor.h',
+ 'nautilus-search-popover.c',
+ 'nautilus-self-check-functions.c',
+ 'nautilus-self-check-functions.h',
+ 'nautilus-shell-search-provider.c',
+ 'nautilus-special-location-bar.c',
+ 'nautilus-star-cell.c',
+ 'nautilus-star-cell.h',
+ 'nautilus-toolbar.c',
+ 'nautilus-toolbar.h',
+ 'nautilus-toolbar-menu-sections.h',
+ 'nautilus-view.c',
+ 'nautilus-view.h',
+ 'nautilus-view-cell.c',
+ 'nautilus-view-cell.h',
+ 'nautilus-view-controls.c',
+ 'nautilus-view-controls.h',
+ 'nautilus-view-item.c',
+ 'nautilus-view-item.h',
+ 'nautilus-view-model.c',
+ 'nautilus-view-model.h',
+ 'nautilus-window-slot.c',
+ 'nautilus-window-slot.h',
+ 'nautilus-window-slot-dnd.c',
+ 'nautilus-window-slot-dnd.h',
+ 'nautilus-window.c',
+ 'nautilus-window.h',
+ 'nautilus-x-content-bar.c',
+ 'nautilus-x-content-bar.h',
+ 'nautilus-bookmark.c',
+ 'nautilus-bookmark.h',
+ 'nautilus-clipboard.c',
+ 'nautilus-clipboard.h',
+ 'nautilus-column-chooser.c',
+ 'nautilus-column-chooser.h',
+ 'nautilus-column-utilities.c',
+ 'nautilus-column-utilities.h',
+ 'nautilus-dbus-launcher.c',
+ 'nautilus-dbus-launcher.h',
+ 'nautilus-debug.c',
+ 'nautilus-debug.h',
+ 'nautilus-directory-async.c',
+ 'nautilus-directory-notify.h',
+ 'nautilus-directory-private.h',
+ 'nautilus-directory.c',
+ 'nautilus-directory.h',
+ 'nautilus-dnd.c',
+ 'nautilus-dnd.h',
+ 'nautilus-file-changes-queue.c',
+ 'nautilus-file-changes-queue.h',
+ 'nautilus-file-conflict-dialog.c',
+ 'nautilus-file-conflict-dialog.h',
+ 'nautilus-file-name-widget-controller.c',
+ 'nautilus-file-name-widget-controller.h',
+ 'nautilus-rename-file-popover-controller.c',
+ 'nautilus-rename-file-popover-controller.h',
+ 'nautilus-new-folder-dialog-controller.c',
+ 'nautilus-new-folder-dialog-controller.h',
+ 'nautilus-compress-dialog-controller.c',
+ 'nautilus-compress-dialog-controller.h',
+ 'nautilus-operations-ui-manager.c',
+ 'nautilus-operations-ui-manager.h',
+ 'nautilus-file-operations.c',
+ 'nautilus-file-operations.h',
+ 'nautilus-file-operations-dbus-data.c',
+ 'nautilus-file-operations-dbus-data.h',
+ 'nautilus-file-private.h',
+ 'nautilus-file-queue.c',
+ 'nautilus-file-queue.h',
+ 'nautilus-file-utilities.c',
+ 'nautilus-file-utilities.h',
+ 'nautilus-file.c',
+ 'nautilus-file.h',
+ 'nautilus-global-preferences.c',
+ 'nautilus-global-preferences.h',
+ 'nautilus-icon-info.c',
+ 'nautilus-icon-info.h',
+ 'nautilus-icon-names.h',
+ 'nautilus-keyfile-metadata.c',
+ 'nautilus-keyfile-metadata.h',
+ 'nautilus-lib-self-check-functions.c',
+ 'nautilus-lib-self-check-functions.h',
+ 'nautilus-metadata.h',
+ 'nautilus-metadata.c',
+ 'nautilus-module.c',
+ 'nautilus-module.h',
+ 'nautilus-monitor.c',
+ 'nautilus-monitor.h',
+ 'nautilus-profile.c',
+ 'nautilus-profile.h',
+ 'nautilus-progress-info.c',
+ 'nautilus-progress-info.h',
+ 'nautilus-progress-info-manager.c',
+ 'nautilus-progress-info-manager.h',
+ 'nautilus-program-choosing.c',
+ 'nautilus-program-choosing.h',
+ 'nautilus-search-directory.c',
+ 'nautilus-search-directory.h',
+ 'nautilus-search-directory-file.c',
+ 'nautilus-search-directory-file.h',
+ 'nautilus-search-provider.c',
+ 'nautilus-search-provider.h',
+ 'nautilus-search-engine.c',
+ 'nautilus-search-engine.h',
+ 'nautilus-search-engine-private.h',
+ 'nautilus-search-engine-model.c',
+ 'nautilus-search-engine-model.h',
+ 'nautilus-search-engine-recent.c',
+ 'nautilus-search-engine-recent.h',
+ 'nautilus-search-engine-simple.c',
+ 'nautilus-search-engine-simple.h',
+ 'nautilus-search-hit.c',
+ 'nautilus-search-hit.h',
+ 'nautilus-signaller.h',
+ 'nautilus-signaller.c',
+ 'nautilus-query.c',
+ 'nautilus-thumbnails.c',
+ 'nautilus-thumbnails.h',
+ 'nautilus-trash-monitor.c',
+ 'nautilus-trash-monitor.h',
+ 'nautilus-ui-utilities.c',
+ 'nautilus-ui-utilities.h',
+ 'nautilus-video-mime-types.h',
+ 'nautilus-vfs-directory.c',
+ 'nautilus-vfs-directory.h',
+ 'nautilus-vfs-file.c',
+ 'nautilus-vfs-file.h',
+ 'nautilus-file-undo-operations.c',
+ 'nautilus-file-undo-operations.h',
+ 'nautilus-file-undo-manager.c',
+ 'nautilus-file-undo-manager.h',
+ 'nautilus-batch-rename-dialog.c',
+ 'nautilus-batch-rename-dialog.h',
+ 'nautilus-batch-rename-utilities.c',
+ 'nautilus-batch-rename-utilities.h',
+ 'nautilus-search-engine-tracker.c',
+ 'nautilus-search-engine-tracker.h',
+ 'nautilus-tag-manager.c',
+ 'nautilus-tag-manager.h',
+ 'nautilus-starred-directory.c',
+ 'nautilus-starred-directory.h',
+ 'nautilus-enums.h',
+ 'nautilus-types.h',
+ 'nautilus-tracker-utilities.c',
+ 'nautilus-tracker-utilities.h'
+]
+
+nautilus_deps = [
+ config_h,
+ eel_2,
+ gio_unix,
+ gmodule,
+ gnome_autoar,
+ gnome_desktop,
+ libadwaita,
+ libportal,
+ libportal_gtk4,
+ nautilus_extension,
+ selinux,
+ tracker_sparql,
+ xml,
+ cloudproviders,
+]
+
+libnautilus = static_library(
+ 'nautilus',
+ libnautilus_sources,
+ dependencies: nautilus_deps,
+ include_directories: nautilus_include_dirs
+)
+
+libnautilus_include_dirs = include_directories('.')
+
+libnautilus_dep = declare_dependency(
+ link_with: libnautilus,
+ include_directories: [
+ nautilus_include_dirs,
+ libnautilus_include_dirs
+ ],
+ dependencies: nautilus_deps,
+ # nautilus-main.c, which is part of the main Nautilus executable, uses
+ # the header, generated by glib-compile-resources. Passing it on from here
+ # will ensure that an internal compile-time dependency is placed on this file,
+ # thus avoiding failures that are difficult to reproduce.
+ sources: resources
+)
+
+nautilus = executable(
+ 'nautilus',
+ 'nautilus-main.c',
+ dependencies: libnautilus_dep,
+ install: true
+)
+
+if get_option('tests') == 'all'
+ test(
+ 'nautilus', nautilus,
+ args: [
+ '--check',
+ ],
+ env: [
+ 'G_DEBUG=fatal-warnings',
+ 'GSETTINGS_SCHEMA_DIR=@0@'.format(join_paths(meson.project_build_root(), 'data')),
+ 'RUNNING_TESTS=@0@'.format('TRUE')
+ ]
+ )
+endif
+
+nautilus_autorun_software_sources = [
+ 'nautilus-autorun-software.c',
+ 'nautilus-icon-info.c',
+ 'nautilus-icon-info.h'
+]
+
+executable(
+ 'nautilus-autorun-software',
+ nautilus_autorun_software_sources,
+ include_directories: nautilus_include_dirs,
+ dependencies: [
+ config_h,
+ gtk,
+ libadwaita,
+ ],
+ install: true
+)
diff --git a/src/nautilus-app-chooser.c b/src/nautilus-app-chooser.c
new file mode 100644
index 0000000..e15abc3
--- /dev/null
+++ b/src/nautilus-app-chooser.c
@@ -0,0 +1,320 @@
+/*
+ * Copyright (C) 2022 António Fernandes <antoniof@gnome.org>
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#include "nautilus-app-chooser.h"
+
+#include <libadwaita-1/adwaita.h>
+#include <glib/gi18n.h>
+
+#include <eel/eel-string.h>
+
+#include "nautilus-file.h"
+#include "nautilus-signaller.h"
+
+struct _NautilusAppChooser
+{
+ GtkDialog parent_instance;
+
+ gchar *content_type;
+ gchar *file_name;
+ gboolean single_content_type;
+
+ GtkWidget *app_chooser_widget_box;
+ GtkWidget *label_description;
+ GtkWidget *set_default_row;
+ GtkWidget *set_as_default_switch;
+ GtkWidget *set_default_box;
+
+ GtkWidget *app_chooser_widget;
+};
+
+G_DEFINE_TYPE (NautilusAppChooser, nautilus_app_chooser, GTK_TYPE_DIALOG)
+
+enum
+{
+ PROP_0,
+ PROP_CONTENT_TYPE,
+ PROP_SINGLE_CONTENT_TYPE,
+ PROP_FILE_NAME,
+ LAST_PROP
+};
+
+static void
+open_cb (NautilusAppChooser *self)
+{
+ gboolean set_new_default = FALSE;
+ g_autoptr (GAppInfo) info = NULL;
+ g_autoptr (GError) error = NULL;
+
+ if (!self->single_content_type)
+ {
+ /* Don't attempt to set an association with multiple content types */
+ return;
+ }
+
+ /* The switch is insensitive if the selected app is already default */
+ if (gtk_widget_get_sensitive (self->set_as_default_switch))
+ {
+ set_new_default = gtk_switch_get_active (GTK_SWITCH (self->set_as_default_switch));
+ }
+
+ if (set_new_default)
+ {
+ info = gtk_app_chooser_get_app_info (GTK_APP_CHOOSER (self->app_chooser_widget));
+ g_app_info_set_as_default_for_type (info, self->content_type,
+ &error);
+ g_signal_emit_by_name (nautilus_signaller_get_current (), "mime-data-changed");
+ }
+
+ if (error != NULL)
+ {
+ g_autofree gchar *message = NULL;
+ GtkWidget *message_dialog;
+
+ message = g_strdup_printf (_("Error while setting “%s” as default application: %s"),
+ g_app_info_get_display_name (info), error->message);
+ message_dialog = adw_message_dialog_new (GTK_WINDOW (self),
+ _("Could not set as default"),
+ message);
+ adw_message_dialog_add_response (ADW_MESSAGE_DIALOG (message_dialog), "close", _("OK"));
+ gtk_window_present (GTK_WINDOW (message_dialog));
+ }
+}
+
+static void
+on_application_activated (NautilusAppChooser *self)
+{
+ open_cb (self);
+ gtk_dialog_response (GTK_DIALOG (self), GTK_RESPONSE_OK);
+}
+
+static void
+on_application_selected (GtkAppChooserWidget *widget,
+ GAppInfo *info,
+ gpointer user_data)
+{
+ NautilusAppChooser *self = NAUTILUS_APP_CHOOSER (user_data);
+ g_autoptr (GAppInfo) default_app = NULL;
+ gboolean is_default;
+
+ gtk_dialog_set_response_sensitive (GTK_DIALOG (self), GTK_RESPONSE_OK, info != NULL);
+
+ default_app = g_app_info_get_default_for_type (self->content_type, FALSE);
+ is_default = default_app != NULL && g_app_info_equal (info, default_app);
+
+ gtk_switch_set_state (GTK_SWITCH (self->set_as_default_switch), is_default);
+ gtk_widget_set_sensitive (GTK_WIDGET (self->set_as_default_switch), !is_default);
+}
+
+static void
+nautilus_app_chooser_set_property (GObject *object,
+ guint param_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ NautilusAppChooser *self = NAUTILUS_APP_CHOOSER (object);
+
+ switch (param_id)
+ {
+ case PROP_CONTENT_TYPE:
+ {
+ self->content_type = g_value_dup_string (value);
+ }
+ break;
+
+ case PROP_SINGLE_CONTENT_TYPE:
+ {
+ self->single_content_type = g_value_get_boolean (value);
+ }
+ break;
+
+ case PROP_FILE_NAME:
+ {
+ self->file_name = g_value_dup_string (value);
+ }
+ break;
+
+ default:
+ {
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
+ }
+ break;
+ }
+}
+
+static void
+nautilus_app_chooser_init (NautilusAppChooser *self)
+{
+ gtk_widget_init_template (GTK_WIDGET (self));
+
+ gtk_widget_set_name (GTK_WIDGET (self), "NautilusAppChooser");
+}
+
+static gboolean
+content_type_is_folder (NautilusAppChooser *self)
+{
+ return g_strcmp0 (self->content_type, "inode/directory") == 0;
+}
+
+static void
+nautilus_app_chooser_constructed (GObject *object)
+{
+ NautilusAppChooser *self = NAUTILUS_APP_CHOOSER (object);
+ g_autoptr (GAppInfo) info = NULL;
+ g_autofree gchar *content_type_description = NULL;
+ g_autofree gchar *description = NULL;
+ gchar *title;
+
+ G_OBJECT_CLASS (nautilus_app_chooser_parent_class)->constructed (object);
+
+ self->app_chooser_widget = gtk_app_chooser_widget_new (self->content_type);
+ gtk_widget_set_vexpand (self->app_chooser_widget, TRUE);
+ gtk_widget_add_css_class (self->app_chooser_widget, "lowres-icon");
+ gtk_box_append (GTK_BOX (self->app_chooser_widget_box), self->app_chooser_widget);
+
+ gtk_app_chooser_widget_set_show_default (GTK_APP_CHOOSER_WIDGET (self->app_chooser_widget), TRUE);
+ gtk_app_chooser_widget_set_show_fallback (GTK_APP_CHOOSER_WIDGET (self->app_chooser_widget), TRUE);
+ gtk_app_chooser_widget_set_show_other (GTK_APP_CHOOSER_WIDGET (self->app_chooser_widget), TRUE);
+
+ /* initialize sensitivity */
+ info = gtk_app_chooser_get_app_info (GTK_APP_CHOOSER (self->app_chooser_widget));
+ if (info != NULL)
+ {
+ on_application_selected (GTK_APP_CHOOSER_WIDGET (self->app_chooser_widget),
+ info, self);
+ }
+
+ g_signal_connect_object (self->app_chooser_widget, "application-selected",
+ G_CALLBACK (on_application_selected), self, 0);
+ g_signal_connect_object (self->app_chooser_widget, "application-activated",
+ G_CALLBACK (on_application_activated), self, G_CONNECT_SWAPPED);
+
+ if (self->file_name != NULL)
+ {
+ /* Translators: %s is the filename. i.e. "Choose an application to open test.jpg" */
+ description = g_strdup_printf (_("Choose an application to open <b>%s</b>."), self->file_name);
+ gtk_label_set_markup (GTK_LABEL (self->label_description), description);
+ }
+
+ if (!self->single_content_type)
+ {
+ title = _("Open Items");
+ }
+ else if (content_type_is_folder (self))
+ {
+ title = _("Open Folder");
+ }
+ else
+ {
+ title = _("Open File");
+ }
+
+ gtk_header_bar_set_title_widget (GTK_HEADER_BAR (gtk_dialog_get_header_bar (GTK_DIALOG (self))),
+ adw_window_title_new (title, NULL));
+
+ if (self->single_content_type && !content_type_is_folder (self))
+ {
+ content_type_description = g_content_type_get_description (self->content_type);
+ if (content_type_description != NULL)
+ {
+ g_autofree gchar *capitalized = NULL;
+ capitalized = eel_str_capitalize (content_type_description);
+ adw_action_row_set_subtitle (ADW_ACTION_ROW (self->set_default_row), capitalized);
+ }
+ }
+ else
+ {
+ gtk_widget_set_visible (self->set_default_box, FALSE);
+ }
+}
+
+static void
+nautilus_app_chooser_finalize (GObject *object)
+{
+ NautilusAppChooser *self = (NautilusAppChooser *) object;
+
+ g_clear_pointer (&self->content_type, g_free);
+ g_clear_pointer (&self->file_name, g_free);
+
+ G_OBJECT_CLASS (nautilus_app_chooser_parent_class)->finalize (object);
+}
+
+static void
+nautilus_app_chooser_class_init (NautilusAppChooserClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+
+ object_class->finalize = nautilus_app_chooser_finalize;
+ object_class->constructed = nautilus_app_chooser_constructed;
+ object_class->set_property = nautilus_app_chooser_set_property;
+
+ gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/nautilus/ui/nautilus-app-chooser.ui");
+
+ gtk_widget_class_bind_template_child (widget_class, NautilusAppChooser, app_chooser_widget_box);
+ gtk_widget_class_bind_template_child (widget_class, NautilusAppChooser, set_as_default_switch);
+ gtk_widget_class_bind_template_child (widget_class, NautilusAppChooser, label_description);
+ gtk_widget_class_bind_template_child (widget_class, NautilusAppChooser, set_default_row);
+ gtk_widget_class_bind_template_child (widget_class, NautilusAppChooser, set_default_box);
+
+ gtk_widget_class_bind_template_callback (widget_class, open_cb);
+
+ g_object_class_install_property (object_class,
+ PROP_CONTENT_TYPE,
+ g_param_spec_string ("content-type", "", "",
+ NULL,
+ G_PARAM_CONSTRUCT_ONLY | G_PARAM_WRITABLE));
+
+ g_object_class_install_property (object_class,
+ PROP_FILE_NAME,
+ g_param_spec_string ("file-name", "", "",
+ NULL,
+ G_PARAM_CONSTRUCT_ONLY | G_PARAM_WRITABLE));
+
+ g_object_class_install_property (object_class,
+ PROP_SINGLE_CONTENT_TYPE,
+ g_param_spec_boolean ("single-content-type", "", "",
+ TRUE,
+ G_PARAM_CONSTRUCT_ONLY | G_PARAM_WRITABLE));
+}
+
+NautilusAppChooser *
+nautilus_app_chooser_new (GList *files,
+ GtkWindow *parent_window)
+{
+ gboolean single_content_type = TRUE;
+ g_autofree gchar *content_type = NULL;
+ g_autofree gchar *file_name = NULL;
+
+ content_type = nautilus_file_get_mime_type (files->data);
+
+ file_name = files->next ? NULL : nautilus_file_get_display_name (files->data);
+
+ for (GList *l = files; l != NULL; l = l->next)
+ {
+ g_autofree gchar *temp_mime_type = NULL;
+ temp_mime_type = nautilus_file_get_mime_type (l->data);
+ if (g_strcmp0 (content_type, temp_mime_type) != 0)
+ {
+ single_content_type = FALSE;
+ break;
+ }
+ }
+
+ return NAUTILUS_APP_CHOOSER (g_object_new (NAUTILUS_TYPE_APP_CHOOSER,
+ "transient-for", parent_window,
+ "content-type", content_type,
+ "use-header-bar", TRUE,
+ "file-name", file_name,
+ "single-content-type", single_content_type,
+ NULL));
+}
+
+GAppInfo *
+nautilus_app_chooser_get_app_info (NautilusAppChooser *self)
+{
+ return gtk_app_chooser_get_app_info (GTK_APP_CHOOSER (self->app_chooser_widget));
+}
diff --git a/src/nautilus-app-chooser.h b/src/nautilus-app-chooser.h
new file mode 100644
index 0000000..3131f49
--- /dev/null
+++ b/src/nautilus-app-chooser.h
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2022 António Fernandes <antoniof@gnome.org>
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#pragma once
+
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+#define NAUTILUS_TYPE_APP_CHOOSER (nautilus_app_chooser_get_type())
+
+G_DECLARE_FINAL_TYPE (NautilusAppChooser, nautilus_app_chooser, NAUTILUS, APP_CHOOSER, GtkDialog)
+
+NautilusAppChooser *nautilus_app_chooser_new (GList *files,
+ GtkWindow *parent_window);
+
+GAppInfo *nautilus_app_chooser_get_app_info (NautilusAppChooser *self);
+
+G_END_DECLS
diff --git a/src/nautilus-application.c b/src/nautilus-application.c
new file mode 100644
index 0000000..22bfdb7
--- /dev/null
+++ b/src/nautilus-application.c
@@ -0,0 +1,1542 @@
+/*
+ * nautilus-application: main Nautilus application class.
+ *
+ * Copyright (C) 1999, 2000 Red Hat, Inc.
+ * Copyright (C) 2000, 2001 Eazel, Inc.
+ * Copyright (C) 2010, Cosimo Cecchi <cosimoc@gnome.org>
+ *
+ * Nautilus is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * Nautilus is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Elliot Lee <sopwith@redhat.com>,
+ * Darin Adler <darin@bentspoon.com>
+ * Cosimo Cecchi <cosimoc@gnome.org>
+ *
+ */
+
+#include "nautilus-application.h"
+
+#include <eel/eel-stock-dialogs.h>
+#include <fcntl.h>
+#include <gdk/gdk.h>
+#include <gio/gio.h>
+#include <glib/gi18n.h>
+#include <glib/gstdio.h>
+#include <gtk/gtk.h>
+#include <libadwaita-1/adwaita.h>
+#include <nautilus-extension.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+
+#define DEBUG_FLAG NAUTILUS_DEBUG_APPLICATION
+#include "nautilus-debug.h"
+
+#include "nautilus-bookmark-list.h"
+#include "nautilus-clipboard.h"
+#include "nautilus-dbus-launcher.h"
+#include "nautilus-dbus-manager.h"
+#include "nautilus-directory-private.h"
+#include "nautilus-file.h"
+#include "nautilus-file-operations.h"
+#include "nautilus-file-undo-manager.h"
+#include "nautilus-file-utilities.h"
+#include "nautilus-freedesktop-dbus.h"
+#include "nautilus-global-preferences.h"
+#include "nautilus-icon-info.h"
+#include "nautilus-lib-self-check-functions.h"
+#include "nautilus-module.h"
+#include "nautilus-preferences-window.h"
+#include "nautilus-previewer.h"
+#include "nautilus-profile.h"
+#include "nautilus-progress-persistence-handler.h"
+#include "nautilus-self-check-functions.h"
+#include "nautilus-shell-search-provider.h"
+#include "nautilus-signaller.h"
+#include "nautilus-tag-manager.h"
+#include "nautilus-tracker-utilities.h"
+#include "nautilus-ui-utilities.h"
+#include "nautilus-view.h"
+#include "nautilus-window-slot.h"
+#include "nautilus-window.h"
+
+typedef struct
+{
+ NautilusProgressPersistenceHandler *progress_handler;
+ NautilusDBusManager *dbus_manager;
+ NautilusFreedesktopDBus *fdb_manager;
+
+ NautilusBookmarkList *bookmark_list;
+
+ NautilusShellSearchProvider *search_provider;
+
+ GList *windows;
+
+ GHashTable *notifications;
+
+ NautilusFileUndoManager *undo_manager;
+
+ NautilusTagManager *tag_manager;
+
+ NautilusDBusLauncher *dbus_launcher;
+
+ guint previewer_selection_id;
+} NautilusApplicationPrivate;
+
+G_DEFINE_TYPE_WITH_PRIVATE (NautilusApplication, nautilus_application, ADW_TYPE_APPLICATION);
+
+void
+nautilus_application_set_accelerator (GApplication *app,
+ const gchar *action_name,
+ const gchar *accel)
+{
+ const gchar *vaccels[] =
+ {
+ accel,
+ NULL
+ };
+
+ gtk_application_set_accels_for_action (GTK_APPLICATION (app), action_name, vaccels);
+}
+
+void
+nautilus_application_set_accelerators (GApplication *app,
+ const gchar *action_name,
+ const gchar **accels)
+{
+ gtk_application_set_accels_for_action (GTK_APPLICATION (app), action_name, accels);
+}
+
+GList *
+nautilus_application_get_windows (NautilusApplication *self)
+{
+ NautilusApplicationPrivate *priv;
+
+ priv = nautilus_application_get_instance_private (self);
+
+ return priv->windows;
+}
+
+NautilusBookmarkList *
+nautilus_application_get_bookmarks (NautilusApplication *self)
+{
+ NautilusApplicationPrivate *priv;
+
+ priv = nautilus_application_get_instance_private (self);
+
+ if (!priv->bookmark_list)
+ {
+ priv->bookmark_list = nautilus_bookmark_list_new ();
+ }
+
+ return priv->bookmark_list;
+}
+
+static gboolean
+check_required_directories (NautilusApplication *self)
+{
+ char *user_directory;
+ GSList *directories;
+ gboolean ret;
+
+ g_assert (NAUTILUS_IS_APPLICATION (self));
+
+ nautilus_profile_start (NULL);
+
+ ret = TRUE;
+
+ user_directory = nautilus_get_user_directory ();
+
+ directories = NULL;
+
+ if (!g_file_test (user_directory, G_FILE_TEST_IS_DIR))
+ {
+ directories = g_slist_prepend (directories, user_directory);
+ }
+
+ if (directories != NULL)
+ {
+ int failed_count;
+ GString *directories_as_string;
+ GSList *l;
+ char *error_string;
+ g_autofree char *detail_string = NULL;
+ AdwMessageDialog *dialog;
+
+ ret = FALSE;
+
+ failed_count = g_slist_length (directories);
+
+ directories_as_string = g_string_new ((const char *) directories->data);
+ for (l = directories->next; l != NULL; l = l->next)
+ {
+ g_string_append_printf (directories_as_string, ", %s", (const char *) l->data);
+ }
+
+ error_string = _("Oops! Something went wrong.");
+ if (failed_count == 1)
+ {
+ detail_string = g_strdup_printf (_("Unable to create a required folder. "
+ "Please create the following folder, or "
+ "set permissions such that it can be created:\n%s"),
+ directories_as_string->str);
+ }
+ else
+ {
+ detail_string = g_strdup_printf (_("Unable to create required folders. "
+ "Please create the following folders, or "
+ "set permissions such that they can be created:\n%s"),
+ directories_as_string->str);
+ }
+
+ dialog = show_dialog (error_string, detail_string, NULL, GTK_MESSAGE_ERROR);
+ /* We need the main event loop so the user has a chance to see the dialog. */
+ gtk_application_add_window (GTK_APPLICATION (self),
+ GTK_WINDOW (dialog));
+
+ g_string_free (directories_as_string, TRUE);
+ }
+
+ g_slist_free (directories);
+ g_free (user_directory);
+ nautilus_profile_end (NULL);
+
+ return ret;
+}
+
+static void
+menu_provider_items_updated_handler (NautilusMenuProvider *provider,
+ GtkWidget *parent_window,
+ gpointer data)
+{
+ g_signal_emit_by_name (nautilus_signaller_get_current (),
+ "popup-menu-changed");
+}
+
+static void
+menu_provider_init_callback (void)
+{
+ GList *providers;
+ GList *l;
+
+ providers = nautilus_module_get_extensions_for_type (NAUTILUS_TYPE_MENU_PROVIDER);
+
+ for (l = providers; l != NULL; l = l->next)
+ {
+ NautilusMenuProvider *provider = NAUTILUS_MENU_PROVIDER (l->data);
+
+ g_signal_connect_after (G_OBJECT (provider), "items-updated",
+ (GCallback) menu_provider_items_updated_handler,
+ NULL);
+ }
+
+ nautilus_module_extension_list_free (providers);
+}
+
+NautilusWindow *
+nautilus_application_create_window (NautilusApplication *self)
+{
+ NautilusWindow *window;
+ gboolean maximized;
+ g_autoptr (GVariant) default_size = NULL;
+ gint default_width = 0;
+ gint default_height = 0;
+
+ g_return_val_if_fail (NAUTILUS_IS_APPLICATION (self), NULL);
+ nautilus_profile_start (NULL);
+
+ window = nautilus_window_new ();
+
+ maximized = g_settings_get_boolean
+ (nautilus_window_state, NAUTILUS_WINDOW_STATE_MAXIMIZED);
+ if (maximized)
+ {
+ gtk_window_maximize (GTK_WINDOW (window));
+ }
+ else
+ {
+ gtk_window_unmaximize (GTK_WINDOW (window));
+ }
+ default_size = g_settings_get_value (nautilus_window_state,
+ NAUTILUS_WINDOW_STATE_INITIAL_SIZE);
+
+ g_variant_get (default_size, "(ii)", &default_width, &default_height);
+
+ gtk_window_set_default_size (GTK_WINDOW (window),
+ MAX (NAUTILUS_WINDOW_MIN_WIDTH, default_width),
+ MAX (NAUTILUS_WINDOW_MIN_HEIGHT, default_height));
+
+ if (g_strcmp0 (PROFILE, "") != 0)
+ {
+ GtkStyleContext *style_context;
+
+ style_context = gtk_widget_get_style_context (GTK_WIDGET (window));
+
+ gtk_style_context_add_class (style_context, "devel");
+ }
+
+ DEBUG ("Creating a new navigation window");
+ nautilus_profile_end (NULL);
+
+ return window;
+}
+
+static NautilusWindowSlot *
+get_window_slot_for_location (NautilusApplication *self,
+ GFile *location)
+{
+ NautilusApplicationPrivate *priv;
+ NautilusWindowSlot *slot;
+ NautilusWindow *window;
+ NautilusFile *file;
+ GList *l, *sl;
+
+ priv = nautilus_application_get_instance_private (self);
+ slot = NULL;
+ file = nautilus_file_get (location);
+
+ if (!nautilus_file_is_directory (file) && !nautilus_file_is_other_locations (file) &&
+ g_file_has_parent (location, NULL))
+ {
+ location = g_file_get_parent (location);
+ }
+ else
+ {
+ g_object_ref (location);
+ }
+
+ for (l = priv->windows; l != NULL; l = l->next)
+ {
+ window = l->data;
+
+ for (sl = nautilus_window_get_slots (window); sl; sl = sl->next)
+ {
+ NautilusWindowSlot *current = sl->data;
+ GFile *slot_location = nautilus_window_slot_get_location (current);
+
+ if (slot_location && g_file_equal (slot_location, location))
+ {
+ slot = current;
+ break;
+ }
+ }
+
+ if (slot)
+ {
+ break;
+ }
+ }
+
+ nautilus_file_unref (file);
+ g_object_unref (location);
+
+ return slot;
+}
+
+void
+nautilus_application_open_location_full (NautilusApplication *self,
+ GFile *location,
+ NautilusOpenFlags flags,
+ GList *selection,
+ NautilusWindow *target_window,
+ NautilusWindowSlot *target_slot)
+{
+ NAUTILUS_APPLICATION_CLASS (G_OBJECT_GET_CLASS (self))->open_location_full (self,
+ location,
+ flags,
+ selection,
+ target_window,
+ target_slot);
+}
+
+static void
+real_open_location_full (NautilusApplication *self,
+ GFile *location,
+ NautilusOpenFlags flags,
+ GList *selection,
+ NautilusWindow *target_window,
+ NautilusWindowSlot *target_slot)
+{
+ NautilusWindowSlot *active_slot = NULL;
+ NautilusWindow *active_window;
+ GFile *old_location = NULL;
+ char *old_uri, *new_uri;
+ gboolean use_same;
+ GdkDisplay *display;
+
+ use_same = TRUE;
+ /* FIXME: We are having problems on getting the current focused window with
+ * gtk_application_get_active_window, see https://bugzilla.gnome.org/show_bug.cgi?id=756499
+ * so what we do is never rely on this on the callers, but would be cool to
+ * make it work withouth explicitly setting the active window on the callers. */
+ active_window = NAUTILUS_WINDOW (gtk_application_get_active_window (GTK_APPLICATION (self)));
+ /* There is no active window if the application is run with
+ * --gapplication-service
+ */
+ if (active_window)
+ {
+ active_slot = nautilus_window_get_active_slot (active_window);
+ /* Just for debug.*/
+ if (active_slot != NULL)
+ {
+ old_location = nautilus_window_slot_get_location (active_slot);
+ }
+ }
+
+
+ /* this happens at startup */
+ if (old_location == NULL)
+ {
+ old_uri = g_strdup ("(none)");
+ }
+ else
+ {
+ old_uri = g_file_get_uri (old_location);
+ }
+
+ new_uri = g_file_get_uri (location);
+
+ DEBUG ("Application opening location, old: %s, new: %s", old_uri, new_uri);
+ nautilus_profile_start ("Application opening location, old: %s, new: %s", old_uri, new_uri);
+
+ g_free (old_uri);
+ g_free (new_uri);
+ /* end debug */
+
+ /* In case a target slot is provided, we can use it's associated window.
+ * In case a target window were given as well, we give preference to the
+ * slot we target at */
+ if (target_slot != NULL)
+ {
+ target_window = nautilus_window_slot_get_window (target_slot);
+ }
+
+ g_assert (!((flags & NAUTILUS_OPEN_FLAG_NEW_WINDOW) != 0 &&
+ (flags & NAUTILUS_OPEN_FLAG_NEW_TAB) != 0));
+
+ /* and if the flags specify so, this is overridden */
+ if ((flags & NAUTILUS_OPEN_FLAG_NEW_WINDOW) != 0)
+ {
+ use_same = FALSE;
+ }
+
+ /* now get/create the window */
+ if (use_same)
+ {
+ if (!target_window)
+ {
+ if (!target_slot)
+ {
+ target_window = active_window;
+ }
+ else
+ {
+ target_window = nautilus_window_slot_get_window (target_slot);
+ }
+ }
+ }
+ else
+ {
+ display = active_window != NULL ?
+ gtk_root_get_display (GTK_ROOT (active_window)) :
+ gdk_display_get_default ();
+
+ target_window = nautilus_application_create_window (self);
+ /* Whatever the caller says, the slot won't be the same */
+ gtk_window_set_display (GTK_WINDOW (target_window), display);
+ target_slot = NULL;
+ }
+
+ g_assert (target_window != NULL);
+
+ /* Application is the one that manages windows, so this flag shouldn't use
+ * it anymore by any client */
+ flags &= ~NAUTILUS_OPEN_FLAG_NEW_WINDOW;
+ nautilus_window_open_location_full (target_window, location, flags, selection, target_slot);
+}
+
+static NautilusWindow *
+open_window (NautilusApplication *self,
+ GFile *location)
+{
+ NautilusWindow *window;
+
+ nautilus_profile_start (NULL);
+ window = nautilus_application_create_window (self);
+
+ if (location != NULL)
+ {
+ nautilus_application_open_location_full (self, location, 0, NULL, window, NULL);
+ }
+ else
+ {
+ GFile *home;
+ home = g_file_new_for_path (g_get_home_dir ());
+ nautilus_application_open_location_full (self, home, 0, NULL, window, NULL);
+
+ g_object_unref (home);
+ }
+
+ nautilus_profile_end (NULL);
+
+ return window;
+}
+
+void
+nautilus_application_open_location (NautilusApplication *self,
+ GFile *location,
+ GFile *selection,
+ const char *startup_id)
+{
+ NautilusWindow *window;
+ NautilusWindowSlot *slot;
+ GList *sel_list = NULL;
+
+ nautilus_profile_start (NULL);
+
+ if (selection != NULL)
+ {
+ sel_list = g_list_prepend (sel_list, nautilus_file_get (selection));
+ }
+
+ slot = get_window_slot_for_location (self, location);
+
+ if (!slot)
+ {
+ window = nautilus_application_create_window (self);
+ }
+ else
+ {
+ window = nautilus_window_slot_get_window (slot);
+ }
+
+ nautilus_application_open_location_full (self, location, 0, sel_list, window, slot);
+
+ if (sel_list != NULL)
+ {
+ nautilus_file_list_free (sel_list);
+ }
+
+ nautilus_profile_end (NULL);
+}
+
+/* Note: when launched from command line we do not reach this method
+ * since we manually handle the command line parameters in order to
+ * parse --version, --check, etc.
+ * However this method is called when open () is called via dbus, for
+ * instance when gtk_uri_open () is called from outside.
+ */
+static void
+nautilus_application_open (GApplication *app,
+ GFile **files,
+ gint n_files,
+ const gchar *hint)
+{
+ NautilusApplication *self = NAUTILUS_APPLICATION (app);
+ gboolean force_new = (g_strcmp0 (hint, "new-window") == 0);
+ NautilusWindowSlot *slot = NULL;
+ GFile *file;
+ gint idx;
+
+ DEBUG ("Open called on the GApplication instance; %d files", n_files);
+
+ /* Open windows at each requested location. */
+ for (idx = 0; idx < n_files; idx++)
+ {
+ file = files[idx];
+
+ if (!force_new)
+ {
+ slot = get_window_slot_for_location (self, file);
+ }
+
+ if (!slot)
+ {
+ open_window (self, file);
+ }
+ else
+ {
+ /* We open the location again to update any possible selection */
+ nautilus_application_open_location_full (NAUTILUS_APPLICATION (app), file, 0, NULL, NULL, slot);
+ }
+ }
+}
+
+static void
+nautilus_application_finalize (GObject *object)
+{
+ NautilusApplication *self;
+ NautilusApplicationPrivate *priv;
+
+ self = NAUTILUS_APPLICATION (object);
+ priv = nautilus_application_get_instance_private (self);
+
+ g_clear_object (&priv->progress_handler);
+ g_clear_object (&priv->bookmark_list);
+
+ g_clear_object (&priv->fdb_manager);
+
+ g_list_free (priv->windows);
+
+ g_hash_table_destroy (priv->notifications);
+
+ g_clear_object (&priv->undo_manager);
+
+ g_clear_object (&priv->tag_manager);
+
+ g_clear_object (&priv->dbus_launcher);
+
+ G_OBJECT_CLASS (nautilus_application_parent_class)->finalize (object);
+}
+
+static gboolean
+do_cmdline_sanity_checks (NautilusApplication *self,
+ GVariantDict *options)
+{
+ gboolean retval = FALSE;
+
+ if (g_variant_dict_contains (options, "check") &&
+ (g_variant_dict_contains (options, G_OPTION_REMAINING) ||
+ g_variant_dict_contains (options, "quit")))
+ {
+ g_printerr ("%s\n",
+ _("--check cannot be used with other options."));
+ goto out;
+ }
+
+ if (g_variant_dict_contains (options, "quit") &&
+ g_variant_dict_contains (options, G_OPTION_REMAINING))
+ {
+ g_printerr ("%s\n",
+ _("--quit cannot be used with URIs."));
+ goto out;
+ }
+
+
+ if (g_variant_dict_contains (options, "select") &&
+ !g_variant_dict_contains (options, G_OPTION_REMAINING))
+ {
+ g_printerr ("%s\n",
+ _("--select must be used with at least an URI."));
+ goto out;
+ }
+
+ retval = TRUE;
+
+out:
+ return retval;
+}
+
+static int
+do_perform_self_checks (void)
+{
+#ifndef NAUTILUS_OMIT_SELF_CHECK
+ gtk_init ();
+
+ nautilus_profile_start (NULL);
+ /* Run the checks (each twice) for nautilus and libnautilus-private. */
+
+ nautilus_run_self_checks ();
+ nautilus_run_lib_self_checks ();
+ eel_exit_if_self_checks_failed ();
+
+ nautilus_run_self_checks ();
+ nautilus_run_lib_self_checks ();
+ eel_exit_if_self_checks_failed ();
+ nautilus_profile_end (NULL);
+#endif
+
+ return EXIT_SUCCESS;
+}
+
+static void
+nautilus_application_select (NautilusApplication *self,
+ GFile **files,
+ gint len)
+{
+ int i;
+ GFile *file;
+ GFile *parent;
+
+ for (i = 0; i < len; i++)
+ {
+ file = files[i];
+ parent = g_file_get_parent (file);
+ if (parent != NULL)
+ {
+ nautilus_application_open_location (self, parent, file, NULL);
+ g_object_unref (parent);
+ }
+ else
+ {
+ nautilus_application_open_location (self, file, NULL, NULL);
+ }
+ }
+}
+
+static void
+action_new_window (GSimpleAction *action,
+ GVariant *parameter,
+ gpointer user_data)
+{
+ NautilusApplication *application;
+ g_autoptr (GFile) home = NULL;
+
+ application = NAUTILUS_APPLICATION (user_data);
+ home = g_file_new_for_path (g_get_home_dir ());
+
+ nautilus_application_open_location_full (application, home,
+ NAUTILUS_OPEN_FLAG_NEW_WINDOW,
+ NULL, NULL, NULL);
+}
+
+static void
+action_clone_window (GSimpleAction *action,
+ GVariant *parameter,
+ gpointer user_data)
+{
+ NautilusWindowSlot *active_slot = NULL;
+ NautilusWindow *active_window = NULL;
+ GtkApplication *application = user_data;
+ g_autoptr (GFile) location = NULL;
+ NautilusView *current_view;
+
+ active_window = NAUTILUS_WINDOW (gtk_application_get_active_window (application));
+ active_slot = nautilus_window_get_active_slot (active_window);
+ current_view = nautilus_window_slot_get_current_view (active_slot);
+
+ if (current_view != NULL &&
+ nautilus_view_is_searching (current_view))
+ {
+ location = g_file_new_for_path (g_get_home_dir ());
+ }
+ else
+ {
+ /* If the user happens to fall asleep while holding ctrl-n, or very
+ * unfortunately opens a new window at a remote location, the current
+ * location will be null, leading to criticals and/or failed assertions.
+ *
+ * Another sad thing is that checking if the view/slot is loading will
+ * not work, as the loading process only really begins after the attributes
+ * for the file have been fetched.
+ */
+ location = nautilus_window_slot_get_location (active_slot);
+ if (location == NULL)
+ {
+ location = nautilus_window_slot_get_pending_location (active_slot);
+ }
+ }
+
+ nautilus_application_open_location_full (NAUTILUS_APPLICATION (application), location,
+ NAUTILUS_OPEN_FLAG_NEW_WINDOW, NULL, NULL, NULL);
+}
+
+static void
+action_preferences (GSimpleAction *action,
+ GVariant *parameter,
+ gpointer user_data)
+{
+ GtkApplication *application = user_data;
+ nautilus_preferences_window_show (gtk_application_get_active_window (application));
+}
+
+static void
+action_about (GSimpleAction *action,
+ GVariant *parameter,
+ gpointer user_data)
+{
+ GtkApplication *application = user_data;
+
+ nautilus_window_show_about_dialog (NAUTILUS_WINDOW (gtk_application_get_active_window (application)));
+}
+
+static void
+action_help (GSimpleAction *action,
+ GVariant *parameter,
+ gpointer user_data)
+{
+ GtkWindow *window;
+ GtkWidget *dialog;
+ GtkApplication *application = user_data;
+ GError *error = NULL;
+
+ window = gtk_application_get_active_window (application);
+ gtk_show_uri (window, "help:gnome-help/files", GDK_CURRENT_TIME);
+
+ if (error)
+ {
+ dialog = adw_message_dialog_new (window ? GTK_WINDOW (window) : NULL,
+ NULL, NULL);
+ adw_message_dialog_format_heading (ADW_MESSAGE_DIALOG (dialog),
+ _("There was an error displaying help: \n%s"),
+ error->message);
+ adw_message_dialog_add_response (ADW_MESSAGE_DIALOG (dialog), "ok", _("_OK"));
+ adw_message_dialog_set_default_response (ADW_MESSAGE_DIALOG (dialog), "ok");
+
+ gtk_window_present (GTK_WINDOW (dialog));
+ g_error_free (error);
+ }
+}
+
+static void
+action_kill (GSimpleAction *action,
+ GVariant *parameter,
+ gpointer user_data)
+{
+ AdwApplication *application = user_data;
+
+ /* we have been asked to force quit */
+ g_application_quit (G_APPLICATION (application));
+}
+
+static void
+action_quit (GSimpleAction *action,
+ GVariant *parameter,
+ gpointer user_data)
+{
+ NautilusApplication *self = NAUTILUS_APPLICATION (user_data);
+ GList *windows, *l;
+
+ windows = nautilus_application_get_windows (self);
+ /* make a copy, since the original list will be modified when destroying
+ * a window, making this list invalid */
+ windows = g_list_copy (windows);
+ for (l = windows; l != NULL; l = l->next)
+ {
+ nautilus_window_close (l->data);
+ }
+
+ g_list_free (windows);
+}
+
+static void
+action_show_help_overlay (GSimpleAction *action,
+ GVariant *state,
+ gpointer user_data)
+{
+ GtkApplication *application = user_data;
+ GtkWindow *window = gtk_application_get_active_window (application);
+
+ g_action_group_activate_action (G_ACTION_GROUP (window), "show-help-overlay", NULL);
+}
+
+const static GActionEntry app_entries[] =
+{
+ { "new-window", action_new_window, NULL, NULL, NULL },
+ { "clone-window", action_clone_window, NULL, NULL, NULL },
+ { "preferences", action_preferences, NULL, NULL, NULL },
+ { "about", action_about, NULL, NULL, NULL },
+ { "help", action_help, NULL, NULL, NULL },
+ { "quit", action_quit, NULL, NULL, NULL },
+ { "kill", action_kill, NULL, NULL, NULL },
+ { "show-help-overlay", action_show_help_overlay, NULL, NULL, NULL },
+};
+
+static void
+nautilus_init_application_actions (NautilusApplication *app)
+{
+ g_action_map_add_action_entries (G_ACTION_MAP (app),
+ app_entries, G_N_ELEMENTS (app_entries),
+ app);
+
+
+ nautilus_application_set_accelerator (G_APPLICATION (app),
+ "app.clone-window", "<Primary>n");
+ nautilus_application_set_accelerator (G_APPLICATION (app),
+ "app.help", "F1");
+ nautilus_application_set_accelerator (G_APPLICATION (app),
+ "app.quit", "<Primary>q");
+}
+
+static void
+nautilus_application_activate (GApplication *app)
+{
+ GFile **files;
+
+ DEBUG ("Calling activate");
+
+ files = g_malloc0 (2 * sizeof (GFile *));
+ files[0] = g_file_new_for_path (g_get_home_dir ());
+ nautilus_application_open (app, files, 1, NULL);
+
+ g_object_unref (files[0]);
+ g_free (files);
+}
+
+static gint
+nautilus_application_handle_file_args (NautilusApplication *self,
+ GVariantDict *options)
+{
+ GFile **files;
+ GFile *file;
+ gint idx, len;
+ g_autofree const gchar **remaining = NULL;
+ GPtrArray *file_array;
+
+ g_variant_dict_lookup (options, G_OPTION_REMAINING, "^a&s", &remaining);
+
+ /* Convert args to GFiles */
+ file_array = g_ptr_array_new_full (0, g_object_unref);
+
+ if (remaining)
+ {
+ for (idx = 0; remaining[idx] != NULL; idx++)
+ {
+ gchar *cwd;
+
+ g_variant_dict_lookup (options, "cwd", "s", &cwd);
+ if (cwd == NULL)
+ {
+ file = g_file_new_for_commandline_arg (remaining[idx]);
+ }
+ else
+ {
+ file = g_file_new_for_commandline_arg_and_cwd (remaining[idx], cwd);
+ g_free (cwd);
+ }
+
+ if (nautilus_is_search_directory (file))
+ {
+ g_autofree char *error_string = NULL;
+ error_string = g_strdup_printf (_("“%s” is an internal protocol. "
+ "Opening this location directly is not supported."),
+ EEL_SEARCH_URI);
+
+ g_printerr ("%s\n", error_string);
+ }
+ else
+ {
+ g_ptr_array_add (file_array, file);
+ }
+ }
+ }
+ else if (g_variant_dict_contains (options, "new-window"))
+ {
+ file = g_file_new_for_path (g_get_home_dir ());
+ g_ptr_array_add (file_array, file);
+ }
+ else
+ {
+ g_ptr_array_unref (file_array);
+
+ /* No command line options or files, just activate the application */
+ nautilus_application_activate (G_APPLICATION (self));
+ return EXIT_SUCCESS;
+ }
+
+ len = file_array->len;
+ files = (GFile **) file_array->pdata;
+
+ if (g_variant_dict_contains (options, "select"))
+ {
+ nautilus_application_select (self, files, len);
+ }
+ else
+ {
+ /* Create new windows */
+ nautilus_application_open (G_APPLICATION (self), files, len,
+ g_variant_dict_contains (options, "new-window") ? "new-window" : "");
+ }
+
+ g_ptr_array_unref (file_array);
+
+ return EXIT_SUCCESS;
+}
+
+static gint
+nautilus_application_command_line (GApplication *application,
+ GApplicationCommandLine *command_line)
+{
+ NautilusApplication *self = NAUTILUS_APPLICATION (application);
+ gint retval = -1;
+ GVariantDict *options;
+
+ nautilus_profile_start (NULL);
+
+ options = g_application_command_line_get_options_dict (command_line);
+
+ if (g_variant_dict_contains (options, "version"))
+ {
+ g_application_command_line_print (command_line,
+ "GNOME nautilus " PACKAGE_VERSION "\n");
+ retval = EXIT_SUCCESS;
+ goto out;
+ }
+
+ if (!do_cmdline_sanity_checks (self, options))
+ {
+ retval = EXIT_FAILURE;
+ goto out;
+ }
+
+ if (g_variant_dict_contains (options, "check"))
+ {
+ retval = do_perform_self_checks ();
+ goto out;
+ }
+
+ if (g_variant_dict_contains (options, "quit"))
+ {
+ DEBUG ("Killing application, as requested");
+ g_action_group_activate_action (G_ACTION_GROUP (application),
+ "kill", NULL);
+ goto out;
+ }
+
+ retval = nautilus_application_handle_file_args (self, options);
+
+out:
+ nautilus_profile_end (NULL);
+
+ return retval;
+}
+
+static void
+nautilus_application_init (NautilusApplication *self)
+{
+ static const GOptionEntry options[] =
+ {
+#ifndef NAUTILUS_OMIT_SELF_CHECK
+ { "check", 'c', 0, G_OPTION_ARG_NONE, NULL,
+ N_("Perform a quick set of self-check tests."), NULL },
+#endif
+ { "version", '\0', 0, G_OPTION_ARG_NONE, NULL,
+ N_("Show the version of the program."), NULL },
+ { "new-window", 'w', 0, G_OPTION_ARG_NONE, NULL,
+ N_("Always open a new window for browsing specified URIs"), NULL },
+ { "quit", 'q', 0, G_OPTION_ARG_NONE, NULL,
+ N_("Quit Nautilus."), NULL },
+ { "select", 's', 0, G_OPTION_ARG_NONE, NULL,
+ N_("Select specified URI in parent folder."), NULL },
+ { G_OPTION_REMAINING, 0, 0, G_OPTION_ARG_STRING_ARRAY, NULL, NULL, N_("[URI…]") },
+
+ /* The following are old options which have no effect anymore. We keep
+ * them around for compatibility reasons, e.g. not breaking old scripts.
+ */
+ { "browser", '\0', G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_NONE, NULL,
+ NULL, NULL },
+ { "geometry", 'g', G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_STRING, NULL,
+ NULL, NULL },
+ { "no-default-window", 'n', G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_NONE, NULL,
+ NULL, NULL },
+ { "no-desktop", '\0', G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_NONE, NULL,
+ NULL, NULL },
+
+ { NULL }
+ };
+ NautilusApplicationPrivate *priv;
+
+ priv = nautilus_application_get_instance_private (self);
+
+ priv->notifications = g_hash_table_new_full (g_str_hash,
+ g_str_equal,
+ g_free,
+ NULL);
+
+ priv->undo_manager = nautilus_file_undo_manager_new ();
+ priv->tag_manager = nautilus_tag_manager_new ();
+
+ priv->dbus_launcher = nautilus_dbus_launcher_new ();
+
+ nautilus_tracker_setup_miner_fs_connection ();
+
+ g_application_add_main_option_entries (G_APPLICATION (self), options);
+
+ nautilus_ensure_extension_points ();
+ nautilus_ensure_extension_builtins ();
+
+ nautilus_clipboard_register ();
+}
+
+NautilusApplication *
+nautilus_application_get_default (void)
+{
+ NautilusApplication *self;
+
+ self = NAUTILUS_APPLICATION (g_application_get_default ());
+
+ return self;
+}
+
+void
+nautilus_application_send_notification (NautilusApplication *self,
+ const gchar *notification_id,
+ GNotification *notification)
+{
+ NautilusApplicationPrivate *priv;
+
+ priv = nautilus_application_get_instance_private (self);
+
+ g_hash_table_add (priv->notifications, g_strdup (notification_id));
+ g_application_send_notification (G_APPLICATION (self), notification_id, notification);
+}
+
+void
+nautilus_application_withdraw_notification (NautilusApplication *self,
+ const gchar *notification_id)
+{
+ NautilusApplicationPrivate *priv;
+
+ priv = nautilus_application_get_instance_private (self);
+ if (!g_hash_table_contains (priv->notifications, notification_id))
+ {
+ return;
+ }
+
+ g_hash_table_remove (priv->notifications, notification_id);
+ g_application_withdraw_notification (G_APPLICATION (self), notification_id);
+}
+
+static void
+on_application_shutdown (GApplication *application,
+ gpointer user_data)
+{
+ NautilusApplication *self = NAUTILUS_APPLICATION (application);
+ NautilusApplicationPrivate *priv;
+ GList *notification_ids;
+ GList *l;
+ gchar *notification_id;
+
+ priv = nautilus_application_get_instance_private (self);
+ notification_ids = g_hash_table_get_keys (priv->notifications);
+ for (l = notification_ids; l != NULL; l = l->next)
+ {
+ notification_id = l->data;
+
+ g_application_withdraw_notification (application, notification_id);
+ }
+
+ g_list_free (notification_ids);
+
+ nautilus_icon_info_clear_caches ();
+}
+
+static void
+icon_theme_changed_callback (GtkIconTheme *icon_theme,
+ gpointer user_data)
+{
+ /* Clear all pixmap caches as the icon => pixmap lookup changed */
+ nautilus_icon_info_clear_caches ();
+
+ /* Tell the world that icons might have changed. We could invent a narrower-scope
+ * signal to mean only "thumbnails might have changed" if this ends up being slow
+ * for some reason.
+ */
+ emit_change_signals_for_all_files_in_all_directories ();
+}
+
+static void
+maybe_migrate_gtk_filechooser_preferences (void)
+{
+ if (!g_settings_get_boolean (nautilus_preferences, NAUTILUS_PREFERENCES_MIGRATED_GTK_SETTINGS))
+ {
+ g_autoptr (GSettingsSchema) schema = NULL;
+
+ /* We don't depend on GTK 3. Check whether its schema is installed. */
+ schema = g_settings_schema_source_lookup (g_settings_schema_source_get_default (),
+ "org.gtk.Settings.FileChooser",
+ FALSE);
+ if (schema != NULL)
+ {
+ g_autoptr (GSettings) gtk3_settings = NULL;
+
+ gtk3_settings = g_settings_new_with_path ("org.gtk.Settings.FileChooser",
+ "/org/gtk/settings/file-chooser/");
+ g_settings_set_boolean (gtk_filechooser_preferences,
+ NAUTILUS_PREFERENCES_SORT_DIRECTORIES_FIRST,
+ g_settings_get_boolean (gtk3_settings,
+ NAUTILUS_PREFERENCES_SORT_DIRECTORIES_FIRST));
+ g_settings_set_boolean (gtk_filechooser_preferences,
+ NAUTILUS_PREFERENCES_SHOW_HIDDEN_FILES,
+ g_settings_get_boolean (gtk3_settings,
+ NAUTILUS_PREFERENCES_SHOW_HIDDEN_FILES));
+ }
+ g_settings_set_boolean (nautilus_preferences,
+ NAUTILUS_PREFERENCES_MIGRATED_GTK_SETTINGS,
+ TRUE);
+ }
+}
+
+void
+nautilus_application_startup_common (NautilusApplication *self)
+{
+ NautilusApplicationPrivate *priv;
+
+ nautilus_profile_start (NULL);
+ priv = nautilus_application_get_instance_private (self);
+
+ g_application_set_resource_base_path (G_APPLICATION (self), "/org/gnome/nautilus");
+
+ /* chain up to the GTK+ implementation early, so gtk_init()
+ * is called for us.
+ */
+ G_APPLICATION_CLASS (nautilus_application_parent_class)->startup (G_APPLICATION (self));
+
+ gtk_window_set_default_icon_name (APPLICATION_ID);
+
+ /* initialize preferences and create the global GSettings objects */
+ nautilus_global_preferences_init ();
+
+ /* initialize nautilus modules */
+ nautilus_profile_start ("Modules");
+ nautilus_module_setup ();
+ nautilus_profile_end ("Modules");
+
+ /* attach menu-provider module callback */
+ menu_provider_init_callback ();
+
+ /* Initialize the UI handler singleton for file operations */
+ priv->progress_handler = nautilus_progress_persistence_handler_new (G_OBJECT (self));
+
+ /* Check the user's .nautilus directories and post warnings
+ * if there are problems.
+ */
+ check_required_directories (self);
+
+ nautilus_init_application_actions (self);
+
+ if (g_strcmp0 (g_getenv ("RUNNING_TESTS"), "TRUE") != 0)
+ {
+ maybe_migrate_gtk_filechooser_preferences ();
+ nautilus_tag_manager_maybe_migrate_tracker2_data (priv->tag_manager);
+ }
+
+ nautilus_profile_end (NULL);
+
+ g_signal_connect (self, "shutdown", G_CALLBACK (on_application_shutdown), NULL);
+
+ g_signal_connect_object (gtk_icon_theme_get_for_display (gdk_display_get_default ()),
+ "changed",
+ G_CALLBACK (icon_theme_changed_callback),
+ NULL, 0);
+}
+
+static void
+nautilus_application_startup (GApplication *app)
+{
+ NautilusApplication *self = NAUTILUS_APPLICATION (app);
+ NautilusApplicationPrivate *priv;
+
+ nautilus_profile_start (NULL);
+ priv = nautilus_application_get_instance_private (self);
+
+ /* create DBus manager */
+ priv->fdb_manager = nautilus_freedesktop_dbus_new ();
+ nautilus_application_startup_common (self);
+
+ nautilus_profile_end (NULL);
+}
+
+static gboolean
+nautilus_application_dbus_register (GApplication *app,
+ GDBusConnection *connection,
+ const gchar *object_path,
+ GError **error)
+{
+ NautilusApplication *self = NAUTILUS_APPLICATION (app);
+ NautilusApplicationPrivate *priv;
+
+ priv = nautilus_application_get_instance_private (self);
+ priv->dbus_manager = nautilus_dbus_manager_new ();
+ if (!nautilus_dbus_manager_register (priv->dbus_manager, connection, error))
+ {
+ return FALSE;
+ }
+
+ priv->search_provider = nautilus_shell_search_provider_new ();
+ if (!nautilus_shell_search_provider_register (priv->search_provider, connection, error))
+ {
+ return FALSE;
+ }
+
+ priv->previewer_selection_id = nautilus_previewer_connect_selection_event (connection);
+
+ return TRUE;
+}
+
+static void
+nautilus_application_dbus_unregister (GApplication *app,
+ GDBusConnection *connection,
+ const gchar *object_path)
+{
+ NautilusApplication *self = NAUTILUS_APPLICATION (app);
+ NautilusApplicationPrivate *priv;
+
+ priv = nautilus_application_get_instance_private (self);
+ if (priv->dbus_manager)
+ {
+ nautilus_dbus_manager_unregister (priv->dbus_manager);
+ g_clear_object (&priv->dbus_manager);
+ }
+
+ if (priv->search_provider)
+ {
+ nautilus_shell_search_provider_unregister (priv->search_provider);
+ g_clear_object (&priv->search_provider);
+ }
+
+ if (priv->previewer_selection_id != 0)
+ {
+ nautilus_previewer_disconnect_selection_event (connection,
+ priv->previewer_selection_id);
+ priv->previewer_selection_id = 0;
+ }
+}
+
+static void
+update_dbus_opened_locations (NautilusApplication *self)
+{
+ NautilusApplicationPrivate *priv;
+ gint i;
+ GList *l, *sl;
+ GList *locations = NULL;
+ gsize locations_size = 0;
+ gchar **locations_array;
+ NautilusWindow *window;
+ GFile *location;
+ const gchar *dbus_object_path = NULL;
+
+ g_autoptr (GVariant) windows_to_locations = NULL;
+ GVariantBuilder windows_to_locations_builder;
+
+ g_return_if_fail (NAUTILUS_IS_APPLICATION (self));
+
+ priv = nautilus_application_get_instance_private (self);
+
+ /* Children of nautilus application could not handle the dbus, so don't
+ * do anything in that case */
+ if (!priv->fdb_manager)
+ {
+ return;
+ }
+
+ dbus_object_path = g_application_get_dbus_object_path (G_APPLICATION (self));
+
+ g_return_if_fail (dbus_object_path);
+
+ g_variant_builder_init (&windows_to_locations_builder, G_VARIANT_TYPE ("a{sas}"));
+
+ for (l = priv->windows; l != NULL; l = l->next)
+ {
+ guint32 id;
+ g_autofree gchar *path = NULL;
+ GVariantBuilder locations_in_window_builder;
+
+ window = l->data;
+
+ g_variant_builder_init (&locations_in_window_builder, G_VARIANT_TYPE ("as"));
+
+ for (sl = nautilus_window_get_slots (window); sl; sl = sl->next)
+ {
+ NautilusWindowSlot *slot = sl->data;
+ location = nautilus_window_slot_get_location (slot);
+
+ if (location != NULL)
+ {
+ gchar *uri = g_file_get_uri (location);
+ GList *found = g_list_find_custom (locations, uri, (GCompareFunc) g_strcmp0);
+
+ g_variant_builder_add (&locations_in_window_builder, "s", uri);
+
+ if (!found)
+ {
+ locations = g_list_prepend (locations, uri);
+ ++locations_size;
+ }
+ else
+ {
+ g_free (uri);
+ }
+ }
+ }
+
+ id = gtk_application_window_get_id (GTK_APPLICATION_WINDOW (window));
+ path = g_strdup_printf ("%s/window/%u", dbus_object_path, id);
+ g_variant_builder_add (&windows_to_locations_builder, "{sas}", path, &locations_in_window_builder);
+ g_variant_builder_clear (&locations_in_window_builder);
+ }
+
+ locations_array = g_new (gchar *, locations_size + 1);
+
+ for (i = 0, l = locations; l; l = l->next, ++i)
+ {
+ /* We reuse the locations string locations saved on list */
+ locations_array[i] = l->data;
+ }
+
+ locations_array[locations_size] = NULL;
+
+ nautilus_freedesktop_dbus_set_open_locations (priv->fdb_manager,
+ (const gchar **) locations_array);
+
+ windows_to_locations = g_variant_ref_sink (g_variant_builder_end (&windows_to_locations_builder));
+ nautilus_freedesktop_dbus_set_open_windows_with_locations (priv->fdb_manager,
+ windows_to_locations);
+
+ g_free (locations_array);
+ g_list_free_full (locations, g_free);
+}
+
+static void
+on_slot_location_changed (NautilusWindowSlot *slot,
+ GParamSpec *pspec,
+ NautilusApplication *self)
+{
+ update_dbus_opened_locations (self);
+}
+
+static void
+on_slot_added (NautilusWindow *window,
+ NautilusWindowSlot *slot,
+ NautilusApplication *self)
+{
+ if (nautilus_window_slot_get_location (slot))
+ {
+ update_dbus_opened_locations (self);
+ }
+
+ g_signal_connect (slot, "notify::location", G_CALLBACK (on_slot_location_changed), self);
+}
+
+static void
+on_slot_removed (NautilusWindow *window,
+ NautilusWindowSlot *slot,
+ NautilusApplication *self)
+{
+ update_dbus_opened_locations (self);
+
+ g_signal_handlers_disconnect_by_func (slot, on_slot_location_changed, self);
+}
+
+static void
+nautilus_application_window_added (GtkApplication *app,
+ GtkWindow *window)
+{
+ NautilusApplication *self = NAUTILUS_APPLICATION (app);
+ NautilusApplicationPrivate *priv;
+
+ priv = nautilus_application_get_instance_private (self);
+ GTK_APPLICATION_CLASS (nautilus_application_parent_class)->window_added (app, window);
+
+ if (NAUTILUS_IS_WINDOW (window))
+ {
+ priv->windows = g_list_prepend (priv->windows, window);
+ g_signal_connect (window, "slot-added", G_CALLBACK (on_slot_added), app);
+ g_signal_connect (window, "slot-removed", G_CALLBACK (on_slot_removed), app);
+ }
+}
+
+static void
+nautilus_application_window_removed (GtkApplication *app,
+ GtkWindow *window)
+{
+ NautilusApplication *self = NAUTILUS_APPLICATION (app);
+ NautilusApplicationPrivate *priv;
+
+ priv = nautilus_application_get_instance_private (self);
+
+ GTK_APPLICATION_CLASS (nautilus_application_parent_class)->window_removed (app, window);
+
+ if (NAUTILUS_IS_WINDOW (window))
+ {
+ priv->windows = g_list_remove_all (priv->windows, window);
+ g_signal_handlers_disconnect_by_func (window, on_slot_added, app);
+ g_signal_handlers_disconnect_by_func (window, on_slot_removed, app);
+ }
+
+ /* if this was the last window, close the previewer */
+ if (g_list_length (priv->windows) == 0)
+ {
+ nautilus_previewer_call_close ();
+ nautilus_progress_persistence_handler_make_persistent (priv->progress_handler);
+ }
+}
+
+/* Manage the local instance command line options. This is only necessary to
+ * resolve correctly relative paths, since if the main instance resolve them in
+ * open(), it will do it with its current cwd, which may not be correct for the
+ * non main GApplication instance */
+static gint
+nautilus_application_handle_local_options (GApplication *app,
+ GVariantDict *options)
+{
+ gchar *cwd;
+
+ cwd = g_get_current_dir ();
+ g_variant_dict_insert (options, "cwd", "s", cwd);
+ g_free (cwd);
+
+ return -1;
+}
+
+static void
+nautilus_application_class_init (NautilusApplicationClass *class)
+{
+ GObjectClass *object_class;
+ GApplicationClass *application_class;
+ GtkApplicationClass *gtkapp_class;
+
+ object_class = G_OBJECT_CLASS (class);
+ object_class->finalize = nautilus_application_finalize;
+
+ application_class = G_APPLICATION_CLASS (class);
+ application_class->startup = nautilus_application_startup;
+ application_class->activate = nautilus_application_activate;
+ application_class->dbus_register = nautilus_application_dbus_register;
+ application_class->dbus_unregister = nautilus_application_dbus_unregister;
+ application_class->open = nautilus_application_open;
+ application_class->command_line = nautilus_application_command_line;
+ application_class->handle_local_options = nautilus_application_handle_local_options;
+
+ class->open_location_full = real_open_location_full;
+
+ gtkapp_class = GTK_APPLICATION_CLASS (class);
+ gtkapp_class->window_added = nautilus_application_window_added;
+ gtkapp_class->window_removed = nautilus_application_window_removed;
+}
+
+NautilusApplication *
+nautilus_application_new (void)
+{
+ return g_object_new (NAUTILUS_TYPE_APPLICATION,
+ "application-id", APPLICATION_ID,
+ "flags", G_APPLICATION_HANDLES_COMMAND_LINE | G_APPLICATION_HANDLES_OPEN,
+ "inactivity-timeout", 12000,
+ NULL);
+}
+
+void
+nautilus_application_search (NautilusApplication *self,
+ NautilusQuery *query)
+{
+ g_autoptr (GFile) location = NULL;
+ NautilusWindow *window;
+
+ location = nautilus_query_get_location (query);
+ window = open_window (self, location);
+ nautilus_window_search (window, query);
+}
+
+gboolean
+nautilus_application_is_sandboxed (void)
+{
+ static gboolean ret;
+
+ static gsize init = 0;
+ if (g_once_init_enter (&init))
+ {
+ ret = g_file_test ("/.flatpak-info", G_FILE_TEST_EXISTS);
+ g_once_init_leave (&init, 1);
+ }
+
+ return ret;
+}
diff --git a/src/nautilus-application.h b/src/nautilus-application.h
new file mode 100644
index 0000000..50bcf4d
--- /dev/null
+++ b/src/nautilus-application.h
@@ -0,0 +1,87 @@
+/*
+ * nautilus-application: main Nautilus application class.
+ *
+ * Copyright (C) 2000 Red Hat, Inc.
+ * Copyright (C) 2010 Cosimo Cecchi <cosimoc@gnome.org>
+ *
+ * Nautilus is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * Nautilus is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include <adwaita.h>
+
+#include "nautilus-types.h"
+
+G_BEGIN_DECLS
+#define NAUTILUS_TYPE_APPLICATION (nautilus_application_get_type())
+G_DECLARE_DERIVABLE_TYPE (NautilusApplication, nautilus_application, NAUTILUS, APPLICATION, AdwApplication)
+
+struct _NautilusApplicationClass {
+ AdwApplicationClass parent_class;
+
+ void (*open_location_full) (NautilusApplication *application,
+ GFile *location,
+ NautilusOpenFlags flags,
+ GList *selection,
+ NautilusWindow *target_window,
+ NautilusWindowSlot *target_slot);
+};
+
+NautilusApplication * nautilus_application_new (void);
+
+NautilusWindow * nautilus_application_create_window (NautilusApplication *application);
+
+void nautilus_application_set_accelerator (GApplication *app,
+ const gchar *action_name,
+ const gchar *accel);
+
+void nautilus_application_set_accelerators (GApplication *app,
+ const gchar *action_name,
+ const gchar **accels);
+
+GList * nautilus_application_get_windows (NautilusApplication *application);
+
+void nautilus_application_open_location (NautilusApplication *application,
+ GFile *location,
+ GFile *selection,
+ const char *startup_id);
+
+void nautilus_application_open_location_full (NautilusApplication *application,
+ GFile *location,
+ NautilusOpenFlags flags,
+ GList *selection,
+ NautilusWindow *target_window,
+ NautilusWindowSlot *target_slot);
+
+NautilusApplication *nautilus_application_get_default (void);
+void nautilus_application_send_notification (NautilusApplication *self,
+ const gchar *notification_id,
+ GNotification *notification);
+void nautilus_application_withdraw_notification (NautilusApplication *self,
+ const gchar *notification_id);
+
+NautilusBookmarkList *
+ nautilus_application_get_bookmarks (NautilusApplication *application);
+void nautilus_application_edit_bookmarks (NautilusApplication *application,
+ NautilusWindow *window);
+
+GtkWidget * nautilus_application_connect_server (NautilusApplication *application,
+ NautilusWindow *window);
+
+void nautilus_application_search (NautilusApplication *application,
+ NautilusQuery *query);
+void nautilus_application_startup_common (NautilusApplication *application);
+gboolean nautilus_application_is_sandboxed (void);
+G_END_DECLS
diff --git a/src/nautilus-autorun-software.c b/src/nautilus-autorun-software.c
new file mode 100644
index 0000000..6d8553f
--- /dev/null
+++ b/src/nautilus-autorun-software.c
@@ -0,0 +1,285 @@
+/* Nautilus
+ *
+ * Copyright (C) 2008 Red Hat, Inc.
+ *
+ * The Gnome Library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * The Gnome Library 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
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with the Gnome Library; see the file COPYING.LIB. If not,
+ * see <http://www.gnu.org/licenses/>.
+ *
+ * Author: David Zeuthen <davidz@redhat.com>
+ */
+
+
+#include <config.h>
+
+#include <unistd.h>
+#include <string.h>
+#include <time.h>
+#include <errno.h>
+#include <gtk/gtk.h>
+#include <libadwaita-1/adwaita.h>
+#include <gio/gio.h>
+
+#include <glib/gi18n.h>
+
+typedef struct
+{
+ GtkWidget *dialog;
+ GMount *mount;
+} AutorunSoftwareDialogData;
+
+static void autorun_software_dialog_mount_unmounted (GMount *mount,
+ AutorunSoftwareDialogData *data);
+
+static void
+autorun_software_dialog_destroy (AutorunSoftwareDialogData *data)
+{
+ g_signal_handlers_disconnect_by_func (G_OBJECT (data->mount),
+ G_CALLBACK (autorun_software_dialog_mount_unmounted),
+ data);
+
+ gtk_window_destroy (GTK_WINDOW (data->dialog));
+ g_object_unref (data->mount);
+ g_free (data);
+}
+
+static void
+autorun_software_dialog_mount_unmounted (GMount *mount,
+ AutorunSoftwareDialogData *data)
+{
+ autorun_software_dialog_destroy (data);
+}
+
+static gboolean
+_check_file (GFile *mount_root,
+ const char *file_path,
+ gboolean must_be_executable)
+{
+ g_autoptr (GFile) file = NULL;
+ g_autoptr (GFileInfo) file_info = NULL;
+
+ file = g_file_get_child (mount_root, file_path);
+ file_info = g_file_query_info (file,
+ G_FILE_ATTRIBUTE_ACCESS_CAN_EXECUTE,
+ G_FILE_QUERY_INFO_NONE,
+ NULL,
+ NULL);
+
+ if (file_info == NULL)
+ {
+ return FALSE;
+ }
+
+ if (must_be_executable &&
+ !g_file_info_get_attribute_boolean (file_info, G_FILE_ATTRIBUTE_ACCESS_CAN_EXECUTE))
+ {
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static void
+autorun (GMount *mount)
+{
+ g_autoptr (GFile) root = NULL;
+ g_autoptr (GFile) program_to_spawn = NULL;
+ g_autoptr (GFile) program_parameter_file = NULL;
+ g_autofree char *error_string = NULL;
+ g_autofree char *path_to_spawn = NULL;
+ g_autofree char *cwd_for_program = NULL;
+ g_autofree char *program_parameter = NULL;
+
+ root = g_mount_get_root (mount);
+
+ /* Careful here, according to
+ *
+ * http://standards.freedesktop.org/autostart-spec/autostart-spec-latest.html
+ *
+ * the ordering does matter.
+ */
+
+ if (_check_file (root, ".autorun", TRUE))
+ {
+ program_to_spawn = g_file_get_child (root, ".autorun");
+ }
+ else if (_check_file (root, "autorun", TRUE))
+ {
+ program_to_spawn = g_file_get_child (root, "autorun");
+ }
+ else if (_check_file (root, "autorun.sh", FALSE))
+ {
+ program_to_spawn = g_file_new_for_path ("/bin/sh");
+ program_parameter_file = g_file_get_child (root, "autorun.sh");
+ }
+
+ if (program_to_spawn != NULL)
+ {
+ path_to_spawn = g_file_get_path (program_to_spawn);
+ }
+ if (program_parameter_file != NULL)
+ {
+ program_parameter = g_file_get_path (program_parameter_file);
+ }
+
+ cwd_for_program = g_file_get_path (root);
+
+ if (path_to_spawn != NULL && cwd_for_program != NULL)
+ {
+ if (chdir (cwd_for_program) == 0)
+ {
+ execl (path_to_spawn, path_to_spawn, program_parameter, NULL);
+ error_string = g_strdup_printf (_("Unable to start the program:\n%s"), strerror (errno));
+ goto out;
+ }
+ error_string = g_strdup_printf (_("Unable to start the program:\n%s"), strerror (errno));
+ goto out;
+ }
+ error_string = g_strdup_printf (_("Unable to locate the program"));
+
+out:
+ if (error_string != NULL)
+ {
+ GtkWidget *dialog;
+ dialog = adw_message_dialog_new (NULL,
+ _("Oops! There was a problem running this software."),
+ error_string);
+ adw_message_dialog_add_response (ADW_MESSAGE_DIALOG (dialog), "ok", _("_OK"));
+ adw_message_dialog_set_default_response (ADW_MESSAGE_DIALOG (dialog), "ok");
+
+ gtk_window_present (GTK_WINDOW (dialog));
+ }
+}
+
+static void
+autorun_software_dialog_response (GtkDialog *dialog,
+ gchar *response,
+ GMount *mount)
+{
+ if (g_strcmp0 (response, "run") == 0)
+ {
+ autorun (mount);
+ }
+}
+
+static void
+present_autorun_for_software_dialog (GMount *mount)
+{
+ GIcon *icon;
+ g_autofree char *mount_name = NULL;
+ GtkWidget *dialog;
+ AutorunSoftwareDialogData *data;
+
+ mount_name = g_mount_get_name (mount);
+
+ dialog = adw_message_dialog_new (NULL, NULL, _("If you don’t trust this location or aren’t sure, press Cancel."));
+ adw_message_dialog_format_heading (ADW_MESSAGE_DIALOG (dialog),
+ _("“%s” contains software intended to be automatically started. Would you like to run it?"),
+ mount_name);
+
+ /* TODO: in a star trek future add support for verifying
+ * software on media (e.g. if it has a certificate, check it
+ * etc.)
+ */
+
+
+ icon = g_mount_get_icon (mount);
+ if (G_IS_THEMED_ICON (icon))
+ {
+ const gchar * const *names;
+
+ names = g_themed_icon_get_names (G_THEMED_ICON (icon));
+
+ if (names != NULL)
+ {
+ gtk_window_set_icon_name (GTK_WINDOW (dialog), names[0]);
+ }
+ }
+
+ data = g_new0 (AutorunSoftwareDialogData, 1);
+ data->dialog = dialog;
+ data->mount = g_object_ref (mount);
+
+ g_signal_connect (G_OBJECT (mount),
+ "unmounted",
+ G_CALLBACK (autorun_software_dialog_mount_unmounted),
+ data);
+
+ adw_message_dialog_add_responses (ADW_MESSAGE_DIALOG (dialog),
+ "cancel", _("_Cancel"),
+ "run", _("_Run"),
+ NULL);
+ adw_message_dialog_set_default_response (ADW_MESSAGE_DIALOG (dialog), "cancel");
+
+ g_signal_connect (dialog,
+ "response",
+ G_CALLBACK (autorun_software_dialog_response),
+ mount);
+
+ gtk_window_present (GTK_WINDOW (dialog));
+}
+
+int
+main (int argc,
+ char *argv[])
+{
+ g_autoptr (GVolumeMonitor) monitor = NULL;
+ g_autoptr (GFile) file = NULL;
+ g_autoptr (GMount) mount = NULL;
+ g_autoptr (GError) error = NULL;
+
+ bindtextdomain (GETTEXT_PACKAGE, LOCALEDIR);
+ bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8");
+ textdomain (GETTEXT_PACKAGE);
+
+ gtk_init ();
+ adw_init ();
+
+ if (argc != 2)
+ {
+ g_print ("Usage: %s mount-uri\n", argv[0]);
+ goto out;
+ }
+
+ /* instantiate monitor so we get the "unmounted" signal properly */
+ monitor = g_volume_monitor_get ();
+ if (monitor == NULL)
+ {
+ g_warning ("Unable to connect to the volume monitor");
+ goto out;
+ }
+
+ file = g_file_new_for_commandline_arg (argv[1]);
+ if (file == NULL)
+ {
+ g_warning ("Unable to parse mount URI");
+ goto out;
+ }
+
+ mount = g_file_find_enclosing_mount (file, NULL, &error);
+ if (mount == NULL)
+ {
+ g_warning ("Unable to find device for URI: %s", error->message);
+ goto out;
+ }
+
+ present_autorun_for_software_dialog (mount);
+
+ while (g_list_model_get_n_items (gtk_window_get_toplevels ()) > 0)
+ {
+ g_main_context_iteration (NULL, TRUE);
+ }
+
+out:
+ return 0;
+}
diff --git a/src/nautilus-batch-rename-dialog.c b/src/nautilus-batch-rename-dialog.c
new file mode 100644
index 0000000..14f142b
--- /dev/null
+++ b/src/nautilus-batch-rename-dialog.c
@@ -0,0 +1,2040 @@
+/* nautilus-batch-rename-dialog.c
+ *
+ * Copyright (C) 2016 Alexandru Pandelea <alexandru.pandelea@gmail.com>
+ *
+ * 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/>.
+ */
+
+#include <config.h>
+
+#include "nautilus-batch-rename-dialog.h"
+#include "nautilus-file.h"
+#include "nautilus-error-reporting.h"
+#include "nautilus-batch-rename-utilities.h"
+
+#include <glib/gprintf.h>
+#include <glib.h>
+#include <string.h>
+#include <glib/gi18n.h>
+
+#define ROW_MARGIN_START 6
+#define ROW_MARGIN_TOP_BOTTOM 4
+
+struct _NautilusBatchRenameDialog
+{
+ GtkDialog parent;
+
+ GtkWidget *grid;
+ NautilusWindow *window;
+
+ GtkWidget *cancel_button;
+ GtkWidget *original_name_listbox;
+ GtkWidget *arrow_listbox;
+ GtkWidget *result_listbox;
+ GtkWidget *name_entry;
+ GtkWidget *rename_button;
+ GtkWidget *find_entry;
+ GtkWidget *mode_stack;
+ GtkWidget *replace_entry;
+ GtkWidget *format_mode_button;
+ GtkWidget *replace_mode_button;
+ GtkWidget *numbering_order_button;
+ GtkWidget *numbering_label;
+ GtkWidget *scrolled_window;
+ GtkWidget *numbering_revealer;
+ GtkWidget *conflict_box;
+ GtkWidget *conflict_label;
+ GtkWidget *conflict_down;
+ GtkWidget *conflict_up;
+
+ GList *listbox_labels_new;
+ GList *listbox_labels_old;
+ GList *listbox_icons;
+ GtkSizeGroup *size_group;
+
+ GList *selection;
+ GList *new_names;
+ NautilusBatchRenameDialogMode mode;
+ NautilusDirectory *directory;
+
+ GActionGroup *action_group;
+
+ GMenu *numbering_order_menu;
+
+ GHashTable *create_date;
+ GList *selection_metadata;
+
+ /* the index of the currently selected conflict */
+ gint selected_conflict;
+ /* total conflicts number */
+ gint conflicts_number;
+
+ GList *duplicates;
+ GList *distinct_parent_directories;
+ GList *directories_pending_conflict_check;
+
+ /* this hash table has information about the status
+ * of all tags: availability, if it's currently used
+ * and position */
+ GHashTable *tag_info_table;
+
+ GtkWidget *preselected_row1;
+ GtkWidget *preselected_row2;
+
+ gint row_height;
+ gboolean rename_clicked;
+
+ GCancellable *metadata_cancellable;
+};
+
+typedef struct
+{
+ gboolean available;
+ gboolean set;
+ gint position;
+ /* if the tag was just added, then we shouldn't update it's position */
+ gboolean just_added;
+ TagConstants tag_constants;
+} TagData;
+
+
+static void update_display_text (NautilusBatchRenameDialog *dialog);
+static void cancel_conflict_check (NautilusBatchRenameDialog *self);
+
+G_DEFINE_TYPE (NautilusBatchRenameDialog, nautilus_batch_rename_dialog, GTK_TYPE_DIALOG);
+
+static void
+change_numbering_order (GSimpleAction *action,
+ GVariant *value,
+ gpointer user_data)
+{
+ NautilusBatchRenameDialog *dialog;
+ const gchar *target_name;
+ guint i;
+
+ dialog = NAUTILUS_BATCH_RENAME_DIALOG (user_data);
+
+ target_name = g_variant_get_string (value, NULL);
+
+ for (i = 0; i < G_N_ELEMENTS (sorts_constants); i++)
+ {
+ if (g_strcmp0 (sorts_constants[i].action_target_name, target_name) == 0)
+ {
+ gtk_menu_button_set_label (GTK_MENU_BUTTON (dialog->numbering_order_button),
+ gettext (sorts_constants[i].label));
+ dialog->selection = nautilus_batch_rename_dialog_sort (dialog->selection,
+ sorts_constants[i].sort_mode,
+ dialog->create_date);
+ break;
+ }
+ }
+
+ g_simple_action_set_state (action, value);
+
+ update_display_text (dialog);
+}
+
+static void
+enable_action (NautilusBatchRenameDialog *self,
+ const gchar *action_name)
+{
+ GAction *action;
+
+ action = g_action_map_lookup_action (G_ACTION_MAP (self->action_group),
+ action_name);
+ g_simple_action_set_enabled (G_SIMPLE_ACTION (action), TRUE);
+}
+
+static void
+disable_action (NautilusBatchRenameDialog *self,
+ const gchar *action_name)
+{
+ GAction *action;
+
+ action = g_action_map_lookup_action (G_ACTION_MAP (self->action_group),
+ action_name);
+ g_simple_action_set_enabled (G_SIMPLE_ACTION (action), FALSE);
+}
+
+static void
+add_tag (NautilusBatchRenameDialog *self,
+ TagConstants tag_constants)
+{
+ g_autofree gchar *tag_text_representation = NULL;
+ gint cursor_position;
+ TagData *tag_data;
+
+ g_object_get (self->name_entry, "cursor-position", &cursor_position, NULL);
+
+ tag_text_representation = batch_rename_get_tag_text_representation (tag_constants);
+ tag_data = g_hash_table_lookup (self->tag_info_table, tag_text_representation);
+ tag_data->available = TRUE;
+ tag_data->set = TRUE;
+ tag_data->just_added = TRUE;
+ tag_data->position = cursor_position;
+
+ /* FIXME: We can add a tag when the cursor is inside a tag, which breaks this.
+ * We need to check the cursor movement and update the actions acordingly or
+ * even better add the tag at the end of the previous tag if this happens.
+ */
+ gtk_editable_insert_text (GTK_EDITABLE (self->name_entry),
+ tag_text_representation,
+ strlen (tag_text_representation),
+ &cursor_position);
+ tag_data->just_added = FALSE;
+ gtk_editable_set_position (GTK_EDITABLE (self->name_entry), cursor_position);
+
+ gtk_entry_grab_focus_without_selecting (GTK_ENTRY (self->name_entry));
+}
+
+static void
+add_metadata_tag (GSimpleAction *action,
+ GVariant *value,
+ gpointer user_data)
+{
+ NautilusBatchRenameDialog *self;
+ const gchar *action_name;
+ guint i;
+
+ self = NAUTILUS_BATCH_RENAME_DIALOG (user_data);
+ action_name = g_action_get_name (G_ACTION (action));
+
+ for (i = 0; i < G_N_ELEMENTS (metadata_tags_constants); i++)
+ {
+ if (g_strcmp0 (metadata_tags_constants[i].action_name, action_name) == 0)
+ {
+ add_tag (self, metadata_tags_constants[i]);
+ disable_action (self, metadata_tags_constants[i].action_name);
+
+ break;
+ }
+ }
+}
+
+static void
+add_numbering_tag (GSimpleAction *action,
+ GVariant *value,
+ gpointer user_data)
+{
+ NautilusBatchRenameDialog *self;
+ const gchar *action_name;
+ guint i;
+
+ self = NAUTILUS_BATCH_RENAME_DIALOG (user_data);
+ action_name = g_action_get_name (G_ACTION (action));
+
+ for (i = 0; i < G_N_ELEMENTS (numbering_tags_constants); i++)
+ {
+ if (g_strcmp0 (numbering_tags_constants[i].action_name, action_name) == 0)
+ {
+ add_tag (self, numbering_tags_constants[i]);
+ }
+ /* We want to allow only one tag of numbering type, so we disable all
+ * of them */
+ disable_action (self, numbering_tags_constants[i].action_name);
+ }
+}
+
+const GActionEntry dialog_entries[] =
+{
+ { "numbering-order-changed", NULL, "s", "'name-ascending'", change_numbering_order },
+ { "add-numbering-no-zero-pad-tag", add_numbering_tag },
+ { "add-numbering-one-zero-pad-tag", add_numbering_tag },
+ { "add-numbering-two-zero-pad-tag", add_numbering_tag },
+ { "add-original-file-name-tag", add_metadata_tag },
+ { "add-creation-date-tag", add_metadata_tag },
+ { "add-equipment-tag", add_metadata_tag },
+ { "add-season-number-tag", add_metadata_tag },
+ { "add-episode-number-tag", add_metadata_tag },
+ { "add-video-album-tag", add_metadata_tag },
+ { "add-track-number-tag", add_metadata_tag },
+ { "add-artist-name-tag", add_metadata_tag },
+ { "add-title-tag", add_metadata_tag },
+ { "add-album-name-tag", add_metadata_tag },
+};
+
+static gint
+compare_int (gconstpointer a,
+ gconstpointer b)
+{
+ int *number1 = (int *) a;
+ int *number2 = (int *) b;
+
+ return *number1 - *number2;
+}
+
+/* This function splits the entry text into a list of regular text and tags.
+ * For instance, "[1, 2, 3]Paris[Creation date]" would result in:
+ * "[1, 2, 3]", "Paris", "[Creation date]" */
+static GList *
+split_entry_text (NautilusBatchRenameDialog *self,
+ gchar *entry_text)
+{
+ GString *normal_text;
+ GString *tag;
+ GArray *tag_positions;
+ g_autoptr (GList) tag_info_keys = NULL;
+ GList *l;
+ gint tags;
+ gint i;
+ gchar *substring;
+ gint tag_end_position;
+ GList *result = NULL;
+ TagData *tag_data;
+
+ tags = 0;
+ tag_end_position = 0;
+ tag_positions = g_array_new (FALSE, FALSE, sizeof (gint));
+
+ tag_info_keys = g_hash_table_get_keys (self->tag_info_table);
+
+ for (l = tag_info_keys; l != NULL; l = l->next)
+ {
+ tag_data = g_hash_table_lookup (self->tag_info_table, l->data);
+ if (tag_data->set)
+ {
+ g_array_append_val (tag_positions, tag_data->position);
+ tags++;
+ }
+ }
+
+ g_array_sort (tag_positions, compare_int);
+
+ for (i = 0; i < tags; i++)
+ {
+ tag = g_string_new ("");
+
+ substring = g_utf8_substring (entry_text, tag_end_position,
+ g_array_index (tag_positions, gint, i));
+ normal_text = g_string_new (substring);
+ g_free (substring);
+
+ if (g_strcmp0 (normal_text->str, ""))
+ {
+ result = g_list_prepend (result, normal_text);
+ }
+ else
+ {
+ g_string_free (normal_text, TRUE);
+ }
+
+ for (l = tag_info_keys; l != NULL; l = l->next)
+ {
+ g_autofree gchar *tag_text_representation = NULL;
+
+ tag_data = g_hash_table_lookup (self->tag_info_table, l->data);
+ if (tag_data->set && g_array_index (tag_positions, gint, i) == tag_data->position)
+ {
+ tag_text_representation = batch_rename_get_tag_text_representation (tag_data->tag_constants);
+ tag_end_position = g_array_index (tag_positions, gint, i) +
+ g_utf8_strlen (tag_text_representation, -1);
+ tag = g_string_append (tag, tag_text_representation);
+
+ break;
+ }
+ }
+
+ result = g_list_prepend (result, tag);
+ }
+
+ normal_text = g_string_new (g_utf8_offset_to_pointer (entry_text, tag_end_position));
+
+ if (g_strcmp0 (normal_text->str, "") != 0)
+ {
+ result = g_list_prepend (result, normal_text);
+ }
+ else
+ {
+ g_string_free (normal_text, TRUE);
+ }
+
+ result = g_list_reverse (result);
+
+ g_array_free (tag_positions, TRUE);
+ return result;
+}
+
+static GList *
+batch_rename_dialog_get_new_names (NautilusBatchRenameDialog *dialog)
+{
+ GList *result = NULL;
+ GList *selection;
+ GList *text_chunks;
+ g_autofree gchar *entry_text = NULL;
+ g_autofree gchar *replace_text = NULL;
+
+ selection = dialog->selection;
+ text_chunks = NULL;
+
+ if (dialog->mode == NAUTILUS_BATCH_RENAME_DIALOG_REPLACE)
+ {
+ entry_text = g_strdup (gtk_editable_get_text (GTK_EDITABLE (dialog->find_entry)));
+ }
+ else
+ {
+ entry_text = g_strdup (gtk_editable_get_text (GTK_EDITABLE (dialog->name_entry)));
+ }
+
+ replace_text = g_strdup (gtk_editable_get_text (GTK_EDITABLE (dialog->replace_entry)));
+
+ if (dialog->mode == NAUTILUS_BATCH_RENAME_DIALOG_REPLACE)
+ {
+ result = batch_rename_dialog_get_new_names_list (dialog->mode,
+ selection,
+ NULL,
+ NULL,
+ entry_text,
+ replace_text);
+ }
+ else
+ {
+ text_chunks = split_entry_text (dialog, entry_text);
+
+ result = batch_rename_dialog_get_new_names_list (dialog->mode,
+ selection,
+ text_chunks,
+ dialog->selection_metadata,
+ entry_text,
+ replace_text);
+ g_list_free_full (text_chunks, string_free);
+ }
+
+ result = g_list_reverse (result);
+
+ return result;
+}
+
+static void
+begin_batch_rename (NautilusBatchRenameDialog *dialog,
+ GList *new_names)
+{
+ batch_rename_sort_lists_for_rename (&dialog->selection, &new_names, NULL, NULL, NULL, FALSE);
+
+ /* do the actual rename here */
+ nautilus_file_batch_rename (dialog->selection, new_names, NULL, NULL);
+
+ gtk_widget_set_cursor (GTK_WIDGET (dialog->window), NULL);
+}
+
+static void
+listbox_header_func (GtkListBoxRow *row,
+ GtkListBoxRow *before,
+ NautilusBatchRenameDialog *dialog)
+{
+ GtkWidget *separator;
+
+ if (before == NULL)
+ {
+ /* First row needs no separator */
+ gtk_list_box_row_set_header (row, NULL);
+ return;
+ }
+
+ separator = gtk_list_box_row_get_header (row);
+ if (separator == NULL)
+ {
+ separator = gtk_separator_new (GTK_ORIENTATION_HORIZONTAL);
+ gtk_widget_show (separator);
+
+ gtk_list_box_row_set_header (row, separator);
+ }
+}
+
+/* This is manually done instead of using GtkSizeGroup because of the computational
+ * complexity of the later.*/
+static void
+update_rows_height (NautilusBatchRenameDialog *dialog)
+{
+ GList *l;
+ GtkRequisition current_row_natural_size;
+ gint maximum_height;
+
+ maximum_height = -1;
+
+ /* check if maximum height has changed */
+ for (l = dialog->listbox_labels_new; l != NULL; l = l->next)
+ {
+ gtk_widget_get_preferred_size (GTK_WIDGET (l->data),
+ NULL,
+ &current_row_natural_size);
+
+ if (current_row_natural_size.height > maximum_height)
+ {
+ maximum_height = current_row_natural_size.height;
+ }
+ }
+
+ for (l = dialog->listbox_labels_old; l != NULL; l = l->next)
+ {
+ gtk_widget_get_preferred_size (GTK_WIDGET (l->data),
+ NULL,
+ &current_row_natural_size);
+
+ if (current_row_natural_size.height > maximum_height)
+ {
+ maximum_height = current_row_natural_size.height;
+ }
+ }
+
+ for (l = dialog->listbox_icons; l != NULL; l = l->next)
+ {
+ gtk_widget_get_preferred_size (GTK_WIDGET (l->data),
+ NULL,
+ &current_row_natural_size);
+
+ if (current_row_natural_size.height > maximum_height)
+ {
+ maximum_height = current_row_natural_size.height;
+ }
+ }
+
+ if (maximum_height != dialog->row_height)
+ {
+ dialog->row_height = maximum_height + ROW_MARGIN_TOP_BOTTOM * 2;
+
+ for (l = dialog->listbox_icons; l != NULL; l = l->next)
+ {
+ g_object_set (G_OBJECT (l->data), "height-request", dialog->row_height, NULL);
+ }
+
+ for (l = dialog->listbox_labels_new; l != NULL; l = l->next)
+ {
+ g_object_set (G_OBJECT (l->data), "height-request", dialog->row_height, NULL);
+ }
+
+ for (l = dialog->listbox_labels_old; l != NULL; l = l->next)
+ {
+ g_object_set (G_OBJECT (l->data), "height-request", dialog->row_height, NULL);
+ }
+ }
+}
+
+static GtkWidget *
+create_original_name_label (NautilusBatchRenameDialog *dialog,
+ const gchar *old_text)
+{
+ GtkWidget *label_old;
+
+ label_old = gtk_label_new (old_text);
+ gtk_label_set_xalign (GTK_LABEL (label_old), 0.0);
+ gtk_widget_set_hexpand (label_old, TRUE);
+ gtk_widget_set_margin_start (label_old, ROW_MARGIN_START);
+ gtk_label_set_ellipsize (GTK_LABEL (label_old), PANGO_ELLIPSIZE_END);
+
+ dialog->listbox_labels_old = g_list_prepend (dialog->listbox_labels_old, label_old);
+
+ gtk_widget_show (label_old);
+
+ return label_old;
+}
+
+static GtkWidget *
+create_result_label (NautilusBatchRenameDialog *dialog,
+ const gchar *new_text)
+{
+ GtkWidget *label_new;
+
+ label_new = gtk_label_new (new_text);
+ gtk_label_set_xalign (GTK_LABEL (label_new), 0.0);
+ gtk_widget_set_hexpand (label_new, TRUE);
+ gtk_widget_set_margin_start (label_new, ROW_MARGIN_START);
+ gtk_label_set_ellipsize (GTK_LABEL (label_new), PANGO_ELLIPSIZE_END);
+
+ dialog->listbox_labels_new = g_list_prepend (dialog->listbox_labels_new, label_new);
+
+ gtk_widget_show (label_new);
+
+ return label_new;
+}
+
+static GtkWidget *
+create_arrow (NautilusBatchRenameDialog *dialog,
+ GtkTextDirection text_direction)
+{
+ GtkWidget *icon;
+
+ if (text_direction == GTK_TEXT_DIR_RTL)
+ {
+ icon = gtk_label_new ("←");
+ }
+ else
+ {
+ icon = gtk_label_new ("→");
+ }
+
+ gtk_label_set_xalign (GTK_LABEL (icon), 1.0);
+ gtk_widget_set_hexpand (icon, FALSE);
+ gtk_widget_set_margin_start (icon, ROW_MARGIN_START);
+
+ dialog->listbox_icons = g_list_prepend (dialog->listbox_icons, icon);
+
+ gtk_widget_show (icon);
+
+ return icon;
+}
+
+static void
+prepare_batch_rename (NautilusBatchRenameDialog *dialog)
+{
+ /* wait for checking conflicts to finish, to be sure that
+ * the rename can actually take place */
+ if (dialog->directories_pending_conflict_check != NULL)
+ {
+ dialog->rename_clicked = TRUE;
+ return;
+ }
+
+ if (!gtk_widget_is_sensitive (dialog->rename_button))
+ {
+ return;
+ }
+
+ gtk_widget_set_cursor_from_name (GTK_WIDGET (dialog->window), "progress");
+
+ gtk_widget_set_cursor_from_name (GTK_WIDGET (dialog), "progress");
+
+ gtk_widget_hide (GTK_WIDGET (dialog));
+ begin_batch_rename (dialog, dialog->new_names);
+
+ gtk_window_destroy (GTK_WINDOW (dialog));
+}
+
+static void
+batch_rename_dialog_on_response (NautilusBatchRenameDialog *dialog,
+ gint response_id,
+ gpointer user_data)
+{
+ if (response_id == GTK_RESPONSE_OK)
+ {
+ prepare_batch_rename (dialog);
+ }
+ else
+ {
+ if (dialog->directories_pending_conflict_check != NULL)
+ {
+ cancel_conflict_check (dialog);
+ }
+
+ gtk_window_destroy (GTK_WINDOW (dialog));
+ }
+}
+
+static void
+fill_display_listbox (NautilusBatchRenameDialog *dialog)
+{
+ GtkWidget *row_child;
+ GList *l1;
+ GList *l2;
+ NautilusFile *file;
+ GString *new_name;
+ gchar *name;
+ GtkTextDirection text_direction;
+
+ gtk_size_group_add_widget (dialog->size_group, dialog->result_listbox);
+ gtk_size_group_add_widget (dialog->size_group, dialog->original_name_listbox);
+
+ text_direction = gtk_widget_get_direction (GTK_WIDGET (dialog));
+
+ for (l1 = dialog->new_names, l2 = dialog->selection; l1 != NULL && l2 != NULL; l1 = l1->next, l2 = l2->next)
+ {
+ file = NAUTILUS_FILE (l2->data);
+ new_name = l1->data;
+
+ name = nautilus_file_get_name (file);
+ row_child = create_original_name_label (dialog, name);
+ gtk_list_box_insert (GTK_LIST_BOX (dialog->original_name_listbox), row_child, -1);
+
+ row_child = create_arrow (dialog, text_direction);
+ gtk_list_box_insert (GTK_LIST_BOX (dialog->arrow_listbox), row_child, -1);
+
+ row_child = create_result_label (dialog, new_name->str);
+ gtk_list_box_insert (GTK_LIST_BOX (dialog->result_listbox), row_child, -1);
+
+ g_free (name);
+ }
+
+ dialog->listbox_labels_old = g_list_reverse (dialog->listbox_labels_old);
+ dialog->listbox_labels_new = g_list_reverse (dialog->listbox_labels_new);
+ dialog->listbox_icons = g_list_reverse (dialog->listbox_icons);
+}
+
+static void
+select_nth_conflict (NautilusBatchRenameDialog *dialog)
+{
+ GList *l;
+ GString *conflict_file_name;
+ GString *display_text;
+ GString *new_name;
+ gint nth_conflict_index;
+ gint nth_conflict;
+ gint name_occurences;
+ GtkAdjustment *adjustment;
+ GtkAllocation allocation;
+ ConflictData *conflict_data;
+ GtkListBoxRow *list_box_row;
+
+ nth_conflict = dialog->selected_conflict;
+ l = g_list_nth (dialog->duplicates, nth_conflict);
+ conflict_data = l->data;
+
+ /* the conflict that has to be selected */
+ conflict_file_name = g_string_new (conflict_data->name);
+ display_text = g_string_new ("");
+
+ nth_conflict_index = conflict_data->index;
+
+ l = g_list_nth (dialog->listbox_labels_new, nth_conflict_index);
+ list_box_row = GTK_LIST_BOX_ROW (gtk_widget_get_parent (l->data));
+ gtk_list_box_select_row (GTK_LIST_BOX (dialog->original_name_listbox),
+ list_box_row);
+
+ l = g_list_nth (dialog->listbox_labels_old, nth_conflict_index);
+ list_box_row = GTK_LIST_BOX_ROW (gtk_widget_get_parent (l->data));
+ gtk_list_box_select_row (GTK_LIST_BOX (dialog->arrow_listbox),
+ list_box_row);
+
+ l = g_list_nth (dialog->listbox_icons, nth_conflict_index);
+ list_box_row = GTK_LIST_BOX_ROW (gtk_widget_get_parent (l->data));
+ gtk_list_box_select_row (GTK_LIST_BOX (dialog->result_listbox),
+ list_box_row);
+
+ /* scroll to the selected row */
+ adjustment = gtk_scrolled_window_get_vadjustment (GTK_SCROLLED_WINDOW (dialog->scrolled_window));
+ gtk_widget_get_allocation (GTK_WIDGET (l->data), &allocation);
+ gtk_adjustment_set_value (adjustment, (allocation.height + 1) * nth_conflict_index);
+
+ name_occurences = 0;
+ for (l = dialog->new_names; l != NULL; l = l->next)
+ {
+ new_name = l->data;
+ if (g_string_equal (new_name, conflict_file_name))
+ {
+ name_occurences++;
+ }
+ }
+ if (name_occurences > 1)
+ {
+ g_string_append_printf (display_text,
+ _("“%s” would not be a unique new name."),
+ conflict_file_name->str);
+ }
+ else
+ {
+ g_string_append_printf (display_text,
+ _("“%s” would conflict with an existing file."),
+ conflict_file_name->str);
+ }
+
+ gtk_label_set_label (GTK_LABEL (dialog->conflict_label),
+ display_text->str);
+
+ g_string_free (conflict_file_name, TRUE);
+ g_string_free (display_text, TRUE);
+}
+
+static void
+select_next_conflict_down (NautilusBatchRenameDialog *dialog)
+{
+ dialog->selected_conflict++;
+
+ if (dialog->selected_conflict == 1)
+ {
+ gtk_widget_set_sensitive (dialog->conflict_up, TRUE);
+ }
+
+ if (dialog->selected_conflict == dialog->conflicts_number - 1)
+ {
+ gtk_widget_set_sensitive (dialog->conflict_down, FALSE);
+ }
+
+ select_nth_conflict (dialog);
+}
+
+static void
+select_next_conflict_up (NautilusBatchRenameDialog *dialog)
+{
+ dialog->selected_conflict--;
+
+ if (dialog->selected_conflict == 0)
+ {
+ gtk_widget_set_sensitive (dialog->conflict_up, FALSE);
+ }
+
+ if (dialog->selected_conflict == dialog->conflicts_number - 2)
+ {
+ gtk_widget_set_sensitive (dialog->conflict_down, TRUE);
+ }
+
+ select_nth_conflict (dialog);
+}
+
+static void
+update_conflict_row_background (NautilusBatchRenameDialog *dialog)
+{
+ GList *l1;
+ GList *l2;
+ GList *l3;
+ GList *duplicates;
+ gint index;
+ GtkStyleContext *context;
+ ConflictData *conflict_data;
+
+ index = 0;
+
+ duplicates = dialog->duplicates;
+
+ for (l1 = dialog->listbox_labels_new,
+ l2 = dialog->listbox_labels_old,
+ l3 = dialog->listbox_icons;
+ l1 != NULL && l2 != NULL && l3 != NULL;
+ l1 = l1->next, l2 = l2->next, l3 = l3->next)
+ {
+ GtkWidget *row1 = gtk_widget_get_parent (l1->data);
+ GtkWidget *row2 = gtk_widget_get_parent (l2->data);
+ GtkWidget *row3 = gtk_widget_get_parent (l3->data);
+
+ context = gtk_widget_get_style_context (row1);
+
+ if (gtk_style_context_has_class (context, "conflict-row"))
+ {
+ gtk_style_context_remove_class (context, "conflict-row");
+
+ context = gtk_widget_get_style_context (row2);
+ gtk_style_context_remove_class (context, "conflict-row");
+
+ context = gtk_widget_get_style_context (row3);
+ gtk_style_context_remove_class (context, "conflict-row");
+ }
+
+ if (duplicates != NULL)
+ {
+ conflict_data = duplicates->data;
+ if (conflict_data->index == index)
+ {
+ context = gtk_widget_get_style_context (row1);
+ gtk_style_context_add_class (context, "conflict-row");
+
+ context = gtk_widget_get_style_context (row2);
+ gtk_style_context_add_class (context, "conflict-row");
+
+ context = gtk_widget_get_style_context (row3);
+ gtk_style_context_add_class (context, "conflict-row");
+
+ duplicates = duplicates->next;
+ }
+ }
+ index++;
+ }
+}
+
+static void
+update_listbox (NautilusBatchRenameDialog *dialog)
+{
+ GList *l1;
+ GList *l2;
+ NautilusFile *file;
+ gchar *old_name;
+ GtkLabel *label;
+ GString *new_name;
+ gboolean empty_name = FALSE;
+
+ for (l1 = dialog->new_names, l2 = dialog->listbox_labels_new; l1 != NULL && l2 != NULL; l1 = l1->next, l2 = l2->next)
+ {
+ label = GTK_LABEL (l2->data);
+ new_name = l1->data;
+
+ gtk_label_set_label (label, new_name->str);
+ gtk_widget_set_tooltip_text (GTK_WIDGET (label), new_name->str);
+
+ if (g_strcmp0 (new_name->str, "") == 0)
+ {
+ empty_name = TRUE;
+ }
+ }
+
+ for (l1 = dialog->selection, l2 = dialog->listbox_labels_old; l1 != NULL && l2 != NULL; l1 = l1->next, l2 = l2->next)
+ {
+ label = GTK_LABEL (l2->data);
+ file = NAUTILUS_FILE (l1->data);
+
+ old_name = nautilus_file_get_name (file);
+ gtk_widget_set_tooltip_text (GTK_WIDGET (label), old_name);
+
+ if (dialog->mode == NAUTILUS_BATCH_RENAME_DIALOG_FORMAT)
+ {
+ gtk_label_set_label (label, old_name);
+ }
+ else
+ {
+ new_name = batch_rename_replace_label_text (old_name,
+ gtk_editable_get_text (GTK_EDITABLE (dialog->find_entry)));
+ gtk_label_set_markup (GTK_LABEL (label), new_name->str);
+
+ g_string_free (new_name, TRUE);
+ }
+
+ g_free (old_name);
+ }
+
+ update_rows_height (dialog);
+
+ if (empty_name)
+ {
+ gtk_widget_set_sensitive (dialog->rename_button, FALSE);
+
+ return;
+ }
+
+ /* check if there are name conflicts and display them if they exist */
+ if (dialog->duplicates != NULL)
+ {
+ update_conflict_row_background (dialog);
+
+ gtk_widget_set_sensitive (dialog->rename_button, FALSE);
+
+ gtk_widget_show (dialog->conflict_box);
+
+ dialog->selected_conflict = 0;
+ dialog->conflicts_number = g_list_length (dialog->duplicates);
+
+ select_nth_conflict (dialog);
+
+ gtk_widget_set_sensitive (dialog->conflict_up, FALSE);
+
+ if (g_list_length (dialog->duplicates) == 1)
+ {
+ gtk_widget_set_sensitive (dialog->conflict_down, FALSE);
+ }
+ else
+ {
+ gtk_widget_set_sensitive (dialog->conflict_down, TRUE);
+ }
+ }
+ else
+ {
+ gtk_widget_hide (dialog->conflict_box);
+
+ /* re-enable the rename button if there are no more name conflicts */
+ if (dialog->duplicates == NULL && !gtk_widget_is_sensitive (dialog->rename_button))
+ {
+ update_conflict_row_background (dialog);
+ gtk_widget_set_sensitive (dialog->rename_button, TRUE);
+ }
+ }
+
+ /* if the rename button was clicked and there's no conflict, then start renaming */
+ if (dialog->rename_clicked && dialog->duplicates == NULL)
+ {
+ prepare_batch_rename (dialog);
+ }
+
+ if (dialog->rename_clicked && dialog->duplicates != NULL)
+ {
+ dialog->rename_clicked = FALSE;
+ }
+}
+
+static void
+check_conflict_for_files (NautilusBatchRenameDialog *dialog,
+ NautilusDirectory *directory,
+ GList *files)
+{
+ gchar *current_directory;
+ gchar *parent_uri;
+ gchar *name;
+ NautilusFile *file;
+ GString *new_name;
+ GString *file_name;
+ GList *l1, *l2;
+ GHashTable *directory_files_table;
+ GHashTable *new_names_table;
+ GHashTable *names_conflicts_table;
+ gboolean exists;
+ gboolean have_conflict;
+ gboolean tag_present;
+ gboolean same_parent_directory;
+ ConflictData *conflict_data;
+
+ current_directory = nautilus_directory_get_uri (directory);
+
+ directory_files_table = g_hash_table_new_full (g_str_hash,
+ g_str_equal,
+ (GDestroyNotify) g_free,
+ NULL);
+ new_names_table = g_hash_table_new_full (g_str_hash,
+ g_str_equal,
+ (GDestroyNotify) g_free,
+ (GDestroyNotify) g_free);
+ names_conflicts_table = g_hash_table_new_full (g_str_hash,
+ g_str_equal,
+ (GDestroyNotify) g_free,
+ (GDestroyNotify) g_free);
+
+ /* names_conflicts_table is used for knowing which names from the list are not unique,
+ * so that they can easily be reached when needed */
+ for (l1 = dialog->new_names, l2 = dialog->selection;
+ l1 != NULL && l2 != NULL;
+ l1 = l1->next, l2 = l2->next)
+ {
+ new_name = l1->data;
+ file = NAUTILUS_FILE (l2->data);
+ parent_uri = nautilus_file_get_parent_uri (file);
+
+ tag_present = g_hash_table_lookup (new_names_table, new_name->str) != NULL;
+ same_parent_directory = g_strcmp0 (parent_uri, current_directory) == 0;
+
+ if (same_parent_directory)
+ {
+ if (!tag_present)
+ {
+ g_hash_table_insert (new_names_table,
+ g_strdup (new_name->str),
+ nautilus_file_get_parent_uri (file));
+ }
+ else
+ {
+ g_hash_table_insert (names_conflicts_table,
+ g_strdup (new_name->str),
+ nautilus_file_get_parent_uri (file));
+ }
+ }
+
+ g_free (parent_uri);
+ }
+
+ for (l1 = files; l1 != NULL; l1 = l1->next)
+ {
+ file = NAUTILUS_FILE (l1->data);
+ g_hash_table_insert (directory_files_table,
+ nautilus_file_get_name (file),
+ GINT_TO_POINTER (TRUE));
+ }
+
+ for (l1 = dialog->selection, l2 = dialog->new_names; l1 != NULL && l2 != NULL; l1 = l1->next, l2 = l2->next)
+ {
+ file = NAUTILUS_FILE (l1->data);
+
+ name = nautilus_file_get_name (file);
+ file_name = g_string_new (name);
+ g_free (name);
+
+ parent_uri = nautilus_file_get_parent_uri (file);
+
+ new_name = l2->data;
+
+ have_conflict = FALSE;
+
+ /* check for duplicate only if the parent of the current file is
+ * the current directory and the name of the file has changed */
+ if (g_strcmp0 (parent_uri, current_directory) == 0 &&
+ !g_string_equal (new_name, file_name))
+ {
+ exists = GPOINTER_TO_INT (g_hash_table_lookup (directory_files_table, new_name->str));
+
+ if (exists == TRUE &&
+ !file_name_conflicts_with_results (dialog->selection, dialog->new_names, new_name, parent_uri))
+ {
+ conflict_data = g_new (ConflictData, 1);
+ conflict_data->name = g_strdup (new_name->str);
+ conflict_data->index = g_list_index (dialog->selection, l1->data);
+ dialog->duplicates = g_list_prepend (dialog->duplicates,
+ conflict_data);
+
+ have_conflict = TRUE;
+ }
+ }
+
+ if (!have_conflict)
+ {
+ tag_present = g_hash_table_lookup (names_conflicts_table, new_name->str) != NULL;
+ same_parent_directory = g_strcmp0 (parent_uri, current_directory) == 0;
+
+ if (tag_present && same_parent_directory)
+ {
+ conflict_data = g_new (ConflictData, 1);
+ conflict_data->name = g_strdup (new_name->str);
+ conflict_data->index = g_list_index (dialog->selection, l1->data);
+ dialog->duplicates = g_list_prepend (dialog->duplicates,
+ conflict_data);
+
+ have_conflict = TRUE;
+ }
+ }
+
+ g_string_free (file_name, TRUE);
+ g_free (parent_uri);
+ }
+
+ g_free (current_directory);
+ g_hash_table_destroy (directory_files_table);
+ g_hash_table_destroy (new_names_table);
+ g_hash_table_destroy (names_conflicts_table);
+}
+
+static void
+on_directory_attributes_ready_for_conflicts_check (NautilusDirectory *conflict_directory,
+ GList *files,
+ gpointer callback_data)
+{
+ NautilusBatchRenameDialog *self;
+
+ self = NAUTILUS_BATCH_RENAME_DIALOG (callback_data);
+
+ check_conflict_for_files (self, conflict_directory, files);
+
+ g_assert (g_list_find (self->directories_pending_conflict_check, conflict_directory) != NULL);
+
+ self->directories_pending_conflict_check = g_list_remove (self->directories_pending_conflict_check, conflict_directory);
+
+ nautilus_directory_unref (conflict_directory);
+
+ if (self->directories_pending_conflict_check == NULL)
+ {
+ self->duplicates = g_list_reverse (self->duplicates);
+
+ update_listbox (self);
+ }
+}
+
+static void
+cancel_conflict_check (NautilusBatchRenameDialog *self)
+{
+ GList *l;
+ NautilusDirectory *directory;
+
+ for (l = self->directories_pending_conflict_check; l != NULL; l = l->next)
+ {
+ directory = l->data;
+
+ nautilus_directory_cancel_callback (directory,
+ on_directory_attributes_ready_for_conflicts_check,
+ self);
+ }
+
+ g_clear_list (&self->directories_pending_conflict_check, g_object_unref);
+}
+
+static void
+file_names_list_has_duplicates_async (NautilusBatchRenameDialog *self)
+{
+ GList *l;
+
+ if (self->directories_pending_conflict_check != NULL)
+ {
+ cancel_conflict_check (self);
+ }
+
+ self->directories_pending_conflict_check = nautilus_directory_list_copy (self->distinct_parent_directories);
+ self->duplicates = NULL;
+
+ for (l = self->distinct_parent_directories; l != NULL; l = l->next)
+ {
+ nautilus_directory_call_when_ready (l->data,
+ NAUTILUS_FILE_ATTRIBUTE_INFO,
+ TRUE,
+ on_directory_attributes_ready_for_conflicts_check,
+ self);
+ }
+}
+
+static gboolean
+have_unallowed_character (NautilusBatchRenameDialog *dialog)
+{
+ GList *names;
+ GString *new_name;
+ const gchar *entry_text;
+ gboolean have_empty_name;
+ gboolean have_unallowed_character_slash;
+ gboolean have_unallowed_character_dot;
+ gboolean have_unallowed_character_dotdot;
+
+ have_empty_name = FALSE;
+ have_unallowed_character_slash = FALSE;
+ have_unallowed_character_dot = FALSE;
+ have_unallowed_character_dotdot = FALSE;
+
+ if (dialog->mode == NAUTILUS_BATCH_RENAME_DIALOG_FORMAT)
+ {
+ entry_text = gtk_editable_get_text (GTK_EDITABLE (dialog->name_entry));
+ }
+ else
+ {
+ entry_text = gtk_editable_get_text (GTK_EDITABLE (dialog->replace_entry));
+ }
+
+ if (strstr (entry_text, "/") != NULL)
+ {
+ have_unallowed_character_slash = TRUE;
+ }
+
+ if (dialog->mode == NAUTILUS_BATCH_RENAME_DIALOG_FORMAT && g_strcmp0 (entry_text, ".") == 0)
+ {
+ have_unallowed_character_dot = TRUE;
+ }
+ else if (dialog->mode == NAUTILUS_BATCH_RENAME_DIALOG_REPLACE)
+ {
+ for (names = dialog->new_names; names != NULL; names = names->next)
+ {
+ new_name = names->data;
+
+ if (g_strcmp0 (new_name->str, ".") == 0)
+ {
+ have_unallowed_character_dot = TRUE;
+ break;
+ }
+ }
+ }
+
+ if (dialog->mode == NAUTILUS_BATCH_RENAME_DIALOG_FORMAT && g_strcmp0 (entry_text, "..") == 0)
+ {
+ have_unallowed_character_dotdot = TRUE;
+ }
+ else if (dialog->mode == NAUTILUS_BATCH_RENAME_DIALOG_REPLACE)
+ {
+ for (names = dialog->new_names; names != NULL; names = names->next)
+ {
+ new_name = names->data;
+
+ if (g_strcmp0 (new_name->str, "") == 0)
+ {
+ have_empty_name = TRUE;
+ break;
+ }
+
+ if (g_strcmp0 (new_name->str, "..") == 0)
+ {
+ have_unallowed_character_dotdot = TRUE;
+ break;
+ }
+ }
+ }
+
+ if (have_empty_name)
+ {
+ gtk_label_set_label (GTK_LABEL (dialog->conflict_label),
+ _("Name cannot be empty."));
+ }
+
+ if (have_unallowed_character_slash)
+ {
+ gtk_label_set_label (GTK_LABEL (dialog->conflict_label),
+ _("Name cannot contain “/”."));
+ }
+
+ if (have_unallowed_character_dot)
+ {
+ gtk_label_set_label (GTK_LABEL (dialog->conflict_label),
+ _("“.” is not a valid name."));
+ }
+
+ if (have_unallowed_character_dotdot)
+ {
+ gtk_label_set_label (GTK_LABEL (dialog->conflict_label),
+ _("“..” is not a valid name."));
+ }
+
+ if (have_unallowed_character_slash || have_unallowed_character_dot || have_unallowed_character_dotdot
+ || have_empty_name)
+ {
+ gtk_widget_set_sensitive (dialog->rename_button, FALSE);
+ gtk_widget_set_sensitive (dialog->conflict_down, FALSE);
+ gtk_widget_set_sensitive (dialog->conflict_up, FALSE);
+
+ gtk_widget_show (dialog->conflict_box);
+
+ return TRUE;
+ }
+ else
+ {
+ gtk_widget_hide (dialog->conflict_box);
+
+ return FALSE;
+ }
+}
+
+static gboolean
+numbering_tag_is_some_added (NautilusBatchRenameDialog *self)
+{
+ guint i;
+ TagData *tag_data;
+
+ for (i = 0; i < G_N_ELEMENTS (numbering_tags_constants); i++)
+ {
+ g_autofree gchar *tag_text_representation = NULL;
+
+ tag_text_representation = batch_rename_get_tag_text_representation (numbering_tags_constants[i]);
+ tag_data = g_hash_table_lookup (self->tag_info_table, tag_text_representation);
+ if (tag_data->set)
+ {
+ return TRUE;
+ }
+ }
+
+ return FALSE;
+}
+
+static void
+update_display_text (NautilusBatchRenameDialog *dialog)
+{
+ if (dialog->selection == NULL)
+ {
+ return;
+ }
+
+ if (dialog->duplicates != NULL)
+ {
+ g_list_free_full (dialog->duplicates, conflict_data_free);
+ dialog->duplicates = NULL;
+ }
+
+ if (dialog->new_names != NULL)
+ {
+ g_list_free_full (dialog->new_names, string_free);
+ }
+
+ if (!numbering_tag_is_some_added (dialog))
+ {
+ gtk_revealer_set_reveal_child (GTK_REVEALER (dialog->numbering_revealer), FALSE);
+ }
+ else
+ {
+ gtk_revealer_set_reveal_child (GTK_REVEALER (dialog->numbering_revealer), TRUE);
+ }
+
+ dialog->new_names = batch_rename_dialog_get_new_names (dialog);
+
+ if (have_unallowed_character (dialog))
+ {
+ return;
+ }
+
+ file_names_list_has_duplicates_async (dialog);
+}
+
+static void
+batch_rename_dialog_mode_changed (NautilusBatchRenameDialog *dialog)
+{
+ if (gtk_check_button_get_active (GTK_CHECK_BUTTON (dialog->format_mode_button)))
+ {
+ gtk_stack_set_visible_child_name (GTK_STACK (dialog->mode_stack), "format");
+
+ dialog->mode = NAUTILUS_BATCH_RENAME_DIALOG_FORMAT;
+
+ gtk_entry_grab_focus_without_selecting (GTK_ENTRY (dialog->name_entry));
+ }
+ else
+ {
+ gtk_stack_set_visible_child_name (GTK_STACK (dialog->mode_stack), "replace");
+
+ dialog->mode = NAUTILUS_BATCH_RENAME_DIALOG_REPLACE;
+
+ gtk_entry_grab_focus_without_selecting (GTK_ENTRY (dialog->find_entry));
+ }
+
+ update_display_text (dialog);
+}
+
+void
+nautilus_batch_rename_dialog_query_finished (NautilusBatchRenameDialog *dialog,
+ GHashTable *hash_table,
+ GList *selection_metadata)
+{
+ GMenuItem *first_created;
+ GMenuItem *last_created;
+ FileMetadata *file_metadata;
+ MetadataType metadata_type;
+ gboolean is_metadata;
+ TagData *tag_data;
+ g_autoptr (GList) tag_info_keys = NULL;
+ GList *l;
+
+ /* for files with no metadata */
+ if (hash_table != NULL && g_hash_table_size (hash_table) == 0)
+ {
+ g_hash_table_destroy (hash_table);
+
+ hash_table = NULL;
+ }
+
+ if (hash_table == NULL)
+ {
+ dialog->create_date = NULL;
+ }
+ else
+ {
+ dialog->create_date = hash_table;
+ }
+
+ if (dialog->create_date != NULL)
+ {
+ first_created = g_menu_item_new ("First Created",
+ "dialog.numbering-order-changed('first-created')");
+
+ g_menu_append_item (dialog->numbering_order_menu, first_created);
+
+ last_created = g_menu_item_new ("Last Created",
+ "dialog.numbering-order-changed('last-created')");
+
+ g_menu_append_item (dialog->numbering_order_menu, last_created);
+ }
+
+ dialog->selection_metadata = selection_metadata;
+ file_metadata = selection_metadata->data;
+ tag_info_keys = g_hash_table_get_keys (dialog->tag_info_table);
+ for (l = tag_info_keys; l != NULL; l = l->next)
+ {
+ /* Only metadata has to be handled here. */
+ tag_data = g_hash_table_lookup (dialog->tag_info_table, l->data);
+ is_metadata = tag_data->tag_constants.is_metadata;
+ if (!is_metadata)
+ {
+ continue;
+ }
+
+ metadata_type = tag_data->tag_constants.metadata_type;
+ if (file_metadata->metadata[metadata_type] == NULL ||
+ file_metadata->metadata[metadata_type]->len <= 0)
+ {
+ disable_action (dialog, tag_data->tag_constants.action_name);
+ tag_data->available = FALSE;
+ }
+ }
+}
+
+static void
+update_row_shadowing (GtkWidget *row,
+ gboolean shown)
+{
+ GtkStyleContext *context;
+ GtkStateFlags flags;
+
+ if (!GTK_IS_LIST_BOX_ROW (row))
+ {
+ return;
+ }
+
+ context = gtk_widget_get_style_context (row);
+ flags = gtk_style_context_get_state (context);
+
+ if (shown)
+ {
+ flags |= GTK_STATE_FLAG_PRELIGHT;
+ }
+ else
+ {
+ flags &= ~GTK_STATE_FLAG_PRELIGHT;
+ }
+
+ gtk_style_context_set_state (context, flags);
+}
+
+static void
+on_event_controller_motion_motion (GtkEventControllerMotion *controller,
+ double x,
+ double y,
+ gpointer user_data)
+{
+ GtkWidget *widget;
+ NautilusBatchRenameDialog *dialog;
+ GtkListBoxRow *row;
+
+ widget = gtk_event_controller_get_widget (GTK_EVENT_CONTROLLER (controller));
+ dialog = NAUTILUS_BATCH_RENAME_DIALOG (user_data);
+
+ if (dialog->preselected_row1 && dialog->preselected_row2)
+ {
+ update_row_shadowing (dialog->preselected_row1, FALSE);
+ update_row_shadowing (dialog->preselected_row2, FALSE);
+ }
+
+ if (widget == dialog->result_listbox)
+ {
+ row = gtk_list_box_get_row_at_y (GTK_LIST_BOX (dialog->original_name_listbox), y);
+ update_row_shadowing (GTK_WIDGET (row), TRUE);
+ dialog->preselected_row1 = GTK_WIDGET (row);
+
+ row = gtk_list_box_get_row_at_y (GTK_LIST_BOX (dialog->arrow_listbox), y);
+ update_row_shadowing (GTK_WIDGET (row), TRUE);
+ dialog->preselected_row2 = GTK_WIDGET (row);
+ }
+
+ if (widget == dialog->arrow_listbox)
+ {
+ row = gtk_list_box_get_row_at_y (GTK_LIST_BOX (dialog->original_name_listbox), y);
+ update_row_shadowing (GTK_WIDGET (row), TRUE);
+ dialog->preselected_row1 = GTK_WIDGET (row);
+
+ row = gtk_list_box_get_row_at_y (GTK_LIST_BOX (dialog->result_listbox), y);
+ update_row_shadowing (GTK_WIDGET (row), TRUE);
+ dialog->preselected_row2 = GTK_WIDGET (row);
+ }
+
+ if (widget == dialog->original_name_listbox)
+ {
+ row = gtk_list_box_get_row_at_y (GTK_LIST_BOX (dialog->result_listbox), y);
+ update_row_shadowing (GTK_WIDGET (row), TRUE);
+ dialog->preselected_row1 = GTK_WIDGET (row);
+
+ row = gtk_list_box_get_row_at_y (GTK_LIST_BOX (dialog->arrow_listbox), y);
+ update_row_shadowing (GTK_WIDGET (row), TRUE);
+ dialog->preselected_row2 = GTK_WIDGET (row);
+ }
+}
+
+static void
+on_event_controller_motion_leave (GtkEventControllerMotion *controller,
+ gpointer user_data)
+{
+ NautilusBatchRenameDialog *dialog;
+
+ dialog = NAUTILUS_BATCH_RENAME_DIALOG (user_data);
+
+ update_row_shadowing (dialog->preselected_row1, FALSE);
+ update_row_shadowing (dialog->preselected_row2, FALSE);
+
+ dialog->preselected_row1 = NULL;
+ dialog->preselected_row2 = NULL;
+}
+
+static void
+nautilus_batch_rename_dialog_initialize_actions (NautilusBatchRenameDialog *dialog)
+{
+ GAction *action;
+
+ dialog->action_group = G_ACTION_GROUP (g_simple_action_group_new ());
+
+ g_action_map_add_action_entries (G_ACTION_MAP (dialog->action_group),
+ dialog_entries,
+ G_N_ELEMENTS (dialog_entries),
+ dialog);
+ gtk_widget_insert_action_group (GTK_WIDGET (dialog),
+ "dialog",
+ G_ACTION_GROUP (dialog->action_group));
+
+ action = g_action_map_lookup_action (G_ACTION_MAP (dialog->action_group),
+ metadata_tags_constants[ORIGINAL_FILE_NAME].action_name);
+ g_simple_action_set_enabled (G_SIMPLE_ACTION (action), FALSE);
+
+ check_metadata_for_selection (dialog, dialog->selection,
+ dialog->metadata_cancellable);
+
+ /* Make sure that the state is initialized to name-ascending */
+ action = g_action_map_lookup_action (G_ACTION_MAP (dialog->action_group),
+ "numbering-order-changed");
+ g_action_change_state (action, g_variant_new_string ("name-ascending"));
+}
+
+static void
+file_names_widget_on_activate (NautilusBatchRenameDialog *dialog)
+{
+ prepare_batch_rename (dialog);
+}
+
+static void
+remove_tag (NautilusBatchRenameDialog *dialog,
+ TagData *tag_data)
+{
+ GAction *action;
+
+ if (!tag_data->set)
+ {
+ g_warning ("Trying to remove an already removed tag");
+
+ return;
+ }
+
+ tag_data->set = FALSE;
+ tag_data->position = -1;
+ action = g_action_map_lookup_action (G_ACTION_MAP (dialog->action_group),
+ tag_data->tag_constants.action_name);
+ g_simple_action_set_enabled (G_SIMPLE_ACTION (action), TRUE);
+}
+
+static gint
+compare_tag_position (gconstpointer a,
+ gconstpointer b)
+{
+ const TagData *tag_data1 = a;
+ const TagData *tag_data2 = b;
+
+ return tag_data1->position - tag_data2->position;
+}
+
+typedef enum
+{
+ TEXT_WAS_DELETED,
+ TEXT_WAS_INSERTED
+} TextChangedMode;
+
+static GList *
+get_tags_intersecting_sorted (NautilusBatchRenameDialog *self,
+ gint start_position,
+ gint end_position,
+ TextChangedMode text_changed_mode)
+{
+ g_autoptr (GList) tag_info_keys = NULL;
+ TagData *tag_data;
+ GList *l;
+ GList *intersecting_tags = NULL;
+ gint tag_end_position;
+
+ tag_info_keys = g_hash_table_get_keys (self->tag_info_table);
+ for (l = tag_info_keys; l != NULL; l = l->next)
+ {
+ g_autofree gchar *tag_text_representation = NULL;
+
+ tag_data = g_hash_table_lookup (self->tag_info_table, l->data);
+ tag_text_representation = batch_rename_get_tag_text_representation (tag_data->tag_constants);
+ tag_end_position = tag_data->position + g_utf8_strlen (tag_text_representation, -1);
+ if (tag_data->set && !tag_data->just_added)
+ {
+ gboolean selection_intersects_tag_start;
+ gboolean selection_intersects_tag_end;
+ gboolean tag_is_contained_in_selection;
+
+ if (text_changed_mode == TEXT_WAS_DELETED)
+ {
+ selection_intersects_tag_start = end_position > tag_data->position &&
+ end_position <= tag_end_position;
+ selection_intersects_tag_end = start_position >= tag_data->position &&
+ start_position < tag_end_position;
+ tag_is_contained_in_selection = start_position <= tag_data->position &&
+ end_position >= tag_end_position;
+ }
+ else
+ {
+ selection_intersects_tag_start = start_position > tag_data->position &&
+ start_position < tag_end_position;
+ selection_intersects_tag_end = FALSE;
+ tag_is_contained_in_selection = FALSE;
+ }
+ if (selection_intersects_tag_end || selection_intersects_tag_start || tag_is_contained_in_selection)
+ {
+ intersecting_tags = g_list_prepend (intersecting_tags, tag_data);
+ }
+ }
+ }
+
+ return g_list_sort (intersecting_tags, compare_tag_position);
+}
+
+static void
+update_tags_positions (NautilusBatchRenameDialog *self,
+ gint start_position,
+ gint end_position,
+ TextChangedMode text_changed_mode)
+{
+ g_autoptr (GList) tag_info_keys = NULL;
+ TagData *tag_data;
+ GList *l;
+
+ tag_info_keys = g_hash_table_get_keys (self->tag_info_table);
+ for (l = tag_info_keys; l != NULL; l = l->next)
+ {
+ tag_data = g_hash_table_lookup (self->tag_info_table, l->data);
+ if (tag_data->set && !tag_data->just_added && tag_data->position >= start_position)
+ {
+ if (text_changed_mode == TEXT_WAS_DELETED)
+ {
+ tag_data->position -= end_position - start_position;
+ }
+ else
+ {
+ tag_data->position += end_position - start_position;
+ }
+ }
+ }
+}
+
+static void
+on_delete_text (GtkEditable *editable,
+ gint start_position,
+ gint end_position,
+ gpointer user_data)
+{
+ NautilusBatchRenameDialog *self;
+ g_autoptr (GList) intersecting_tags = NULL;
+ gint final_start_position;
+ gint final_end_position;
+ GList *l;
+
+ self = NAUTILUS_BATCH_RENAME_DIALOG (user_data);
+ intersecting_tags = get_tags_intersecting_sorted (self, start_position,
+ end_position, TEXT_WAS_DELETED);
+ if (intersecting_tags)
+ {
+ gint last_tag_end_position;
+ g_autofree gchar *tag_text_representation = NULL;
+ TagData *first_tag = g_list_first (intersecting_tags)->data;
+ TagData *last_tag = g_list_last (intersecting_tags)->data;
+
+ tag_text_representation = batch_rename_get_tag_text_representation (last_tag->tag_constants);
+ last_tag_end_position = last_tag->position +
+ g_utf8_strlen (tag_text_representation, -1);
+ final_start_position = MIN (start_position, first_tag->position);
+ final_end_position = MAX (end_position, last_tag_end_position);
+ }
+ else
+ {
+ final_start_position = start_position;
+ final_end_position = end_position;
+ }
+
+ g_signal_handlers_block_by_func (editable, (gpointer) on_delete_text, user_data);
+ gtk_editable_delete_text (editable, final_start_position, final_end_position);
+ g_signal_handlers_unblock_by_func (editable, (gpointer) on_delete_text, user_data);
+
+ /* Mark the tags as removed */
+ for (l = intersecting_tags; l != NULL; l = l->next)
+ {
+ remove_tag (self, l->data);
+ }
+
+ /* If we removed the numbering tag, we want to enable all numbering actions */
+ if (!numbering_tag_is_some_added (self))
+ {
+ guint i;
+
+ for (i = 0; i < G_N_ELEMENTS (numbering_tags_constants); i++)
+ {
+ enable_action (self, numbering_tags_constants[i].action_name);
+ }
+ }
+
+ update_tags_positions (self, final_start_position,
+ final_end_position, TEXT_WAS_DELETED);
+ update_display_text (self);
+
+ g_signal_stop_emission_by_name (editable, "delete-text");
+}
+
+static void
+on_insert_text (GtkEditable *editable,
+ const gchar *new_text,
+ gint new_text_length,
+ gpointer position,
+ gpointer user_data)
+{
+ NautilusBatchRenameDialog *self;
+ gint start_position;
+ gint end_position;
+ g_autoptr (GList) intersecting_tags = NULL;
+
+ self = NAUTILUS_BATCH_RENAME_DIALOG (user_data);
+ start_position = *(int *) position;
+ end_position = start_position + g_utf8_strlen (new_text, -1);
+ intersecting_tags = get_tags_intersecting_sorted (self, start_position,
+ end_position, TEXT_WAS_INSERTED);
+ if (!intersecting_tags)
+ {
+ g_signal_handlers_block_by_func (editable, (gpointer) on_insert_text, user_data);
+ gtk_editable_insert_text (editable, new_text, new_text_length, position);
+ g_signal_handlers_unblock_by_func (editable, (gpointer) on_insert_text, user_data);
+
+ update_tags_positions (self, start_position, end_position, TEXT_WAS_INSERTED);
+ update_display_text (self);
+ }
+
+ g_signal_stop_emission_by_name (editable, "insert-text");
+}
+
+static void
+file_names_widget_entry_on_changed (NautilusBatchRenameDialog *self)
+{
+ update_display_text (self);
+}
+
+static void
+nautilus_batch_rename_dialog_finalize (GObject *object)
+{
+ NautilusBatchRenameDialog *dialog;
+ GList *l;
+ guint i;
+
+ dialog = NAUTILUS_BATCH_RENAME_DIALOG (object);
+
+ if (dialog->directories_pending_conflict_check != NULL)
+ {
+ cancel_conflict_check (dialog);
+ }
+
+ g_list_free (dialog->listbox_labels_new);
+ g_list_free (dialog->listbox_labels_old);
+ g_list_free (dialog->listbox_icons);
+
+ for (l = dialog->selection_metadata; l != NULL; l = l->next)
+ {
+ FileMetadata *file_metadata;
+
+ file_metadata = l->data;
+ for (i = 0; i < G_N_ELEMENTS (file_metadata->metadata); i++)
+ {
+ if (file_metadata->metadata[i])
+ {
+ g_string_free (file_metadata->metadata[i], TRUE);
+ }
+ }
+
+ g_string_free (file_metadata->file_name, TRUE);
+ g_free (file_metadata);
+ }
+
+ if (dialog->create_date != NULL)
+ {
+ g_hash_table_destroy (dialog->create_date);
+ }
+
+ g_list_free_full (dialog->new_names, string_free);
+ g_list_free_full (dialog->duplicates, conflict_data_free);
+
+ nautilus_file_list_free (dialog->selection);
+ nautilus_directory_unref (dialog->directory);
+ nautilus_directory_list_free (dialog->distinct_parent_directories);
+
+ g_object_unref (dialog->size_group);
+
+ g_hash_table_destroy (dialog->tag_info_table);
+
+ g_cancellable_cancel (dialog->metadata_cancellable);
+ g_clear_object (&dialog->metadata_cancellable);
+
+ G_OBJECT_CLASS (nautilus_batch_rename_dialog_parent_class)->finalize (object);
+}
+
+static void
+nautilus_batch_rename_dialog_class_init (NautilusBatchRenameDialogClass *klass)
+{
+ GtkWidgetClass *widget_class;
+ GObjectClass *oclass;
+
+ widget_class = GTK_WIDGET_CLASS (klass);
+ oclass = G_OBJECT_CLASS (klass);
+
+ oclass->finalize = nautilus_batch_rename_dialog_finalize;
+
+ gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/nautilus/ui/nautilus-batch-rename-dialog.ui");
+
+ gtk_widget_class_bind_template_child (widget_class, NautilusBatchRenameDialog, grid);
+ gtk_widget_class_bind_template_child (widget_class, NautilusBatchRenameDialog, cancel_button);
+ gtk_widget_class_bind_template_child (widget_class, NautilusBatchRenameDialog, original_name_listbox);
+ gtk_widget_class_bind_template_child (widget_class, NautilusBatchRenameDialog, arrow_listbox);
+ gtk_widget_class_bind_template_child (widget_class, NautilusBatchRenameDialog, result_listbox);
+ gtk_widget_class_bind_template_child (widget_class, NautilusBatchRenameDialog, name_entry);
+ gtk_widget_class_bind_template_child (widget_class, NautilusBatchRenameDialog, rename_button);
+ gtk_widget_class_bind_template_child (widget_class, NautilusBatchRenameDialog, find_entry);
+ gtk_widget_class_bind_template_child (widget_class, NautilusBatchRenameDialog, replace_entry);
+ gtk_widget_class_bind_template_child (widget_class, NautilusBatchRenameDialog, mode_stack);
+ gtk_widget_class_bind_template_child (widget_class, NautilusBatchRenameDialog, replace_mode_button);
+ gtk_widget_class_bind_template_child (widget_class, NautilusBatchRenameDialog, format_mode_button);
+ gtk_widget_class_bind_template_child (widget_class, NautilusBatchRenameDialog, numbering_order_button);
+ gtk_widget_class_bind_template_child (widget_class, NautilusBatchRenameDialog, scrolled_window);
+ gtk_widget_class_bind_template_child (widget_class, NautilusBatchRenameDialog, numbering_order_menu);
+ gtk_widget_class_bind_template_child (widget_class, NautilusBatchRenameDialog, numbering_revealer);
+ gtk_widget_class_bind_template_child (widget_class, NautilusBatchRenameDialog, conflict_box);
+ gtk_widget_class_bind_template_child (widget_class, NautilusBatchRenameDialog, conflict_label);
+ gtk_widget_class_bind_template_child (widget_class, NautilusBatchRenameDialog, conflict_up);
+ gtk_widget_class_bind_template_child (widget_class, NautilusBatchRenameDialog, conflict_down);
+ gtk_widget_class_bind_template_child (widget_class, NautilusBatchRenameDialog, numbering_label);
+
+ gtk_widget_class_bind_template_callback (widget_class, file_names_widget_on_activate);
+ gtk_widget_class_bind_template_callback (widget_class, file_names_widget_entry_on_changed);
+ gtk_widget_class_bind_template_callback (widget_class, batch_rename_dialog_mode_changed);
+ gtk_widget_class_bind_template_callback (widget_class, select_next_conflict_up);
+ gtk_widget_class_bind_template_callback (widget_class, select_next_conflict_down);
+ gtk_widget_class_bind_template_callback (widget_class, batch_rename_dialog_on_response);
+}
+
+GtkWidget *
+nautilus_batch_rename_dialog_new (GList *selection,
+ NautilusDirectory *directory,
+ NautilusWindow *window)
+{
+ NautilusBatchRenameDialog *dialog;
+ GString *dialog_title;
+ GList *l;
+ gboolean all_targets_are_folders;
+ gboolean all_targets_are_regular_files;
+
+ dialog = g_object_new (NAUTILUS_TYPE_BATCH_RENAME_DIALOG, "use-header-bar", TRUE, NULL);
+
+ dialog->selection = nautilus_file_list_copy (selection);
+ dialog->directory = nautilus_directory_ref (directory);
+ dialog->window = window;
+
+ gtk_window_set_transient_for (GTK_WINDOW (dialog),
+ GTK_WINDOW (window));
+
+ all_targets_are_folders = TRUE;
+ for (l = selection; l != NULL; l = l->next)
+ {
+ if (!nautilus_file_is_directory (NAUTILUS_FILE (l->data)))
+ {
+ all_targets_are_folders = FALSE;
+ break;
+ }
+ }
+
+ all_targets_are_regular_files = TRUE;
+ for (l = selection; l != NULL; l = l->next)
+ {
+ if (!nautilus_file_is_regular_file (NAUTILUS_FILE (l->data)))
+ {
+ all_targets_are_regular_files = FALSE;
+ break;
+ }
+ }
+
+ dialog_title = g_string_new ("");
+ if (all_targets_are_folders)
+ {
+ g_string_append_printf (dialog_title,
+ ngettext ("Rename %d Folder",
+ "Rename %d Folders",
+ g_list_length (selection)),
+ g_list_length (selection));
+ }
+ else if (all_targets_are_regular_files)
+ {
+ g_string_append_printf (dialog_title,
+ ngettext ("Rename %d File",
+ "Rename %d Files",
+ g_list_length (selection)),
+ g_list_length (selection));
+ }
+ else
+ {
+ g_string_append_printf (dialog_title,
+ /* To translators: %d is the total number of files and folders.
+ * Singular case of the string is never used */
+ ngettext ("Rename %d File and Folder",
+ "Rename %d Files and Folders",
+ g_list_length (selection)),
+ g_list_length (selection));
+ }
+
+ gtk_window_set_title (GTK_WINDOW (dialog), dialog_title->str);
+
+ dialog->distinct_parent_directories = batch_rename_files_get_distinct_parents (selection);
+
+ add_tag (dialog, metadata_tags_constants[ORIGINAL_FILE_NAME]);
+
+ nautilus_batch_rename_dialog_initialize_actions (dialog);
+
+ update_display_text (dialog);
+
+ fill_display_listbox (dialog);
+
+ gtk_widget_set_cursor (GTK_WIDGET (window), NULL);
+
+ g_string_free (dialog_title, TRUE);
+
+ return GTK_WIDGET (dialog);
+}
+
+static void
+connect_to_pointer_motion_events (NautilusBatchRenameDialog *self,
+ GtkWidget *listbox)
+{
+ GtkEventController *controller;
+
+ controller = gtk_event_controller_motion_new ();
+ gtk_widget_add_controller (listbox, controller);
+ gtk_event_controller_set_propagation_phase (controller, GTK_PHASE_CAPTURE);
+ g_signal_connect (controller, "leave",
+ G_CALLBACK (on_event_controller_motion_leave), self);
+ g_signal_connect (controller, "motion",
+ G_CALLBACK (on_event_controller_motion_motion), self);
+}
+
+static void
+nautilus_batch_rename_dialog_init (NautilusBatchRenameDialog *self)
+{
+ TagData *tag_data;
+ guint i;
+
+ gtk_widget_init_template (GTK_WIDGET (self));
+
+ gtk_list_box_set_header_func (GTK_LIST_BOX (self->original_name_listbox),
+ (GtkListBoxUpdateHeaderFunc) listbox_header_func,
+ self,
+ NULL);
+ gtk_list_box_set_header_func (GTK_LIST_BOX (self->arrow_listbox),
+ (GtkListBoxUpdateHeaderFunc) listbox_header_func,
+ self,
+ NULL);
+ gtk_list_box_set_header_func (GTK_LIST_BOX (self->result_listbox),
+ (GtkListBoxUpdateHeaderFunc) listbox_header_func,
+ self,
+ NULL);
+
+
+ self->mode = NAUTILUS_BATCH_RENAME_DIALOG_FORMAT;
+
+ gtk_label_set_ellipsize (GTK_LABEL (self->conflict_label), PANGO_ELLIPSIZE_END);
+ gtk_label_set_max_width_chars (GTK_LABEL (self->conflict_label), 1);
+
+ self->duplicates = NULL;
+ self->distinct_parent_directories = NULL;
+ self->directories_pending_conflict_check = NULL;
+ self->new_names = NULL;
+ self->rename_clicked = FALSE;
+
+ self->tag_info_table = g_hash_table_new_full (g_str_hash,
+ g_str_equal,
+ (GDestroyNotify) g_free,
+ (GDestroyNotify) g_free);
+
+ for (i = 0; i < G_N_ELEMENTS (numbering_tags_constants); i++)
+ {
+ g_autofree gchar *tag_text_representation = NULL;
+
+ tag_text_representation = batch_rename_get_tag_text_representation (numbering_tags_constants[i]);
+ tag_data = g_new (TagData, 1);
+ tag_data->available = TRUE;
+ tag_data->set = FALSE;
+ tag_data->position = -1;
+ tag_data->tag_constants = numbering_tags_constants[i];
+ g_hash_table_insert (self->tag_info_table, g_strdup (tag_text_representation), tag_data);
+ }
+
+ for (i = 0; i < G_N_ELEMENTS (metadata_tags_constants); i++)
+ {
+ g_autofree gchar *tag_text_representation = NULL;
+
+ /* Only the original name is available and set at the start */
+ tag_text_representation = batch_rename_get_tag_text_representation (metadata_tags_constants[i]);
+
+ tag_data = g_new (TagData, 1);
+ tag_data->available = FALSE;
+ tag_data->set = FALSE;
+ tag_data->position = -1;
+ tag_data->tag_constants = metadata_tags_constants[i];
+ g_hash_table_insert (self->tag_info_table, g_strdup (tag_text_representation), tag_data);
+ }
+
+ self->row_height = -1;
+
+ g_signal_connect_object (gtk_editable_get_delegate (GTK_EDITABLE (self->name_entry)),
+ "delete-text", G_CALLBACK (on_delete_text), self, 0);
+ g_signal_connect_object (gtk_editable_get_delegate (GTK_EDITABLE (self->name_entry)),
+ "insert-text", G_CALLBACK (on_insert_text), self, 0);
+
+ self->size_group = gtk_size_group_new (GTK_SIZE_GROUP_HORIZONTAL);
+
+ connect_to_pointer_motion_events (self, self->original_name_listbox);
+ connect_to_pointer_motion_events (self, self->result_listbox);
+ connect_to_pointer_motion_events (self, self->arrow_listbox);
+
+ self->metadata_cancellable = g_cancellable_new ();
+}
diff --git a/src/nautilus-batch-rename-dialog.h b/src/nautilus-batch-rename-dialog.h
new file mode 100644
index 0000000..0749fa3
--- /dev/null
+++ b/src/nautilus-batch-rename-dialog.h
@@ -0,0 +1,232 @@
+/* nautilus-batch-rename-utilities.c
+ *
+ * Copyright (C) 2016 Alexandru Pandelea <alexandru.pandelea@gmail.com>
+ *
+ * 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/>.
+ */
+
+#pragma once
+
+#include <glib.h>
+#include <glib/gprintf.h>
+#include <glib/gi18n.h>
+#include <gtk/gtk.h>
+#include "nautilus-files-view.h"
+
+G_BEGIN_DECLS
+
+typedef enum
+{
+ EQUIPMENT,
+ CREATION_DATE,
+ SEASON_NUMBER,
+ EPISODE_NUMBER,
+ TRACK_NUMBER,
+ ARTIST_NAME,
+ TITLE,
+ ALBUM_NAME,
+ ORIGINAL_FILE_NAME,
+ METADATA_INVALID,
+} MetadataType;
+
+typedef enum
+{
+ NUMBERING_NO_ZERO_PAD,
+ NUMBERING_ONE_ZERO_PAD,
+ NUMBERING_TWO_ZERO_PAD,
+ NUMBERING_INVALID,
+} NumberingType;
+
+typedef enum {
+ NAUTILUS_BATCH_RENAME_DIALOG_APPEND = 0,
+ NAUTILUS_BATCH_RENAME_DIALOG_PREPEND = 1,
+ NAUTILUS_BATCH_RENAME_DIALOG_REPLACE = 2,
+ NAUTILUS_BATCH_RENAME_DIALOG_FORMAT = 3,
+} NautilusBatchRenameDialogMode;
+
+typedef enum {
+ ORIGINAL_ASCENDING = 0,
+ ORIGINAL_DESCENDING = 1,
+ FIRST_MODIFIED = 2,
+ LAST_MODIFIED = 3,
+ FIRST_CREATED = 4,
+ LAST_CREATED = 5,
+} SortMode;
+
+typedef struct
+{
+ const gchar *action_name;
+ const gchar *label;
+ MetadataType metadata_type;
+ NumberingType numbering_type;
+ gboolean is_metadata;
+} TagConstants;
+
+typedef struct
+{
+ const gchar *action_target_name;
+ const gchar *label;
+ const SortMode sort_mode;
+} SortConstants;
+
+static const SortConstants sorts_constants[] =
+{
+ {
+ "name-ascending",
+ N_("Original Name (Ascending)"),
+ ORIGINAL_ASCENDING,
+ },
+ {
+ "name-descending",
+ N_("Original Name (Descending)"),
+ ORIGINAL_DESCENDING,
+ },
+ {
+ "first-modified",
+ N_("First Modified"),
+ FIRST_MODIFIED,
+ },
+ {
+ "last-modified",
+ N_("Last Modified"),
+ LAST_MODIFIED,
+ },
+ {
+ "first-created",
+ N_("First Created"),
+ FIRST_CREATED,
+ },
+ {
+ "last-created",
+ N_("Last Created"),
+ LAST_CREATED,
+ },
+};
+
+static const TagConstants metadata_tags_constants[] =
+{
+ {
+ "add-equipment-tag",
+ N_("Camera model"),
+ EQUIPMENT,
+ NUMBERING_INVALID,
+ TRUE,
+ },
+ {
+ "add-creation-date-tag",
+ N_("Creation date"),
+ CREATION_DATE,
+ NUMBERING_INVALID,
+ TRUE,
+ },
+ {
+ "add-season-number-tag",
+ N_("Season number"),
+ SEASON_NUMBER,
+ NUMBERING_INVALID,
+ TRUE,
+ },
+ {
+ "add-episode-number-tag",
+ N_("Episode number"),
+ EPISODE_NUMBER,
+ NUMBERING_INVALID,
+ TRUE,
+ },
+ {
+ "add-track-number-tag",
+ N_("Track number"),
+ TRACK_NUMBER,
+ NUMBERING_INVALID,
+ TRUE,
+ },
+ {
+ "add-artist-name-tag",
+ N_("Artist name"),
+ ARTIST_NAME,
+ NUMBERING_INVALID,
+ TRUE,
+ },
+ {
+ "add-title-tag",
+ N_("Title"),
+ TITLE,
+ NUMBERING_INVALID,
+ TRUE,
+ },
+ {
+ "add-album-name-tag",
+ N_("Album name"),
+ ALBUM_NAME,
+ NUMBERING_INVALID,
+ TRUE,
+ },
+ {
+ "add-original-file-name-tag",
+ N_("Original file name"),
+ ORIGINAL_FILE_NAME,
+ NUMBERING_INVALID,
+ TRUE,
+ },
+};
+
+static const TagConstants numbering_tags_constants[] =
+{
+ {
+ "add-numbering-no-zero-pad-tag",
+ N_("1, 2, 3"),
+ METADATA_INVALID,
+ NUMBERING_NO_ZERO_PAD,
+ FALSE,
+ },
+ {
+ "add-numbering-one-zero-pad-tag",
+ N_("01, 02, 03"),
+ METADATA_INVALID,
+ NUMBERING_ONE_ZERO_PAD,
+ FALSE,
+ },
+ {
+ "add-numbering-two-zero-pad-tag",
+ N_("001, 002, 003"),
+ METADATA_INVALID,
+ NUMBERING_TWO_ZERO_PAD,
+ FALSE,
+ },
+};
+
+typedef struct
+{
+ gchar *name;
+ gint index;
+} ConflictData;
+
+typedef struct {
+ GString *file_name;
+ GString *metadata [G_N_ELEMENTS (metadata_tags_constants)];
+} FileMetadata;
+
+#define NAUTILUS_TYPE_BATCH_RENAME_DIALOG (nautilus_batch_rename_dialog_get_type())
+
+G_DECLARE_FINAL_TYPE (NautilusBatchRenameDialog, nautilus_batch_rename_dialog, NAUTILUS, BATCH_RENAME_DIALOG, GtkDialog);
+
+GtkWidget* nautilus_batch_rename_dialog_new (GList *selection,
+ NautilusDirectory *directory,
+ NautilusWindow *window);
+
+void nautilus_batch_rename_dialog_query_finished (NautilusBatchRenameDialog *dialog,
+ GHashTable *hash_table,
+ GList *selection_metadata);
+
+G_END_DECLS \ No newline at end of file
diff --git a/src/nautilus-batch-rename-utilities.c b/src/nautilus-batch-rename-utilities.c
new file mode 100644
index 0000000..a81fc2e
--- /dev/null
+++ b/src/nautilus-batch-rename-utilities.c
@@ -0,0 +1,1186 @@
+/* nautilus-batch-rename-utilities.c
+ *
+ * Copyright (C) 2016 Alexandru Pandelea <alexandru.pandelea@gmail.com>
+ *
+ * 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/>.
+ */
+
+#include "nautilus-batch-rename-dialog.h"
+#include "nautilus-batch-rename-utilities.h"
+#include "nautilus-file.h"
+#include "nautilus-tracker-utilities.h"
+
+#include <glib.h>
+#include <gtk/gtk.h>
+#include <string.h>
+#include <stdarg.h>
+#include <eel/eel-vfs-extensions.h>
+
+typedef struct
+{
+ NautilusFile *file;
+ gint position;
+} CreateDateElem;
+
+typedef struct
+{
+ NautilusBatchRenameDialog *dialog;
+ GHashTable *date_order_hash_table;
+
+ GList *selection_metadata;
+
+ gboolean has_metadata[G_N_ELEMENTS (metadata_tags_constants)];
+
+ GCancellable *cancellable;
+} QueryData;
+
+enum
+{
+ FILE_NAME_INDEX,
+ CREATION_DATE_INDEX,
+ YEAR_INDEX,
+ MONTH_INDEX,
+ DAY_INDEX,
+ HOURS_INDEX,
+ MINUTES_INDEX,
+ SECONDS_INDEX,
+ CAMERA_MODEL_INDEX,
+ SEASON_INDEX,
+ EPISODE_NUMBER_INDEX,
+ TRACK_NUMBER_INDEX,
+ ARTIST_NAME_INDEX,
+ TITLE_INDEX,
+ ALBUM_NAME_INDEX,
+} QueryMetadata;
+
+static void on_cursor_callback (GObject *object,
+ GAsyncResult *result,
+ gpointer user_data);
+
+void
+string_free (gpointer mem)
+{
+ if (mem != NULL)
+ {
+ g_string_free (mem, TRUE);
+ }
+}
+
+void
+conflict_data_free (gpointer mem)
+{
+ ConflictData *conflict_data = mem;
+
+ g_free (conflict_data->name);
+ g_free (conflict_data);
+}
+
+gchar *
+batch_rename_get_tag_text_representation (TagConstants tag_constants)
+{
+ return g_strdup_printf ("[%s]", gettext (tag_constants.label));
+}
+
+static GString *
+batch_rename_replace (gchar *string,
+ gchar *substring,
+ gchar *replacement)
+{
+ GString *new_string;
+ gchar **splitted_string;
+ gint i, n_splits;
+
+ new_string = g_string_new ("");
+
+ if (substring == NULL || replacement == NULL)
+ {
+ g_string_append (new_string, string);
+
+ return new_string;
+ }
+
+ if (g_utf8_strlen (substring, -1) == 0)
+ {
+ g_string_append (new_string, string);
+
+ return new_string;
+ }
+
+ splitted_string = g_strsplit (string, substring, -1);
+ if (splitted_string == NULL)
+ {
+ g_string_append (new_string, string);
+
+ return new_string;
+ }
+
+ n_splits = g_strv_length (splitted_string);
+
+ for (i = 0; i < n_splits; i++)
+ {
+ g_string_append (new_string, splitted_string[i]);
+
+ if (i != n_splits - 1)
+ {
+ g_string_append (new_string, replacement);
+ }
+ }
+
+ g_strfreev (splitted_string);
+
+ return new_string;
+}
+
+void
+batch_rename_sort_lists_for_rename (GList **selection,
+ GList **new_names,
+ GList **old_names,
+ GList **new_files,
+ GList **old_files,
+ gboolean is_undo_redo)
+{
+ GList *new_names_list;
+ GList *new_names_list2;
+ GList *files;
+ GList *files2;
+ GList *old_names_list = NULL;
+ GList *new_files_list = NULL;
+ GList *old_files_list = NULL;
+ GList *old_names_list2 = NULL;
+ GList *new_files_list2 = NULL;
+ GList *old_files_list2 = NULL;
+ GString *new_file_name;
+ GString *new_name;
+ GString *old_name;
+ GFile *new_file;
+ GFile *old_file;
+ NautilusFile *file;
+ gboolean order_changed = TRUE;
+
+ /* in the following case:
+ * file1 -> file2
+ * file2 -> file3
+ * file2 must be renamed first, so because of that, the list has to be reordered
+ */
+ while (order_changed)
+ {
+ order_changed = FALSE;
+
+ if (is_undo_redo)
+ {
+ old_names_list = *old_names;
+ new_files_list = *new_files;
+ old_files_list = *old_files;
+ }
+
+ for (new_names_list = *new_names, files = *selection;
+ new_names_list != NULL && files != NULL;
+ new_names_list = new_names_list->next, files = files->next)
+ {
+ g_autofree gchar *old_file_name = NULL;
+ g_autoptr (NautilusFile) parent = NULL;
+
+ old_file_name = nautilus_file_get_name (NAUTILUS_FILE (files->data));
+ new_file_name = new_names_list->data;
+ parent = nautilus_file_get_parent (NAUTILUS_FILE (files->data));
+
+ if (is_undo_redo)
+ {
+ old_names_list2 = old_names_list;
+ new_files_list2 = new_files_list;
+ old_files_list2 = old_files_list;
+ }
+
+ for (files2 = files, new_names_list2 = new_names_list;
+ files2 != NULL && new_names_list2 != NULL;
+ files2 = files2->next, new_names_list2 = new_names_list2->next)
+ {
+ g_autofree gchar *file_name = NULL;
+ g_autoptr (NautilusFile) parent2 = NULL;
+
+ file_name = nautilus_file_get_name (NAUTILUS_FILE (files2->data));
+ new_name = new_names_list2->data;
+
+ parent2 = nautilus_file_get_parent (NAUTILUS_FILE (files2->data));
+
+ if (files2 != files && g_strcmp0 (file_name, new_file_name->str) == 0 &&
+ parent == parent2)
+ {
+ file = NAUTILUS_FILE (files2->data);
+
+ *selection = g_list_remove_link (*selection, files2);
+ *new_names = g_list_remove_link (*new_names, new_names_list2);
+
+ *selection = g_list_prepend (*selection, file);
+ *new_names = g_list_prepend (*new_names, new_name);
+
+ if (is_undo_redo)
+ {
+ old_name = old_names_list2->data;
+ new_file = new_files_list2->data;
+ old_file = old_files_list2->data;
+
+ *old_names = g_list_remove_link (*old_names, old_names_list2);
+ *new_files = g_list_remove_link (*new_files, new_files_list2);
+ *old_files = g_list_remove_link (*old_files, old_files_list2);
+
+ *old_names = g_list_prepend (*old_names, old_name);
+ *new_files = g_list_prepend (*new_files, new_file);
+ *old_files = g_list_prepend (*old_files, old_file);
+ }
+
+ order_changed = TRUE;
+ break;
+ }
+
+ if (is_undo_redo)
+ {
+ old_names_list2 = old_names_list2->next;
+ new_files_list2 = new_files_list2->next;
+ old_files_list2 = old_files_list2->next;
+ }
+ }
+
+ if (is_undo_redo)
+ {
+ old_names_list = old_names_list->next;
+ new_files_list = new_files_list->next;
+ old_files_list = old_files_list->next;
+ }
+ }
+ }
+}
+
+/* This function changes the background color of the replaced part of the name */
+GString *
+batch_rename_replace_label_text (gchar *label,
+ const gchar *substring)
+{
+ GString *new_label;
+ gchar **splitted_string;
+ gchar *token;
+ gint i, n_splits;
+
+ new_label = g_string_new ("");
+
+ if (substring == NULL || g_strcmp0 (substring, "") == 0)
+ {
+ token = g_markup_escape_text (label, -1);
+ new_label = g_string_append (new_label, token);
+ g_free (token);
+
+ return new_label;
+ }
+
+ splitted_string = g_strsplit (label, substring, -1);
+ if (splitted_string == NULL)
+ {
+ token = g_markup_escape_text (label, -1);
+ new_label = g_string_append (new_label, token);
+ g_free (token);
+
+ return new_label;
+ }
+
+ n_splits = g_strv_length (splitted_string);
+
+ for (i = 0; i < n_splits; i++)
+ {
+ token = g_markup_escape_text (splitted_string[i], -1);
+ new_label = g_string_append (new_label, token);
+
+ g_free (token);
+
+ if (i != n_splits - 1)
+ {
+ token = g_markup_escape_text (substring, -1);
+ g_string_append_printf (new_label,
+ "<span background=\'#f57900\' color='white'>%s</span>",
+ token);
+
+ g_free (token);
+ }
+ }
+
+ g_strfreev (splitted_string);
+
+ return new_label;
+}
+
+static gchar *
+get_metadata (GList *selection_metadata,
+ gchar *file_name,
+ MetadataType metadata_type)
+{
+ GList *l;
+ FileMetadata *file_metadata;
+ gchar *metadata = NULL;
+
+ for (l = selection_metadata; l != NULL; l = l->next)
+ {
+ file_metadata = l->data;
+ if (g_strcmp0 (file_name, file_metadata->file_name->str) == 0)
+ {
+ if (file_metadata->metadata[metadata_type] &&
+ file_metadata->metadata[metadata_type]->len > 0)
+ {
+ metadata = file_metadata->metadata[metadata_type]->str;
+ }
+
+ break;
+ }
+ }
+
+ return metadata;
+}
+
+static GString *
+batch_rename_format (NautilusFile *file,
+ GList *text_chunks,
+ GList *selection_metadata,
+ gint count)
+{
+ GList *l;
+ GString *tag_string;
+ GString *new_name;
+ gboolean added_tag;
+ MetadataType metadata_type;
+ g_autofree gchar *file_name = NULL;
+ g_autofree gchar *extension = NULL;
+ gint i;
+ gchar *metadata;
+
+ file_name = nautilus_file_get_display_name (file);
+ if (!nautilus_file_is_directory (file))
+ {
+ extension = nautilus_file_get_extension (file);
+ }
+
+ new_name = g_string_new ("");
+
+ for (l = text_chunks; l != NULL; l = l->next)
+ {
+ added_tag = FALSE;
+ tag_string = l->data;
+
+ for (i = 0; i < G_N_ELEMENTS (numbering_tags_constants); i++)
+ {
+ g_autofree gchar *tag_text_representation = NULL;
+
+ tag_text_representation = batch_rename_get_tag_text_representation (numbering_tags_constants[i]);
+ if (g_strcmp0 (tag_string->str, tag_text_representation) == 0)
+ {
+ switch (numbering_tags_constants[i].numbering_type)
+ {
+ case NUMBERING_NO_ZERO_PAD:
+ {
+ g_string_append_printf (new_name, "%d", count);
+ }
+ break;
+
+ case NUMBERING_ONE_ZERO_PAD:
+ {
+ g_string_append_printf (new_name, "%02d", count);
+ }
+ break;
+
+ case NUMBERING_TWO_ZERO_PAD:
+ {
+ g_string_append_printf (new_name, "%03d", count);
+ }
+ break;
+
+ default:
+ {
+ g_warn_if_reached ();
+ }
+ break;
+ }
+
+ added_tag = TRUE;
+ break;
+ }
+ }
+
+ if (added_tag)
+ {
+ continue;
+ }
+
+ for (i = 0; i < G_N_ELEMENTS (metadata_tags_constants); i++)
+ {
+ g_autofree gchar *tag_text_representation = NULL;
+
+ tag_text_representation = batch_rename_get_tag_text_representation (metadata_tags_constants[i]);
+ if (g_strcmp0 (tag_string->str, tag_text_representation) == 0)
+ {
+ metadata_type = metadata_tags_constants[i].metadata_type;
+ metadata = get_metadata (selection_metadata, file_name, metadata_type);
+
+ /* TODO: This is a hack, we should provide a cancellable for checking
+ * the metadata, and if that is happening don't enter here. We can
+ * special case original file name upper in the call stack */
+ if (!metadata && metadata_type != ORIGINAL_FILE_NAME)
+ {
+ g_warning ("Metadata not present in one file, it shouldn't have been added. File name: %s, Metadata: %s",
+ file_name, metadata_tags_constants[i].label);
+ continue;
+ }
+
+ switch (metadata_type)
+ {
+ case ORIGINAL_FILE_NAME:
+ {
+ if (nautilus_file_is_directory (file))
+ {
+ new_name = g_string_append (new_name, file_name);
+ }
+ else
+ {
+ g_autofree gchar *base_name = NULL;
+ base_name = eel_filename_strip_extension (file_name);
+ new_name = g_string_append (new_name, base_name);
+ }
+ }
+ break;
+
+ case TRACK_NUMBER:
+ {
+ g_string_append_printf (new_name, "%02d", atoi (metadata));
+ }
+ break;
+
+ default:
+ {
+ new_name = g_string_append (new_name, metadata);
+ }
+ break;
+ }
+
+ added_tag = TRUE;
+ break;
+ }
+ }
+
+ if (!added_tag)
+ {
+ new_name = g_string_append (new_name, tag_string->str);
+ }
+ }
+
+ if (g_strcmp0 (new_name->str, "") == 0)
+ {
+ new_name = g_string_append (new_name, file_name);
+ }
+ else
+ {
+ if (extension != NULL)
+ {
+ new_name = g_string_append (new_name, extension);
+ }
+ }
+
+ return new_name;
+}
+
+GList *
+batch_rename_dialog_get_new_names_list (NautilusBatchRenameDialogMode mode,
+ GList *selection,
+ GList *text_chunks,
+ GList *selection_metadata,
+ gchar *entry_text,
+ gchar *replace_text)
+{
+ GList *l;
+ GList *result;
+ GString *file_name;
+ GString *new_name;
+ NautilusFile *file;
+ gchar *name;
+ gint count;
+
+ result = NULL;
+ count = 1;
+
+ for (l = selection; l != NULL; l = l->next)
+ {
+ file = NAUTILUS_FILE (l->data);
+
+ name = nautilus_file_get_name (file);
+ file_name = g_string_new (name);
+
+ /* get the new name here and add it to the list*/
+ if (mode == NAUTILUS_BATCH_RENAME_DIALOG_FORMAT)
+ {
+ new_name = batch_rename_format (file,
+ text_chunks,
+ selection_metadata,
+ count++);
+ result = g_list_prepend (result, new_name);
+ }
+
+ if (mode == NAUTILUS_BATCH_RENAME_DIALOG_REPLACE)
+ {
+ new_name = batch_rename_replace (file_name->str,
+ entry_text,
+ replace_text);
+ result = g_list_prepend (result, new_name);
+ }
+
+ g_string_free (file_name, TRUE);
+ g_free (name);
+ }
+
+ return result;
+}
+
+/* There is a case that a new name for a file conflicts with an existing file name
+ * in the directory but it's not a problem because the file in the directory that
+ * conflicts is part of the batch renaming selection and it's going to change the name anyway. */
+gboolean
+file_name_conflicts_with_results (GList *selection,
+ GList *new_names,
+ GString *old_name,
+ gchar *parent_uri)
+{
+ GList *l1;
+ GList *l2;
+ NautilusFile *selection_file;
+ GString *new_name;
+
+ for (l1 = selection, l2 = new_names; l1 != NULL && l2 != NULL; l1 = l1->next, l2 = l2->next)
+ {
+ g_autofree gchar *name1 = NULL;
+ g_autofree gchar *selection_parent_uri = NULL;
+
+ selection_file = NAUTILUS_FILE (l1->data);
+ name1 = nautilus_file_get_name (selection_file);
+
+ selection_parent_uri = nautilus_file_get_parent_uri (selection_file);
+
+ if (g_strcmp0 (name1, old_name->str) == 0)
+ {
+ new_name = l2->data;
+
+ /* if the name didn't change, then there's a conflict */
+ if (g_string_equal (old_name, new_name) &&
+ (parent_uri == NULL || g_strcmp0 (parent_uri, selection_parent_uri) == 0))
+ {
+ return FALSE;
+ }
+
+
+ /* if this file exists and it changed it's name, then there's no
+ * conflict */
+ return TRUE;
+ }
+ }
+
+ /* the case this function searched for doesn't exist, so the file
+ * has a conlfict */
+ return FALSE;
+}
+
+static gint
+compare_files_by_name_ascending (gconstpointer a,
+ gconstpointer b)
+{
+ NautilusFile *file1;
+ NautilusFile *file2;
+
+ file1 = NAUTILUS_FILE (a);
+ file2 = NAUTILUS_FILE (b);
+
+ return nautilus_file_compare_for_sort (file1, file2,
+ NAUTILUS_FILE_SORT_BY_DISPLAY_NAME,
+ FALSE, FALSE);
+}
+
+static gint
+compare_files_by_name_descending (gconstpointer a,
+ gconstpointer b)
+{
+ NautilusFile *file1;
+ NautilusFile *file2;
+
+ file1 = NAUTILUS_FILE (a);
+ file2 = NAUTILUS_FILE (b);
+
+ return nautilus_file_compare_for_sort (file1, file2,
+ NAUTILUS_FILE_SORT_BY_DISPLAY_NAME,
+ FALSE, TRUE);
+}
+
+static gint
+compare_files_by_first_modified (gconstpointer a,
+ gconstpointer b)
+{
+ NautilusFile *file1;
+ NautilusFile *file2;
+
+ file1 = NAUTILUS_FILE (a);
+ file2 = NAUTILUS_FILE (b);
+
+ return nautilus_file_compare_for_sort (file1, file2,
+ NAUTILUS_FILE_SORT_BY_MTIME,
+ FALSE, FALSE);
+}
+
+static gint
+compare_files_by_last_modified (gconstpointer a,
+ gconstpointer b)
+{
+ NautilusFile *file1;
+ NautilusFile *file2;
+
+ file1 = NAUTILUS_FILE (a);
+ file2 = NAUTILUS_FILE (b);
+
+ return nautilus_file_compare_for_sort (file1, file2,
+ NAUTILUS_FILE_SORT_BY_MTIME,
+ FALSE, TRUE);
+}
+
+static gint
+compare_files_by_first_created (gconstpointer a,
+ gconstpointer b)
+{
+ CreateDateElem *elem1;
+ CreateDateElem *elem2;
+
+ elem1 = (CreateDateElem *) a;
+ elem2 = (CreateDateElem *) b;
+
+ return elem1->position - elem2->position;
+}
+
+static gint
+compare_files_by_last_created (gconstpointer a,
+ gconstpointer b)
+{
+ CreateDateElem *elem1;
+ CreateDateElem *elem2;
+
+ elem1 = (CreateDateElem *) a;
+ elem2 = (CreateDateElem *) b;
+
+ return elem2->position - elem1->position;
+}
+
+GList *
+nautilus_batch_rename_dialog_sort (GList *selection,
+ SortMode mode,
+ GHashTable *creation_date_table)
+{
+ GList *l, *l2;
+ NautilusFile *file;
+ GList *create_date_list;
+ GList *create_date_list_sorted;
+ gchar *name;
+
+ if (mode == ORIGINAL_ASCENDING)
+ {
+ return g_list_sort (selection, compare_files_by_name_ascending);
+ }
+
+ if (mode == ORIGINAL_DESCENDING)
+ {
+ return g_list_sort (selection, compare_files_by_name_descending);
+ }
+
+ if (mode == FIRST_MODIFIED)
+ {
+ return g_list_sort (selection, compare_files_by_first_modified);
+ }
+
+ if (mode == LAST_MODIFIED)
+ {
+ return g_list_sort (selection, compare_files_by_last_modified);
+ }
+
+ if (mode == FIRST_CREATED || mode == LAST_CREATED)
+ {
+ create_date_list = NULL;
+
+ for (l = selection; l != NULL; l = l->next)
+ {
+ CreateDateElem *elem;
+ elem = g_new (CreateDateElem, 1);
+
+ file = NAUTILUS_FILE (l->data);
+
+ name = nautilus_file_get_name (file);
+ elem->file = file;
+ elem->position = GPOINTER_TO_INT (g_hash_table_lookup (creation_date_table, name));
+ g_free (name);
+
+ create_date_list = g_list_prepend (create_date_list, elem);
+ }
+
+ if (mode == FIRST_CREATED)
+ {
+ create_date_list_sorted = g_list_sort (create_date_list,
+ compare_files_by_first_created);
+ }
+ else
+ {
+ create_date_list_sorted = g_list_sort (create_date_list,
+ compare_files_by_last_created);
+ }
+
+ for (l = selection, l2 = create_date_list_sorted; l2 != NULL; l = l->next, l2 = l2->next)
+ {
+ CreateDateElem *elem = l2->data;
+ l->data = elem->file;
+ }
+
+ g_list_free_full (create_date_list, g_free);
+ }
+
+ return selection;
+}
+
+static void
+cursor_next (QueryData *query_data,
+ TrackerSparqlCursor *cursor)
+{
+ tracker_sparql_cursor_next_async (cursor,
+ query_data->cancellable,
+ on_cursor_callback,
+ query_data);
+}
+
+static void
+remove_metadata (QueryData *query_data,
+ MetadataType metadata_type)
+{
+ GList *l;
+ FileMetadata *metadata_to_delete;
+
+ for (l = query_data->selection_metadata; l != NULL; l = l->next)
+ {
+ metadata_to_delete = l->data;
+ if (metadata_to_delete->metadata[metadata_type])
+ {
+ g_string_free (metadata_to_delete->metadata[metadata_type], TRUE);
+ metadata_to_delete->metadata[metadata_type] = NULL;
+ }
+ }
+
+ query_data->has_metadata[metadata_type] = FALSE;
+}
+
+static GString *
+format_date_time (GDateTime *date_time)
+{
+ g_autofree gchar *date = NULL;
+ GString *formated_date;
+
+ date = g_date_time_format (date_time, "%x");
+ if (strstr (date, "/") != NULL)
+ {
+ formated_date = batch_rename_replace (date, "/", "-");
+ }
+ else
+ {
+ formated_date = g_string_new (date);
+ }
+
+ return formated_date;
+}
+
+static void
+on_cursor_callback (GObject *object,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ TrackerSparqlCursor *cursor;
+ gboolean success;
+ QueryData *query_data;
+ MetadataType metadata_type;
+ g_autoptr (GError) error = NULL;
+ GList *l;
+ FileMetadata *file_metadata;
+ GDateTime *date_time;
+ guint i;
+ const gchar *current_metadata;
+ const gchar *file_name;
+ const gchar *creation_date;
+ const gchar *year;
+ const gchar *month;
+ const gchar *day;
+ const gchar *hours;
+ const gchar *minutes;
+ const gchar *seconds;
+ const gchar *equipment;
+ const gchar *season_number;
+ const gchar *episode_number;
+ const gchar *track_number;
+ const gchar *artist_name;
+ const gchar *title;
+ const gchar *album_name;
+
+ file_metadata = NULL;
+
+ cursor = TRACKER_SPARQL_CURSOR (object);
+ query_data = user_data;
+
+ success = tracker_sparql_cursor_next_finish (cursor, result, &error);
+ if (!success)
+ {
+ if (error != NULL)
+ {
+ g_warning ("Error on batch rename tracker query cursor: %s", error->message);
+ }
+
+ g_clear_object (&cursor);
+
+ /* The dialog is going away at the time of cancellation */
+ if (error == NULL ||
+ (error != NULL && error->code != G_IO_ERROR_CANCELLED))
+ {
+ nautilus_batch_rename_dialog_query_finished (query_data->dialog,
+ query_data->date_order_hash_table,
+ query_data->selection_metadata);
+ }
+
+ g_free (query_data);
+
+ return;
+ }
+
+ creation_date = tracker_sparql_cursor_get_string (cursor, CREATION_DATE_INDEX, NULL);
+
+ year = tracker_sparql_cursor_get_string (cursor, YEAR_INDEX, NULL);
+ month = tracker_sparql_cursor_get_string (cursor, MONTH_INDEX, NULL);
+ day = tracker_sparql_cursor_get_string (cursor, DAY_INDEX, NULL);
+ hours = tracker_sparql_cursor_get_string (cursor, HOURS_INDEX, NULL);
+ minutes = tracker_sparql_cursor_get_string (cursor, MINUTES_INDEX, NULL);
+ seconds = tracker_sparql_cursor_get_string (cursor, SECONDS_INDEX, NULL);
+ equipment = tracker_sparql_cursor_get_string (cursor, CAMERA_MODEL_INDEX, NULL);
+ season_number = tracker_sparql_cursor_get_string (cursor, SEASON_INDEX, NULL);
+ episode_number = tracker_sparql_cursor_get_string (cursor, EPISODE_NUMBER_INDEX, NULL);
+ track_number = tracker_sparql_cursor_get_string (cursor, TRACK_NUMBER_INDEX, NULL);
+ artist_name = tracker_sparql_cursor_get_string (cursor, ARTIST_NAME_INDEX, NULL);
+ title = tracker_sparql_cursor_get_string (cursor, TITLE_INDEX, NULL);
+ album_name = tracker_sparql_cursor_get_string (cursor, ALBUM_NAME_INDEX, NULL);
+
+ /* Search for the metadata object corresponding to the file name */
+ file_name = tracker_sparql_cursor_get_string (cursor, FILE_NAME_INDEX, NULL);
+ for (l = query_data->selection_metadata; l != NULL; l = l->next)
+ {
+ file_metadata = l->data;
+
+ if (g_strcmp0 (file_name, file_metadata->file_name->str) == 0)
+ {
+ break;
+ }
+ }
+
+ /* Set metadata when available, and delete for the whole selection when not */
+ for (i = 0; i < G_N_ELEMENTS (metadata_tags_constants); i++)
+ {
+ if (query_data->has_metadata[i])
+ {
+ metadata_type = metadata_tags_constants[i].metadata_type;
+ current_metadata = NULL;
+ switch (metadata_type)
+ {
+ case ORIGINAL_FILE_NAME:
+ {
+ current_metadata = file_name;
+ }
+ break;
+
+ case CREATION_DATE:
+ {
+ current_metadata = creation_date;
+ }
+ break;
+
+ case EQUIPMENT:
+ {
+ current_metadata = equipment;
+ }
+ break;
+
+ case SEASON_NUMBER:
+ {
+ current_metadata = season_number;
+ }
+ break;
+
+ case EPISODE_NUMBER:
+ {
+ current_metadata = episode_number;
+ }
+ break;
+
+ case ARTIST_NAME:
+ {
+ current_metadata = artist_name;
+ }
+ break;
+
+ case ALBUM_NAME:
+ {
+ current_metadata = album_name;
+ }
+ break;
+
+ case TITLE:
+ {
+ current_metadata = title;
+ }
+ break;
+
+ case TRACK_NUMBER:
+ {
+ current_metadata = track_number;
+ }
+ break;
+
+ default:
+ {
+ g_warn_if_reached ();
+ }
+ break;
+ }
+
+ /* TODO: Figure out how to inform the user of why the metadata is
+ * unavailable when one or more contains the unallowed character "/"
+ */
+ if (!current_metadata || g_strrstr (current_metadata, "/"))
+ {
+ remove_metadata (query_data,
+ metadata_type);
+
+ if (metadata_type == CREATION_DATE &&
+ query_data->date_order_hash_table)
+ {
+ g_hash_table_destroy (query_data->date_order_hash_table);
+ query_data->date_order_hash_table = NULL;
+ }
+ }
+ else
+ {
+ if (metadata_type == CREATION_DATE)
+ {
+ /* Add the sort order to the order hash table */
+ g_hash_table_insert (query_data->date_order_hash_table,
+ g_strdup (tracker_sparql_cursor_get_string (cursor, 0, NULL)),
+ GINT_TO_POINTER (g_hash_table_size (query_data->date_order_hash_table)));
+
+ date_time = g_date_time_new_local (atoi (year),
+ atoi (month),
+ atoi (day),
+ atoi (hours),
+ atoi (minutes),
+ atoi (seconds));
+
+ file_metadata->metadata[metadata_type] = format_date_time (date_time);
+ }
+ else
+ {
+ file_metadata->metadata[metadata_type] = g_string_new (current_metadata);
+ }
+ }
+ }
+ }
+
+ /* Get next */
+ cursor_next (query_data, cursor);
+}
+
+static void
+batch_rename_dialog_query_callback (GObject *object,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ TrackerSparqlConnection *connection;
+ TrackerSparqlCursor *cursor;
+ QueryData *query_data;
+ g_autoptr (GError) error = NULL;
+
+ connection = TRACKER_SPARQL_CONNECTION (object);
+ query_data = user_data;
+
+ cursor = tracker_sparql_connection_query_finish (connection,
+ result,
+ &error);
+
+ if (error != NULL)
+ {
+ g_warning ("Error on batch rename query for metadata: %s", error->message);
+
+ /* The dialog is being finalized at this point */
+ if (error->code != G_IO_ERROR_CANCELLED)
+ {
+ nautilus_batch_rename_dialog_query_finished (query_data->dialog,
+ query_data->date_order_hash_table,
+ query_data->selection_metadata);
+ }
+
+ g_free (query_data);
+ }
+ else
+ {
+ cursor_next (query_data, cursor);
+ }
+}
+
+void
+check_metadata_for_selection (NautilusBatchRenameDialog *dialog,
+ GList *selection,
+ GCancellable *cancellable)
+{
+ TrackerSparqlConnection *connection;
+ GString *query;
+ GList *l;
+ NautilusFile *file;
+ GError *error;
+ QueryData *query_data;
+ gchar *file_name;
+ FileMetadata *file_metadata;
+ GList *selection_metadata;
+ guint i;
+ g_autofree gchar *parent_uri = NULL;
+ gchar *file_name_escaped;
+
+ error = NULL;
+ selection_metadata = NULL;
+
+ query = g_string_new ("SELECT "
+ "nfo:fileName(?file) "
+ "nie:contentCreated(?content) "
+ "year(nie:contentCreated(?content)) "
+ "month(nie:contentCreated(?content)) "
+ "day(nie:contentCreated(?content)) "
+ "hours(nie:contentCreated(?content)) "
+ "minutes(nie:contentCreated(?content)) "
+ "seconds(nie:contentCreated(?content)) "
+ "nfo:model(nfo:equipment(?content)) "
+ "nmm:seasonNumber(?content) "
+ "nmm:episodeNumber(?content) "
+ "nmm:trackNumber(?content) "
+ "nmm:artistName(nmm:performer(?content)) "
+ "nie:title(?content) "
+ "nie:title(nmm:musicAlbum(?content)) "
+ "WHERE { ?file a nfo:FileDataObject. ?file nie:url ?url. ?content nie:isStoredAs ?file. ");
+
+ parent_uri = nautilus_file_get_parent_uri (NAUTILUS_FILE (selection->data));
+
+ g_string_append_printf (query,
+ "FILTER(tracker:uri-is-parent(\"%s\", ?url)) ",
+ parent_uri);
+
+ for (l = selection; l != NULL; l = l->next)
+ {
+ file = NAUTILUS_FILE (l->data);
+ file_name = nautilus_file_get_name (file);
+ file_name_escaped = tracker_sparql_escape_string (file_name);
+
+ if (l == selection)
+ {
+ g_string_append_printf (query,
+ "FILTER (nfo:fileName(?file) IN (\"%s\", ",
+ file_name_escaped);
+ }
+ else if (l->next == NULL)
+ {
+ g_string_append_printf (query,
+ "\"%s\")) ",
+ file_name_escaped);
+ }
+ else
+ {
+ g_string_append_printf (query,
+ "\"%s\", ",
+ file_name_escaped);
+ }
+
+ file_metadata = g_new0 (FileMetadata, 1);
+ file_metadata->file_name = g_string_new (file_name);
+ file_metadata->metadata[ORIGINAL_FILE_NAME] = g_string_new (file_name);
+
+ selection_metadata = g_list_prepend (selection_metadata, file_metadata);
+
+ g_free (file_name);
+ g_free (file_name_escaped);
+ }
+
+ selection_metadata = g_list_reverse (selection_metadata);
+
+ g_string_append (query, "} ORDER BY ASC(nie:contentCreated(?content))");
+
+ connection = nautilus_tracker_get_miner_fs_connection (&error);
+ if (!connection)
+ {
+ if (error)
+ {
+ g_warning ("Error on batch rename tracker connection: %s", error->message);
+ g_error_free (error);
+ }
+
+ return;
+ }
+
+ query_data = g_new (QueryData, 1);
+ query_data->date_order_hash_table = g_hash_table_new_full (g_str_hash,
+ g_str_equal,
+ (GDestroyNotify) g_free,
+ NULL);
+ query_data->dialog = dialog;
+ query_data->selection_metadata = selection_metadata;
+ for (i = 0; i < G_N_ELEMENTS (metadata_tags_constants); i++)
+ {
+ query_data->has_metadata[i] = TRUE;
+ }
+ query_data->cancellable = cancellable;
+
+ /* Make an asynchronous query to the store */
+ tracker_sparql_connection_query_async (connection,
+ query->str,
+ cancellable,
+ batch_rename_dialog_query_callback,
+ query_data);
+
+ g_string_free (query, TRUE);
+}
+
+GList *
+batch_rename_files_get_distinct_parents (GList *selection)
+{
+ GList *result;
+ GList *l1;
+ NautilusFile *file;
+ NautilusDirectory *directory;
+ NautilusFile *parent;
+
+ result = NULL;
+ for (l1 = selection; l1 != NULL; l1 = l1->next)
+ {
+ file = NAUTILUS_FILE (l1->data);
+ parent = nautilus_file_get_parent (file);
+ directory = nautilus_directory_get_for_file (parent);
+ if (!g_list_find (result, directory))
+ {
+ result = g_list_prepend (result, directory);
+ }
+
+ nautilus_file_unref (parent);
+ }
+
+ return result;
+}
diff --git a/src/nautilus-batch-rename-utilities.h b/src/nautilus-batch-rename-utilities.h
new file mode 100644
index 0000000..9b163aa
--- /dev/null
+++ b/src/nautilus-batch-rename-utilities.h
@@ -0,0 +1,70 @@
+/* nautilus-batch-rename-utilities.c
+ *
+ * Copyright (C) 2016 Alexandru Pandelea <alexandru.pandelea@gmail.com>
+ *
+ * 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/>.
+ */
+
+#pragma once
+
+#include <gio/gio.h>
+#include <gtk/gtk.h>
+#include <tracker-sparql.h>
+
+GList* batch_rename_dialog_get_new_names_list (NautilusBatchRenameDialogMode mode,
+ GList *selection,
+ GList *tags_list,
+ GList *selection_metadata,
+ gchar *entry_text,
+ gchar *replace_text);
+
+GList* file_names_list_has_duplicates (NautilusBatchRenameDialog *dialog,
+ NautilusDirectory *model,
+ GList *names,
+ GList *selection,
+ GList *parents_list,
+ GCancellable *cancellable);
+
+GList* nautilus_batch_rename_dialog_sort (GList *selection,
+ SortMode mode,
+ GHashTable *creation_date_table);
+
+void check_metadata_for_selection (NautilusBatchRenameDialog *dialog,
+ GList *selection,
+ GCancellable *cancellable);
+
+gboolean selection_has_single_parent (GList *selection);
+
+void string_free (gpointer mem);
+
+void conflict_data_free (gpointer mem);
+
+GList* batch_rename_files_get_distinct_parents (GList *selection);
+
+gboolean file_name_conflicts_with_results (GList *selection,
+ GList *new_names,
+ GString *old_name,
+ gchar *parent_uri);
+
+GString* batch_rename_replace_label_text (gchar *label,
+ const gchar *substr);
+
+gchar* batch_rename_get_tag_text_representation (TagConstants tag_constants);
+
+void batch_rename_sort_lists_for_rename (GList **selection,
+ GList **new_names,
+ GList **old_names,
+ GList **new_files,
+ GList **old_files,
+ gboolean is_undo_redo); \ No newline at end of file
diff --git a/src/nautilus-bookmark-list.c b/src/nautilus-bookmark-list.c
new file mode 100644
index 0000000..b2d7e66
--- /dev/null
+++ b/src/nautilus-bookmark-list.c
@@ -0,0 +1,670 @@
+/*
+ * Nautilus
+ *
+ * Copyright (C) 1999, 2000 Eazel, Inc.
+ *
+ * Nautilus is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * Nautilus is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: John Sullivan <sullivan@eazel.com>
+ */
+
+/* nautilus-bookmark-list.c - implementation of centralized list of bookmarks.
+ */
+
+#include <config.h>
+#include "nautilus-bookmark-list.h"
+
+#include "nautilus-file-utilities.h"
+#include "nautilus-file.h"
+#include "nautilus-icon-names.h"
+
+#include <gio/gio.h>
+#include <string.h>
+#include <errno.h>
+
+#define MAX_BOOKMARK_LENGTH 80
+#define LOAD_JOB 1
+#define SAVE_JOB 2
+
+struct _NautilusBookmarkList
+{
+ GObject parent_instance;
+
+ GList *list;
+ GFileMonitor *monitor;
+ GQueue *pending_ops;
+};
+
+enum
+{
+ CHANGED,
+ LAST_SIGNAL
+};
+
+static guint signals[LAST_SIGNAL];
+
+/* forward declarations */
+#define NAUTILUS_BOOKMARK_LIST_ERROR (nautilus_bookmark_list_error_quark ())
+static GQuark nautilus_bookmark_list_error_quark (void);
+
+static void nautilus_bookmark_list_load_file (NautilusBookmarkList *bookmarks);
+static void nautilus_bookmark_list_save_file (NautilusBookmarkList *bookmarks);
+
+G_DEFINE_TYPE (NautilusBookmarkList, nautilus_bookmark_list, G_TYPE_OBJECT)
+
+static GQuark
+nautilus_bookmark_list_error_quark (void)
+{
+ return g_quark_from_static_string ("nautilus-bookmark-list-error-quark");
+}
+
+static NautilusBookmark *
+new_bookmark_from_uri (const char *uri,
+ const char *label)
+{
+ NautilusBookmark *new_bookmark = NULL;
+ g_autoptr (GFile) location = NULL;
+
+ if (uri)
+ {
+ location = g_file_new_for_uri (uri);
+ new_bookmark = nautilus_bookmark_new (location, label);
+ }
+
+ return new_bookmark;
+}
+
+static GFile *
+nautilus_bookmark_list_get_legacy_file (void)
+{
+ g_autofree char *filename = NULL;
+
+ filename = g_build_filename (g_get_home_dir (),
+ ".gtk-bookmarks",
+ NULL);
+
+ return g_file_new_for_path (filename);
+}
+
+static GFile *
+nautilus_bookmark_list_get_file (void)
+{
+ g_autofree char *filename = NULL;
+
+ filename = g_build_filename (g_get_user_config_dir (),
+ "gtk-3.0",
+ "bookmarks",
+ NULL);
+
+ return g_file_new_for_path (filename);
+}
+
+/* Initialization. */
+
+static void
+bookmark_in_list_changed_callback (NautilusBookmark *bookmark,
+ NautilusBookmarkList *bookmarks)
+{
+ g_assert (NAUTILUS_IS_BOOKMARK (bookmark));
+ g_assert (NAUTILUS_IS_BOOKMARK_LIST (bookmarks));
+
+ /* save changes to the list */
+ nautilus_bookmark_list_save_file (bookmarks);
+}
+
+static void
+bookmark_in_list_notify (GObject *object,
+ GParamSpec *pspec,
+ NautilusBookmarkList *bookmarks)
+{
+ /* emit the changed signal without saving, as only appearance properties changed */
+ g_signal_emit (bookmarks, signals[CHANGED], 0);
+}
+
+static void
+stop_monitoring_bookmark (NautilusBookmarkList *bookmarks,
+ NautilusBookmark *bookmark)
+{
+ g_signal_handlers_disconnect_by_func (bookmark,
+ bookmark_in_list_changed_callback,
+ bookmarks);
+}
+
+static void
+stop_monitoring_one (gpointer data,
+ gpointer user_data)
+{
+ g_assert (NAUTILUS_IS_BOOKMARK (data));
+ g_assert (NAUTILUS_IS_BOOKMARK_LIST (user_data));
+
+ stop_monitoring_bookmark (NAUTILUS_BOOKMARK_LIST (user_data),
+ NAUTILUS_BOOKMARK (data));
+}
+
+static void
+clear (NautilusBookmarkList *bookmarks)
+{
+ g_list_foreach (bookmarks->list, stop_monitoring_one, bookmarks);
+ g_list_free_full (bookmarks->list, g_object_unref);
+ bookmarks->list = NULL;
+}
+
+static void
+do_finalize (GObject *object)
+{
+ NautilusBookmarkList *self = NAUTILUS_BOOKMARK_LIST (object);
+
+ if (self->monitor != NULL)
+ {
+ g_file_monitor_cancel (self->monitor);
+ g_clear_object (&self->monitor);
+ }
+
+ g_queue_free (self->pending_ops);
+
+ clear (self);
+
+ G_OBJECT_CLASS (nautilus_bookmark_list_parent_class)->finalize (object);
+}
+
+static void
+nautilus_bookmark_list_class_init (NautilusBookmarkListClass *class)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (class);
+
+ object_class->finalize = do_finalize;
+
+ signals[CHANGED] =
+ g_signal_new ("changed",
+ G_TYPE_FROM_CLASS (object_class),
+ G_SIGNAL_RUN_LAST,
+ 0,
+ NULL, NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+}
+
+static void
+bookmark_monitor_changed_cb (GFileMonitor *monitor,
+ GFile *child,
+ GFile *other_file,
+ GFileMonitorEvent eflags,
+ gpointer user_data)
+{
+ if (eflags == G_FILE_MONITOR_EVENT_CHANGED ||
+ eflags == G_FILE_MONITOR_EVENT_CREATED)
+ {
+ g_return_if_fail (NAUTILUS_IS_BOOKMARK_LIST (NAUTILUS_BOOKMARK_LIST (user_data)));
+ nautilus_bookmark_list_load_file (NAUTILUS_BOOKMARK_LIST (user_data));
+ }
+}
+
+static void
+nautilus_bookmark_list_init (NautilusBookmarkList *bookmarks)
+{
+ g_autoptr (GFile) file = NULL;
+
+ bookmarks->pending_ops = g_queue_new ();
+
+ nautilus_bookmark_list_load_file (bookmarks);
+
+ file = nautilus_bookmark_list_get_file ();
+ bookmarks->monitor = g_file_monitor_file (file, G_FILE_MONITOR_NONE, NULL, NULL);
+ g_file_monitor_set_rate_limit (bookmarks->monitor, 1000);
+
+ g_signal_connect (bookmarks->monitor, "changed",
+ G_CALLBACK (bookmark_monitor_changed_cb), bookmarks);
+}
+
+static void
+insert_bookmark_internal (NautilusBookmarkList *bookmarks,
+ NautilusBookmark *bookmark,
+ int index)
+{
+ bookmarks->list = g_list_insert (bookmarks->list, bookmark, index);
+
+ g_signal_connect_object (bookmark, "contents-changed",
+ G_CALLBACK (bookmark_in_list_changed_callback), bookmarks, 0);
+ g_signal_connect_object (bookmark, "notify::icon",
+ G_CALLBACK (bookmark_in_list_notify), bookmarks, 0);
+ g_signal_connect_object (bookmark, "notify::name",
+ G_CALLBACK (bookmark_in_list_notify), bookmarks, 0);
+}
+
+/**
+ * nautilus_bookmark_list_item_with_location:
+ *
+ * Get the bookmark with the specified location, if any
+ * @bookmarks: the list of bookmarks.
+ * @location: a #GFile
+ * @index: location where to store bookmark index, or %NULL
+ *
+ * Return value: the bookmark with location @location, or %NULL.
+ **/
+NautilusBookmark *
+nautilus_bookmark_list_item_with_location (NautilusBookmarkList *bookmarks,
+ GFile *location,
+ guint *index)
+{
+ GList *node;
+ NautilusBookmark *bookmark;
+ guint idx;
+
+ g_return_val_if_fail (NAUTILUS_IS_BOOKMARK_LIST (bookmarks), NULL);
+ g_return_val_if_fail (G_IS_FILE (location), NULL);
+
+ idx = 0;
+
+ for (node = bookmarks->list; node != NULL; node = node->next)
+ {
+ g_autoptr (GFile) bookmark_location = NULL;
+
+ bookmark = node->data;
+ bookmark_location = nautilus_bookmark_get_location (bookmark);
+
+ if (g_file_equal (location, bookmark_location))
+ {
+ if (index)
+ {
+ *index = idx;
+ }
+
+ return bookmark;
+ }
+
+ idx++;
+ }
+
+ return NULL;
+}
+
+/**
+ * nautilus_bookmark_list_append:
+ *
+ * Append a bookmark to a bookmark list.
+ * @bookmarks: NautilusBookmarkList to append to.
+ * @bookmark: Bookmark to append a copy of.
+ **/
+void
+nautilus_bookmark_list_append (NautilusBookmarkList *bookmarks,
+ NautilusBookmark *bookmark)
+{
+ g_return_if_fail (NAUTILUS_IS_BOOKMARK_LIST (bookmarks));
+ g_return_if_fail (NAUTILUS_IS_BOOKMARK (bookmark));
+
+ if (g_list_find_custom (bookmarks->list, bookmark,
+ nautilus_bookmark_compare_with) != NULL)
+ {
+ return;
+ }
+
+ insert_bookmark_internal (bookmarks, g_object_ref (bookmark), -1);
+ nautilus_bookmark_list_save_file (bookmarks);
+}
+
+static void
+process_next_op (NautilusBookmarkList *bookmarks);
+
+static void
+op_processed_cb (NautilusBookmarkList *self)
+{
+ g_queue_pop_tail (self->pending_ops);
+
+ if (!g_queue_is_empty (self->pending_ops))
+ {
+ process_next_op (self);
+ }
+}
+
+static void
+load_callback (GObject *source_object,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ NautilusBookmarkList *self = NAUTILUS_BOOKMARK_LIST (source_object);
+ g_autoptr (GError) error = NULL;
+ g_autofree gchar *contents = NULL;
+ char **lines;
+ int i;
+
+ contents = g_task_propagate_pointer (G_TASK (res), &error);
+ if (error != NULL)
+ {
+ g_warning ("Unable to get contents of the bookmarks file: %s",
+ error->message);
+ op_processed_cb (self);
+ return;
+ }
+
+ lines = g_strsplit (contents, "\n", -1);
+ for (i = 0; lines[i]; i++)
+ {
+ /* Ignore empty or invalid lines that cannot be parsed properly */
+ if (lines[i][0] != '\0' && lines[i][0] != ' ')
+ {
+ /* gtk 2.7/2.8 might have labels appended to bookmarks which are separated by a space
+ * we must seperate the bookmark uri and the potential label
+ */
+ char *space;
+ g_autofree char *label = NULL;
+
+ space = strchr (lines[i], ' ');
+ if (space)
+ {
+ *space = '\0';
+ label = g_strdup (space + 1);
+ }
+
+ insert_bookmark_internal (self, new_bookmark_from_uri (lines[i], label), -1);
+ }
+ }
+
+ g_signal_emit (self, signals[CHANGED], 0);
+ op_processed_cb (self);
+
+ g_strfreev (lines);
+}
+
+static void
+load_io_thread (GTask *task,
+ gpointer source_object,
+ gpointer task_data,
+ GCancellable *cancellable)
+{
+ GFile *file;
+ gchar *contents;
+ GError *error = NULL;
+
+ file = nautilus_bookmark_list_get_file ();
+ if (!g_file_query_exists (file, NULL))
+ {
+ g_object_unref (file);
+ file = nautilus_bookmark_list_get_legacy_file ();
+ }
+
+ g_file_load_contents (file, NULL, &contents, NULL, NULL, &error);
+ g_object_unref (file);
+
+ if (error != NULL)
+ {
+ g_task_return_error (task, error);
+ }
+ else
+ {
+ g_task_return_pointer (task, contents, g_free);
+ }
+}
+
+static void
+load_file_async (NautilusBookmarkList *self)
+{
+ g_autoptr (GTask) task = NULL;
+
+ /* Wipe out old list. */
+ clear (self);
+
+ task = g_task_new (G_OBJECT (self),
+ NULL,
+ load_callback, NULL);
+ g_task_run_in_thread (task, load_io_thread);
+}
+
+static void
+save_callback (GObject *source_object,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ NautilusBookmarkList *self = NAUTILUS_BOOKMARK_LIST (source_object);
+ g_autoptr (GError) error = NULL;
+ gboolean success;
+ g_autoptr (GFile) file = NULL;
+
+ success = g_task_propagate_boolean (G_TASK (res), &error);
+
+ if (error != NULL)
+ {
+ g_warning ("Unable to replace contents of the bookmarks file: %s",
+ error->message);
+ }
+
+ /* g_file_replace_contents() returned FALSE, but did not set an error. */
+ if (!success)
+ {
+ g_warning ("Unable to replace contents of the bookmarks file.");
+ }
+
+ /* re-enable bookmark file monitoring */
+ file = nautilus_bookmark_list_get_file ();
+ self->monitor = g_file_monitor_file (file, 0, NULL, NULL);
+
+ g_file_monitor_set_rate_limit (self->monitor, 1000);
+ g_signal_connect (self->monitor, "changed",
+ G_CALLBACK (bookmark_monitor_changed_cb), self);
+
+ op_processed_cb (self);
+}
+
+static void
+save_io_thread (GTask *task,
+ gpointer source_object,
+ gpointer task_data,
+ GCancellable *cancellable)
+{
+ gchar *contents;
+ g_autofree gchar *path = NULL;
+ g_autoptr (GFile) parent = NULL;
+ g_autoptr (GFile) file = NULL;
+ gboolean success;
+ GError *error = NULL;
+
+ file = nautilus_bookmark_list_get_file ();
+ parent = g_file_get_parent (file);
+ path = g_file_get_path (parent);
+
+ if (g_mkdir_with_parents (path, 0700) == -1)
+ {
+ int saved_errno = errno;
+
+ g_set_error (&error, NAUTILUS_BOOKMARK_LIST_ERROR, 0,
+ "Failed to create bookmarks folder %s: %s",
+ path, g_strerror (saved_errno));
+ g_task_return_error (task, error);
+ return;
+ }
+
+ contents = (gchar *) g_task_get_task_data (task);
+
+ success = g_file_replace_contents (file,
+ contents, strlen (contents),
+ NULL, FALSE, 0, NULL,
+ NULL, &error);
+
+ if (error != NULL)
+ {
+ g_task_return_error (task, error);
+ }
+ else
+ {
+ g_task_return_boolean (task, success);
+ }
+}
+
+static void
+save_file_async (NautilusBookmarkList *self)
+{
+ g_autoptr (GTask) task = NULL;
+ GString *bookmark_string;
+ gchar *contents;
+ GList *l;
+
+ bookmark_string = g_string_new (NULL);
+
+ /* temporarily disable bookmark file monitoring when writing file */
+ if (self->monitor != NULL)
+ {
+ g_file_monitor_cancel (self->monitor);
+ g_clear_object (&self->monitor);
+ }
+
+ for (l = self->list; l; l = l->next)
+ {
+ NautilusBookmark *bookmark;
+
+ bookmark = NAUTILUS_BOOKMARK (l->data);
+
+ /* make sure we save label if it has one for compatibility with GTK 2.7 and 2.8 */
+ if (nautilus_bookmark_get_has_custom_name (bookmark))
+ {
+ const char *label;
+ g_autofree char *uri = NULL;
+
+ label = nautilus_bookmark_get_name (bookmark);
+ uri = nautilus_bookmark_get_uri (bookmark);
+
+ g_string_append_printf (bookmark_string,
+ "%s %s\n", uri, label);
+ }
+ else
+ {
+ g_autofree char *uri = NULL;
+
+ uri = nautilus_bookmark_get_uri (bookmark);
+
+ g_string_append_printf (bookmark_string, "%s\n", uri);
+ }
+ }
+
+ task = g_task_new (G_OBJECT (self),
+ NULL,
+ save_callback, NULL);
+ contents = g_string_free (bookmark_string, FALSE);
+ g_task_set_task_data (task, contents, g_free);
+
+ g_task_run_in_thread (task, save_io_thread);
+}
+
+static void
+process_next_op (NautilusBookmarkList *bookmarks)
+{
+ gint op;
+
+ op = GPOINTER_TO_INT (g_queue_peek_tail (bookmarks->pending_ops));
+
+ if (op == LOAD_JOB)
+ {
+ load_file_async (bookmarks);
+ }
+ else
+ {
+ save_file_async (bookmarks);
+ }
+}
+
+/**
+ * nautilus_bookmark_list_load_file:
+ *
+ * Reads bookmarks from file, clobbering contents in memory.
+ * @bookmarks: the list of bookmarks to fill with file contents.
+ **/
+static void
+nautilus_bookmark_list_load_file (NautilusBookmarkList *bookmarks)
+{
+ g_queue_push_head (bookmarks->pending_ops, GINT_TO_POINTER (LOAD_JOB));
+
+ if (g_queue_get_length (bookmarks->pending_ops) == 1)
+ {
+ process_next_op (bookmarks);
+ }
+}
+
+/**
+ * nautilus_bookmark_list_save_file:
+ *
+ * Save bookmarks to disk.
+ * @bookmarks: the list of bookmarks to save.
+ **/
+static void
+nautilus_bookmark_list_save_file (NautilusBookmarkList *bookmarks)
+{
+ g_signal_emit (bookmarks, signals[CHANGED], 0);
+
+ g_queue_push_head (bookmarks->pending_ops, GINT_TO_POINTER (SAVE_JOB));
+
+ if (g_queue_get_length (bookmarks->pending_ops) == 1)
+ {
+ process_next_op (bookmarks);
+ }
+}
+
+gboolean
+nautilus_bookmark_list_can_bookmark_location (NautilusBookmarkList *list,
+ GFile *location)
+{
+ g_autoptr (NautilusBookmark) bookmark = NULL;
+
+ if (nautilus_bookmark_list_item_with_location (list, location, NULL))
+ {
+ /* Already bookmarked */
+ return FALSE;
+ }
+
+ if (nautilus_is_search_directory (location))
+ {
+ return FALSE;
+ }
+
+ if (nautilus_is_recent_directory (location) ||
+ nautilus_is_starred_directory (location) ||
+ nautilus_is_home_directory (location) ||
+ nautilus_is_trash_directory (location) ||
+ nautilus_is_other_locations_directory (location))
+ {
+ /* Already in the sidebar */
+ return FALSE;
+ }
+
+ bookmark = nautilus_bookmark_new (location, NULL);
+ return !nautilus_bookmark_get_is_builtin (bookmark);
+}
+
+/**
+ * nautilus_bookmark_list_new:
+ *
+ * Create a new bookmark_list, with contents read from disk.
+ *
+ * Return value: A pointer to the new widget.
+ **/
+NautilusBookmarkList *
+nautilus_bookmark_list_new (void)
+{
+ NautilusBookmarkList *list;
+
+ list = NAUTILUS_BOOKMARK_LIST (g_object_new (NAUTILUS_TYPE_BOOKMARK_LIST, NULL));
+
+ return list;
+}
+
+/**
+ * nautilus_bookmark_list_get_all:
+ *
+ * Get a GList of all NautilusBookmark.
+ * @bookmarks: NautilusBookmarkList from where to get the bookmarks.
+ **/
+GList *
+nautilus_bookmark_list_get_all (NautilusBookmarkList *bookmarks)
+{
+ g_return_val_if_fail (NAUTILUS_IS_BOOKMARK_LIST (bookmarks), NULL);
+
+ return bookmarks->list;
+}
diff --git a/src/nautilus-bookmark-list.h b/src/nautilus-bookmark-list.h
new file mode 100644
index 0000000..4849ea8
--- /dev/null
+++ b/src/nautilus-bookmark-list.h
@@ -0,0 +1,47 @@
+
+/*
+ * Nautilus
+ *
+ * Copyright (C) 1999, 2000 Eazel, Inc.
+ *
+ * Nautilus is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * Nautilus is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: John Sullivan <sullivan@eazel.com>
+ */
+
+/* nautilus-bookmark-list.h - interface for centralized list of bookmarks.
+ */
+
+#pragma once
+
+#include "nautilus-bookmark.h"
+#include <gio/gio.h>
+
+G_BEGIN_DECLS
+
+#define NAUTILUS_TYPE_BOOKMARK_LIST (nautilus_bookmark_list_get_type())
+
+G_DECLARE_FINAL_TYPE (NautilusBookmarkList, nautilus_bookmark_list, NAUTILUS, BOOKMARK_LIST, GObject)
+
+NautilusBookmarkList * nautilus_bookmark_list_new (void);
+void nautilus_bookmark_list_append (NautilusBookmarkList *bookmarks,
+ NautilusBookmark *bookmark);
+NautilusBookmark * nautilus_bookmark_list_item_with_location (NautilusBookmarkList *bookmarks,
+ GFile *location,
+ guint *index);
+gboolean nautilus_bookmark_list_can_bookmark_location (NautilusBookmarkList *list,
+ GFile *location);
+GList * nautilus_bookmark_list_get_all (NautilusBookmarkList *bookmarks);
+
+G_END_DECLS \ No newline at end of file
diff --git a/src/nautilus-bookmark.c b/src/nautilus-bookmark.c
new file mode 100644
index 0000000..f016eee
--- /dev/null
+++ b/src/nautilus-bookmark.c
@@ -0,0 +1,788 @@
+/* nautilus-bookmark.c - implementation of individual bookmarks.
+ *
+ * Copyright (C) 1999, 2000 Eazel, Inc.
+ * Copyright (C) 2011, Red Hat, Inc.
+ *
+ * The Gnome Library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * The Gnome Library 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
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with the Gnome Library; see the file COPYING.LIB. If not,
+ * see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: John Sullivan <sullivan@eazel.com>
+ * Cosimo Cecchi <cosimoc@redhat.com>
+ */
+
+#include <config.h>
+
+#include "nautilus-bookmark.h"
+
+#include <eel/eel-vfs-extensions.h>
+#include <gio/gio.h>
+#include <glib/gi18n.h>
+#include <gtk/gtk.h>
+
+#include "nautilus-file.h"
+#include "nautilus-file-utilities.h"
+#include "nautilus-icon-names.h"
+
+#define DEBUG_FLAG NAUTILUS_DEBUG_BOOKMARKS
+#include "nautilus-debug.h"
+
+enum
+{
+ CONTENTS_CHANGED,
+ LAST_SIGNAL
+};
+
+enum
+{
+ PROP_NAME = 1,
+ PROP_CUSTOM_NAME,
+ PROP_LOCATION,
+ PROP_ICON,
+ PROP_SYMBOLIC_ICON,
+ NUM_PROPERTIES
+};
+
+static GParamSpec *properties[NUM_PROPERTIES] = { NULL };
+static guint signals[LAST_SIGNAL];
+
+struct _NautilusBookmark
+{
+ GObject parent_instance;
+
+ char *name;
+ gboolean has_custom_name;
+ GFile *location;
+ GIcon *icon;
+ GIcon *symbolic_icon;
+ NautilusFile *file;
+
+ char *scroll_file;
+
+ gboolean exists;
+ guint exists_id;
+ GCancellable *cancellable;
+};
+
+static void nautilus_bookmark_disconnect_file (NautilusBookmark *file);
+
+G_DEFINE_TYPE (NautilusBookmark, nautilus_bookmark, G_TYPE_OBJECT);
+
+static void
+nautilus_bookmark_set_name_internal (NautilusBookmark *bookmark,
+ const char *new_name)
+{
+ if (g_strcmp0 (bookmark->name, new_name) != 0)
+ {
+ g_free (bookmark->name);
+ bookmark->name = g_strdup (new_name);
+
+ g_object_notify_by_pspec (G_OBJECT (bookmark), properties[PROP_NAME]);
+ }
+}
+
+static void
+bookmark_set_name_from_ready_file (NautilusBookmark *self,
+ NautilusFile *file)
+{
+ g_autofree gchar *display_name = NULL;
+
+ if (self->has_custom_name)
+ {
+ return;
+ }
+
+ display_name = nautilus_file_get_display_name (self->file);
+
+ if (nautilus_file_is_other_locations (self->file))
+ {
+ nautilus_bookmark_set_name_internal (self, _("Other Locations"));
+ }
+ else if (nautilus_file_is_home (self->file))
+ {
+ nautilus_bookmark_set_name_internal (self, _("Home"));
+ }
+ else if (g_strcmp0 (self->name, display_name) != 0)
+ {
+ nautilus_bookmark_set_name_internal (self, display_name);
+ DEBUG ("%s: name changed to %s", nautilus_bookmark_get_name (self), display_name);
+ }
+}
+
+static void
+bookmark_file_changed_callback (NautilusFile *file,
+ NautilusBookmark *bookmark)
+{
+ g_autoptr (GFile) location = NULL;
+
+ g_assert (file == bookmark->file);
+
+ DEBUG ("%s: file changed", nautilus_bookmark_get_name (bookmark));
+
+ location = nautilus_file_get_location (file);
+
+ if (!g_file_equal (bookmark->location, location) &&
+ !nautilus_file_is_in_trash (file))
+ {
+ DEBUG ("%s: file got moved", nautilus_bookmark_get_name (bookmark));
+
+ g_object_unref (bookmark->location);
+ bookmark->location = g_object_ref (location);
+
+ g_object_notify_by_pspec (G_OBJECT (bookmark), properties[PROP_LOCATION]);
+ g_signal_emit (bookmark, signals[CONTENTS_CHANGED], 0);
+ }
+
+ if (nautilus_file_is_gone (file) ||
+ nautilus_file_is_in_trash (file))
+ {
+ /* The file we were monitoring has been trashed, deleted,
+ * or moved in a way that we didn't notice. We should make
+ * a spanking new NautilusFile object for this
+ * location so if a new file appears in this place
+ * we will notice. However, we can't immediately do so
+ * because creating a new NautilusFile directly as a result
+ * of noticing a file goes away may trigger i/o on that file
+ * again, noticeing it is gone, leading to a loop.
+ * So, the new NautilusFile is created when the bookmark
+ * is used again. However, this is not really a problem, as
+ * we don't want to change the icon or anything about the
+ * bookmark just because its not there anymore.
+ */
+ DEBUG ("%s: trashed", nautilus_bookmark_get_name (bookmark));
+ nautilus_bookmark_disconnect_file (bookmark);
+ }
+ else
+ {
+ bookmark_set_name_from_ready_file (bookmark, file);
+ }
+}
+
+gboolean
+nautilus_bookmark_get_is_builtin (NautilusBookmark *bookmark)
+{
+ GUserDirectory xdg_type;
+
+ /* if this is not an XDG dir, it's never builtin */
+ if (!nautilus_bookmark_get_xdg_type (bookmark, &xdg_type))
+ {
+ return FALSE;
+ }
+
+ /* exclude XDG locations which are not in our builtin list */
+ return (xdg_type != G_USER_DIRECTORY_DESKTOP) &&
+ (xdg_type != G_USER_DIRECTORY_TEMPLATES) &&
+ (xdg_type != G_USER_DIRECTORY_PUBLIC_SHARE);
+}
+
+gboolean
+nautilus_bookmark_get_xdg_type (NautilusBookmark *bookmark,
+ GUserDirectory *directory)
+{
+ gboolean match;
+ GFile *location;
+ const gchar *path;
+ GUserDirectory dir;
+
+ match = FALSE;
+
+ for (dir = 0; dir < G_USER_N_DIRECTORIES; dir++)
+ {
+ path = g_get_user_special_dir (dir);
+ if (!path)
+ {
+ continue;
+ }
+
+ location = g_file_new_for_path (path);
+ match = g_file_equal (location, bookmark->location);
+ g_object_unref (location);
+
+ if (match)
+ {
+ break;
+ }
+ }
+
+ if (match && directory != NULL)
+ {
+ *directory = dir;
+ }
+
+ return match;
+}
+
+static GIcon *
+get_native_icon (NautilusBookmark *bookmark,
+ gboolean symbolic)
+{
+ GUserDirectory xdg_type;
+ GIcon *icon = NULL;
+
+ if (bookmark->file == NULL)
+ {
+ goto out;
+ }
+
+ if (!nautilus_bookmark_get_xdg_type (bookmark, &xdg_type))
+ {
+ goto out;
+ }
+
+ if (xdg_type < G_USER_N_DIRECTORIES)
+ {
+ if (symbolic)
+ {
+ icon = nautilus_special_directory_get_symbolic_icon (xdg_type);
+ }
+ else
+ {
+ icon = nautilus_special_directory_get_icon (xdg_type);
+ }
+ }
+
+out:
+ if (icon == NULL)
+ {
+ if (symbolic)
+ {
+ icon = g_themed_icon_new (NAUTILUS_ICON_FOLDER);
+ }
+ else
+ {
+ icon = g_themed_icon_new (NAUTILUS_ICON_FULLCOLOR_FOLDER);
+ }
+ }
+
+ return icon;
+}
+
+static void
+nautilus_bookmark_set_icon_to_default (NautilusBookmark *bookmark)
+{
+ g_autoptr (GIcon) icon = NULL;
+ g_autoptr (GIcon) symbolic_icon = NULL;
+
+ if (!bookmark->exists)
+ {
+ DEBUG ("%s: file does not exist, set warning icon", nautilus_bookmark_get_name (bookmark));
+ symbolic_icon = g_themed_icon_new ("dialog-warning-symbolic");
+ icon = g_themed_icon_new ("dialog-warning");
+ }
+ else if (g_file_is_native (bookmark->location))
+ {
+ symbolic_icon = get_native_icon (bookmark, TRUE);
+ icon = get_native_icon (bookmark, FALSE);
+ }
+ else
+ {
+ symbolic_icon = g_themed_icon_new (NAUTILUS_ICON_FOLDER_REMOTE);
+ icon = g_themed_icon_new (NAUTILUS_ICON_FULLCOLOR_FOLDER_REMOTE);
+ }
+
+ DEBUG ("%s: setting icon to default", nautilus_bookmark_get_name (bookmark));
+
+ g_object_set (bookmark,
+ "icon", icon,
+ "symbolic-icon", symbolic_icon,
+ NULL);
+}
+
+static void
+nautilus_bookmark_disconnect_file (NautilusBookmark *bookmark)
+{
+ if (bookmark->file != NULL)
+ {
+ DEBUG ("%s: disconnecting file",
+ nautilus_bookmark_get_name (bookmark));
+
+ g_signal_handlers_disconnect_by_func (bookmark->file,
+ G_CALLBACK (bookmark_file_changed_callback),
+ bookmark);
+ g_clear_object (&bookmark->file);
+ }
+
+ if (bookmark->cancellable != NULL)
+ {
+ g_cancellable_cancel (bookmark->cancellable);
+ g_clear_object (&bookmark->cancellable);
+ }
+
+ if (bookmark->exists_id != 0)
+ {
+ g_source_remove (bookmark->exists_id);
+ bookmark->exists_id = 0;
+ }
+}
+
+static void
+nautilus_bookmark_connect_file (NautilusBookmark *bookmark)
+{
+ if (bookmark->file != NULL)
+ {
+ DEBUG ("%s: file already connected, returning",
+ nautilus_bookmark_get_name (bookmark));
+ return;
+ }
+
+ if (bookmark->exists)
+ {
+ DEBUG ("%s: creating file", nautilus_bookmark_get_name (bookmark));
+
+ bookmark->file = nautilus_file_get (bookmark->location);
+ g_assert (!nautilus_file_is_gone (bookmark->file));
+
+ g_signal_connect_object (bookmark->file, "changed",
+ G_CALLBACK (bookmark_file_changed_callback), bookmark, 0);
+ }
+
+ if (bookmark->icon == NULL ||
+ bookmark->symbolic_icon == NULL)
+ {
+ nautilus_bookmark_set_icon_to_default (bookmark);
+ }
+
+ if (bookmark->file != NULL &&
+ nautilus_file_check_if_ready (bookmark->file, NAUTILUS_FILE_ATTRIBUTE_INFO))
+ {
+ bookmark_set_name_from_ready_file (bookmark, bookmark->file);
+ }
+
+ if (bookmark->name == NULL)
+ {
+ bookmark->name = nautilus_compute_title_for_location (bookmark->location);
+ }
+}
+
+static void
+nautilus_bookmark_set_exists (NautilusBookmark *bookmark,
+ gboolean exists)
+{
+ if (bookmark->exists == exists)
+ {
+ return;
+ }
+
+ bookmark->exists = exists;
+ DEBUG ("%s: setting bookmark to exist: %d\n",
+ nautilus_bookmark_get_name (bookmark), exists);
+
+ /* refresh icon */
+ nautilus_bookmark_set_icon_to_default (bookmark);
+}
+
+static gboolean
+exists_non_native_idle_cb (gpointer user_data)
+{
+ NautilusBookmark *bookmark = user_data;
+ bookmark->exists_id = 0;
+ nautilus_bookmark_set_exists (bookmark, FALSE);
+
+ return FALSE;
+}
+
+static void
+exists_query_info_ready_cb (GObject *source,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ g_autoptr (GFileInfo) info = NULL;
+ NautilusBookmark *bookmark;
+ g_autoptr (GError) error = NULL;
+ gboolean exists = FALSE;
+
+ info = g_file_query_info_finish (G_FILE (source), res, &error);
+ if (!info && g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+ {
+ return;
+ }
+
+ bookmark = user_data;
+
+ if (info)
+ {
+ exists = TRUE;
+
+ g_clear_object (&bookmark->cancellable);
+ }
+
+ nautilus_bookmark_set_exists (bookmark, exists);
+}
+
+static void
+nautilus_bookmark_update_exists (NautilusBookmark *bookmark)
+{
+ /* Convert to a path, returning FALSE if not local. */
+ if (!g_file_is_native (bookmark->location) &&
+ bookmark->exists_id == 0)
+ {
+ bookmark->exists_id =
+ g_idle_add (exists_non_native_idle_cb, bookmark);
+ return;
+ }
+
+ if (bookmark->cancellable != NULL)
+ {
+ return;
+ }
+
+ bookmark->cancellable = g_cancellable_new ();
+ g_file_query_info_async (bookmark->location,
+ G_FILE_ATTRIBUTE_STANDARD_TYPE,
+ 0, G_PRIORITY_DEFAULT,
+ bookmark->cancellable,
+ exists_query_info_ready_cb, bookmark);
+}
+
+/* GObject methods */
+
+static void
+nautilus_bookmark_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ NautilusBookmark *self = NAUTILUS_BOOKMARK (object);
+ GIcon *new_icon;
+
+ switch (property_id)
+ {
+ case PROP_ICON:
+ {
+ new_icon = g_value_get_object (value);
+
+ if (new_icon != NULL && !g_icon_equal (self->icon, new_icon))
+ {
+ g_clear_object (&self->icon);
+ self->icon = g_object_ref (new_icon);
+ }
+ }
+ break;
+
+ case PROP_SYMBOLIC_ICON:
+ {
+ new_icon = g_value_get_object (value);
+
+ if (new_icon != NULL && !g_icon_equal (self->symbolic_icon, new_icon))
+ {
+ g_clear_object (&self->symbolic_icon);
+ self->symbolic_icon = g_object_ref (new_icon);
+ }
+ }
+ break;
+
+ case PROP_LOCATION:
+ {
+ self->location = g_value_dup_object (value);
+ }
+ break;
+
+ case PROP_CUSTOM_NAME:
+ {
+ self->has_custom_name = g_value_get_boolean (value);
+ }
+ break;
+
+ case PROP_NAME:
+ {
+ nautilus_bookmark_set_name_internal (self, g_value_get_string (value));
+ }
+ break;
+
+ default:
+ {
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ }
+ break;
+ }
+}
+
+static void
+nautilus_bookmark_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ NautilusBookmark *self = NAUTILUS_BOOKMARK (object);
+
+ switch (property_id)
+ {
+ case PROP_NAME:
+ {
+ g_value_set_string (value, self->name);
+ }
+ break;
+
+ case PROP_ICON:
+ {
+ g_value_set_object (value, self->icon);
+ }
+ break;
+
+ case PROP_SYMBOLIC_ICON:
+ {
+ g_value_set_object (value, self->symbolic_icon);
+ }
+ break;
+
+ case PROP_LOCATION:
+ {
+ g_value_set_object (value, self->location);
+ }
+ break;
+
+ case PROP_CUSTOM_NAME:
+ {
+ g_value_set_boolean (value, self->has_custom_name);
+ }
+ break;
+
+ default:
+ {
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ }
+ break;
+ }
+}
+
+static void
+nautilus_bookmark_finalize (GObject *object)
+{
+ NautilusBookmark *bookmark;
+
+ g_assert (NAUTILUS_IS_BOOKMARK (object));
+
+ bookmark = NAUTILUS_BOOKMARK (object);
+
+ nautilus_bookmark_disconnect_file (bookmark);
+
+ g_object_unref (bookmark->location);
+ g_clear_object (&bookmark->icon);
+ g_clear_object (&bookmark->symbolic_icon);
+
+ g_free (bookmark->name);
+ g_free (bookmark->scroll_file);
+
+ G_OBJECT_CLASS (nautilus_bookmark_parent_class)->finalize (object);
+}
+
+static void
+nautilus_bookmark_constructed (GObject *obj)
+{
+ NautilusBookmark *self = NAUTILUS_BOOKMARK (obj);
+
+ nautilus_bookmark_connect_file (self);
+ nautilus_bookmark_update_exists (self);
+}
+
+static void
+nautilus_bookmark_class_init (NautilusBookmarkClass *class)
+{
+ GObjectClass *oclass = G_OBJECT_CLASS (class);
+
+ oclass->finalize = nautilus_bookmark_finalize;
+ oclass->get_property = nautilus_bookmark_get_property;
+ oclass->set_property = nautilus_bookmark_set_property;
+ oclass->constructed = nautilus_bookmark_constructed;
+
+ signals[CONTENTS_CHANGED] =
+ g_signal_new ("contents-changed",
+ G_TYPE_FROM_CLASS (class),
+ G_SIGNAL_RUN_LAST,
+ 0,
+ NULL, NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+
+ properties[PROP_NAME] =
+ g_param_spec_string ("name",
+ "Bookmark's name",
+ "The name of this bookmark",
+ NULL,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_CONSTRUCT);
+
+ properties[PROP_CUSTOM_NAME] =
+ g_param_spec_boolean ("custom-name",
+ "Whether the bookmark has a custom name",
+ "Whether the bookmark has a custom name",
+ FALSE,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_CONSTRUCT);
+
+ properties[PROP_LOCATION] =
+ g_param_spec_object ("location",
+ "Bookmark's location",
+ "The location of this bookmark",
+ G_TYPE_FILE,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_CONSTRUCT_ONLY);
+
+ properties[PROP_ICON] =
+ g_param_spec_object ("icon",
+ "Bookmark's icon",
+ "The icon of this bookmark",
+ G_TYPE_ICON,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+
+ properties[PROP_SYMBOLIC_ICON] =
+ g_param_spec_object ("symbolic-icon",
+ "Bookmark's symbolic icon",
+ "The symbolic icon of this bookmark",
+ G_TYPE_ICON,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+
+ g_object_class_install_properties (oclass, NUM_PROPERTIES, properties);
+}
+
+static void
+nautilus_bookmark_init (NautilusBookmark *bookmark)
+{
+ bookmark->exists = TRUE;
+}
+
+const gchar *
+nautilus_bookmark_get_name (NautilusBookmark *bookmark)
+{
+ g_return_val_if_fail (NAUTILUS_IS_BOOKMARK (bookmark), NULL);
+
+ return bookmark->name;
+}
+
+gboolean
+nautilus_bookmark_get_has_custom_name (NautilusBookmark *bookmark)
+{
+ g_return_val_if_fail (NAUTILUS_IS_BOOKMARK (bookmark), FALSE);
+
+ return (bookmark->has_custom_name);
+}
+
+/**
+ * nautilus_bookmark_compare_with:
+ *
+ * Check whether two bookmarks are considered identical.
+ * @a: first NautilusBookmark*.
+ * @b: second NautilusBookmark*.
+ *
+ * Return value: 0 if @a and @b have same name and uri, 1 otherwise
+ * (GCompareFunc style)
+ **/
+int
+nautilus_bookmark_compare_with (gconstpointer a,
+ gconstpointer b)
+{
+ NautilusBookmark *bookmark_a;
+ NautilusBookmark *bookmark_b;
+
+ g_return_val_if_fail (NAUTILUS_IS_BOOKMARK ((gpointer) a), 1);
+ g_return_val_if_fail (NAUTILUS_IS_BOOKMARK ((gpointer) b), 1);
+
+ bookmark_a = NAUTILUS_BOOKMARK ((gpointer) a);
+ bookmark_b = NAUTILUS_BOOKMARK ((gpointer) b);
+
+ if (!g_file_equal (bookmark_a->location,
+ bookmark_b->location))
+ {
+ return 1;
+ }
+
+ if (g_strcmp0 (bookmark_a->name,
+ bookmark_b->name) != 0)
+ {
+ return 1;
+ }
+
+ return 0;
+}
+
+GIcon *
+nautilus_bookmark_get_symbolic_icon (NautilusBookmark *bookmark)
+{
+ g_return_val_if_fail (NAUTILUS_IS_BOOKMARK (bookmark), NULL);
+
+ /* Try to connect a file in case file exists now but didn't earlier. */
+ nautilus_bookmark_connect_file (bookmark);
+
+ if (bookmark->symbolic_icon)
+ {
+ return g_object_ref (bookmark->symbolic_icon);
+ }
+ return NULL;
+}
+
+GIcon *
+nautilus_bookmark_get_icon (NautilusBookmark *bookmark)
+{
+ g_return_val_if_fail (NAUTILUS_IS_BOOKMARK (bookmark), NULL);
+
+ /* Try to connect a file in case file exists now but didn't earlier. */
+ nautilus_bookmark_connect_file (bookmark);
+
+ if (bookmark->icon)
+ {
+ return g_object_ref (bookmark->icon);
+ }
+ return NULL;
+}
+
+GFile *
+nautilus_bookmark_get_location (NautilusBookmark *bookmark)
+{
+ g_return_val_if_fail (NAUTILUS_IS_BOOKMARK (bookmark), NULL);
+
+ /* Try to connect a file in case file exists now but didn't earlier.
+ * This allows a bookmark to update its image properly in the case
+ * where a new file appears with the same URI as a previously-deleted
+ * file. Calling connect_file here means that attempts to activate the
+ * bookmark will update its image if possible.
+ */
+ nautilus_bookmark_connect_file (bookmark);
+
+ return g_object_ref (bookmark->location);
+}
+
+char *
+nautilus_bookmark_get_uri (NautilusBookmark *bookmark)
+{
+ g_autoptr (GFile) file = NULL;
+
+ file = nautilus_bookmark_get_location (bookmark);
+
+ return g_file_get_uri (file);
+}
+
+NautilusBookmark *
+nautilus_bookmark_new (GFile *location,
+ const gchar *custom_name)
+{
+ NautilusBookmark *new_bookmark;
+
+ new_bookmark = NAUTILUS_BOOKMARK (g_object_new (NAUTILUS_TYPE_BOOKMARK,
+ "location", location,
+ "name", custom_name,
+ "custom-name", custom_name != NULL,
+ NULL));
+
+ return new_bookmark;
+}
+
+void
+nautilus_bookmark_set_scroll_pos (NautilusBookmark *bookmark,
+ const char *uri)
+{
+ g_free (bookmark->scroll_file);
+ bookmark->scroll_file = g_strdup (uri);
+}
+
+char *
+nautilus_bookmark_get_scroll_pos (NautilusBookmark *bookmark)
+{
+ return g_strdup (bookmark->scroll_file);
+}
diff --git a/src/nautilus-bookmark.h b/src/nautilus-bookmark.h
new file mode 100644
index 0000000..6bf3852
--- /dev/null
+++ b/src/nautilus-bookmark.h
@@ -0,0 +1,54 @@
+
+/* nautilus-bookmark.h - implementation of individual bookmarks.
+ *
+ * Copyright (C) 1999, 2000 Eazel, Inc.
+ * Copyright (C) 2011, Red Hat, Inc.
+ *
+ * The Gnome Library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * The Gnome Library 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
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with the Gnome Library; see the file COPYING.LIB. If not,
+ * see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: John Sullivan <sullivan@eazel.com>
+ * Cosimo Cecchi <cosimoc@redhat.com>
+ */
+
+#pragma once
+
+#include <gtk/gtk.h>
+#include <gio/gio.h>
+
+G_BEGIN_DECLS
+
+#define NAUTILUS_TYPE_BOOKMARK nautilus_bookmark_get_type()
+
+G_DECLARE_FINAL_TYPE (NautilusBookmark, nautilus_bookmark, NAUTILUS, BOOKMARK, GObject)
+
+NautilusBookmark * nautilus_bookmark_new (GFile *location,
+ const char *custom_name);
+const char * nautilus_bookmark_get_name (NautilusBookmark *bookmark);
+GFile * nautilus_bookmark_get_location (NautilusBookmark *bookmark);
+char * nautilus_bookmark_get_uri (NautilusBookmark *bookmark);
+GIcon * nautilus_bookmark_get_icon (NautilusBookmark *bookmark);
+GIcon * nautilus_bookmark_get_symbolic_icon (NautilusBookmark *bookmark);
+gboolean nautilus_bookmark_get_xdg_type (NautilusBookmark *bookmark,
+ GUserDirectory *directory);
+gboolean nautilus_bookmark_get_is_builtin (NautilusBookmark *bookmark);
+gboolean nautilus_bookmark_get_has_custom_name (NautilusBookmark *bookmark);
+int nautilus_bookmark_compare_with (gconstpointer a,
+ gconstpointer b);
+
+void nautilus_bookmark_set_scroll_pos (NautilusBookmark *bookmark,
+ const char *uri);
+char * nautilus_bookmark_get_scroll_pos (NautilusBookmark *bookmark);
+
+G_END_DECLS
diff --git a/src/nautilus-clipboard.c b/src/nautilus-clipboard.c
new file mode 100644
index 0000000..99dc02d
--- /dev/null
+++ b/src/nautilus-clipboard.c
@@ -0,0 +1,349 @@
+/* nautilus-clipboard.c
+ *
+ * Nautilus Clipboard support. For now, routines to support component cut
+ * and paste.
+ *
+ * Copyright (C) 1999, 2000 Free Software Foundaton
+ * Copyright (C) 2000, 2001 Eazel, Inc.
+ * Copyright (C) 2016 Carlos Soriano <csoriano@gnome.org>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library 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
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this program; if not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Rebecca Schulman <rebecka@eazel.com>,
+ * Darin Adler <darin@bentspoon.com>
+ */
+
+#include <config.h>
+#include "nautilus-clipboard.h"
+#include "nautilus-file-utilities.h"
+#include "nautilus-file.h"
+
+#include <glib/gi18n.h>
+#include <gtk/gtk.h>
+#include <string.h>
+
+/* The .files member contains elements of type NautilusFile. */
+struct _NautilusClipboard
+{
+ gboolean cut;
+ GList *files;
+};
+
+/* Boxed type used to wrap this struct in a clipboard GValue. */
+G_DEFINE_BOXED_TYPE (NautilusClipboard, nautilus_clipboard,
+ nautilus_clipboard_copy, nautilus_clipboard_free)
+
+static char *
+nautilus_clipboard_to_string (NautilusClipboard *clip)
+{
+ GString *uris;
+ char *uri;
+ guint i;
+ GList *l;
+
+ uris = g_string_new (clip->cut ? "cut" : "copy");
+
+ for (i = 0, l = clip->files; l != NULL; l = l->next, i++)
+ {
+ uri = nautilus_file_get_uri (l->data);
+
+ g_string_append_c (uris, '\n');
+ g_string_append (uris, uri);
+
+ g_free (uri);
+ }
+
+ return g_string_free (uris, FALSE);
+}
+
+static NautilusClipboard *
+nautilus_clipboard_from_string (char *string,
+ GError **error)
+{
+ NautilusClipboard *clip;
+ g_auto (GStrv) lines = NULL;
+ g_autolist (NautilusFile) files = NULL;
+
+ if (string == NULL)
+ {
+ *error = g_error_new (G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "Clipboard string cannot be NULL.");
+ return NULL;
+ }
+
+ lines = g_strsplit (string, "\n", 0);
+
+ if (g_strcmp0 (lines[0], "cut") != 0 && g_strcmp0 (lines[0], "copy") != 0)
+ {
+ /* Translators: Do not translate 'cut' and 'copy'. These are literal keywords. */
+ *error = g_error_new (G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "Nautilus Clipboard must begin with “cut” or “copy”.");
+ return NULL;
+ }
+
+ /* Line 0 is "cut" or "copy", so uris start at line 1. */
+ for (int i = 1; lines[i] != NULL; i++)
+ {
+ if (g_strcmp0 (lines[i], "") == 0)
+ {
+ *error = g_error_new (G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "Nautilus Clipboard must not have empty lines.");
+ return NULL;
+ }
+ else if (!g_uri_is_valid (lines[i], G_URI_FLAGS_NONE, error))
+ {
+ return NULL;
+ }
+ files = g_list_prepend (files, nautilus_file_get_by_uri (lines[i]));
+ }
+
+ clip = g_new0 (NautilusClipboard, 1);
+ files = g_list_reverse (files);
+ clip->files = g_steal_pointer (&files);
+ clip->cut = g_str_equal (lines[0], "cut");
+
+ return clip;
+}
+
+#if 0 && NAUTILUS_DND_NEEDS_GTK4_REIMPLEMENTATION
+void
+nautilus_clipboard_clear_if_colliding_uris (GtkWidget *widget,
+ const GList *item_uris)
+{
+ GtkSelectionData *data;
+ GList *clipboard_item_uris, *l;
+ gboolean collision;
+
+ collision = FALSE;
+ data = gtk_clipboard_wait_for_contents (gtk_widget_get_clipboard (widget),
+ copied_files_atom);
+ if (data == NULL)
+ {
+ return;
+ }
+
+ clipboard_item_uris = nautilus_clipboard_get_uri_list_from_selection_data (data);
+
+ for (l = (GList *) item_uris; l; l = l->next)
+ {
+ if (g_list_find_custom ((GList *) clipboard_item_uris, l->data,
+ (GCompareFunc) g_strcmp0))
+ {
+ collision = TRUE;
+ break;
+ }
+ }
+
+ if (collision)
+ {
+ gtk_clipboard_clear (gtk_widget_get_clipboard (widget));
+ }
+
+ if (clipboard_item_uris)
+ {
+ g_list_free_full (clipboard_item_uris, g_free);
+ }
+}
+#endif
+
+/*
+ * This asumes the implementation of GTK_TYPE_FILE_LIST is a GSList<GFile>.
+ * As of writing this, the API docs don't provide for this assumption.
+ */
+static GSList *
+convert_file_list_to_gdk_file_list (NautilusClipboard *clip)
+{
+ GSList *file_list = NULL;
+ for (GList *l = clip->files; l != NULL; l = l->next)
+ {
+ file_list = g_slist_prepend (file_list,
+ nautilus_file_get_location (l->data));
+ }
+ return g_slist_reverse (file_list);
+}
+
+static void
+nautilus_clipboard_serialize (GdkContentSerializer *serializer)
+{
+ NautilusClipboard *clip;
+ g_autofree gchar *str = NULL;
+ g_autoptr (GError) error = NULL;
+
+ clip = g_value_get_boxed (gdk_content_serializer_get_value (serializer));
+
+ str = nautilus_clipboard_to_string (clip);
+
+ if (g_output_stream_printf (gdk_content_serializer_get_output_stream (serializer),
+ NULL,
+ gdk_content_serializer_get_cancellable (serializer),
+ &error,
+ "%s", str))
+ {
+ gdk_content_serializer_return_success (serializer);
+ }
+ else
+ {
+ gdk_content_serializer_return_error (serializer, error);
+ }
+}
+
+static void
+nautilus_clipboard_deserialize_finish (GObject *source,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ GdkContentDeserializer *deserializer = user_data;
+ GOutputStream *output = G_OUTPUT_STREAM (source);
+ GError *error = NULL;
+ g_autofree gchar *string = NULL;
+ g_autoptr (NautilusClipboard) clip = NULL;
+
+ if (g_output_stream_splice_finish (output, result, &error) < 0)
+ {
+ gdk_content_deserializer_return_error (deserializer, error);
+ return;
+ }
+
+ /* write terminating NULL */
+ if (g_output_stream_write (output, "", 1, NULL, &error) < 0 ||
+ !g_output_stream_close (output, NULL, &error))
+ {
+ gdk_content_deserializer_return_error (deserializer, error);
+ return;
+ }
+
+ string = g_memory_output_stream_steal_data (G_MEMORY_OUTPUT_STREAM (output));
+
+ clip = nautilus_clipboard_from_string (string, &error);
+
+ if (clip == NULL)
+ {
+ gdk_content_deserializer_return_error (deserializer, error);
+ return;
+ }
+
+ g_value_set_boxed (gdk_content_deserializer_get_value (deserializer), clip);
+ gdk_content_deserializer_return_success (deserializer);
+}
+
+static void
+nautilus_clipboard_deserialize (GdkContentDeserializer *deserializer)
+{
+ g_autoptr (GOutputStream) output = NULL;
+
+ output = g_memory_output_stream_new_resizable ();
+ g_output_stream_splice_async (output,
+ gdk_content_deserializer_get_input_stream (deserializer),
+ G_OUTPUT_STREAM_SPLICE_CLOSE_SOURCE,
+ gdk_content_deserializer_get_priority (deserializer),
+ gdk_content_deserializer_get_cancellable (deserializer),
+ nautilus_clipboard_deserialize_finish,
+ deserializer);
+}
+
+/**
+ * nautilus_clipboard_peek_files:
+ * @clip: The current local clipboard value.
+ *
+ * Returns: (transfer none): The internal GList of GFile objects.
+ */
+GList *
+nautilus_clipboard_peek_files (NautilusClipboard *clip)
+{
+ return clip->files;
+}
+
+/**
+ * nautilus_clipboard_get_uri_list:
+ * @clip: The current local clipboard value.
+ *
+ * Returns: (transfer full): A GList of URI strings.
+ */
+GList *
+nautilus_clipboard_get_uri_list (NautilusClipboard *clip)
+{
+ GList *uris = NULL;
+
+ for (GList *l = clip->files; l != NULL; l = l->next)
+ {
+ uris = g_list_prepend (uris, nautilus_file_get_uri (l->data));
+ }
+
+ return g_list_reverse (uris);
+}
+
+gboolean
+nautilus_clipboard_is_cut (NautilusClipboard *clip)
+{
+ return clip->cut;
+}
+
+NautilusClipboard *
+nautilus_clipboard_copy (NautilusClipboard *clip)
+{
+ NautilusClipboard *new_clip = g_new0 (NautilusClipboard, 1);
+
+ new_clip->cut = clip->cut;
+ new_clip->files = nautilus_file_list_copy (clip->files);
+
+ return new_clip;
+}
+
+void
+nautilus_clipboard_free (NautilusClipboard *clip)
+{
+ nautilus_file_list_free (clip->files);
+ g_free (clip);
+}
+
+void
+nautilus_clipboard_prepare_for_files (GdkClipboard *clipboard,
+ GList *files,
+ gboolean cut)
+{
+ g_autoptr (NautilusClipboard) clip = NULL;
+ g_autoslist (GFile) file_list = NULL;
+ GdkContentProvider *providers[2];
+ g_autoptr (GdkContentProvider) provider = NULL;
+
+ clip = g_new (NautilusClipboard, 1);
+ clip->cut = cut;
+ clip->files = nautilus_file_list_copy (files);
+
+ file_list = convert_file_list_to_gdk_file_list (clip);
+
+ providers[0] = gdk_content_provider_new_typed (NAUTILUS_TYPE_CLIPBOARD, clip);
+ providers[1] = gdk_content_provider_new_typed (GDK_TYPE_FILE_LIST, file_list);
+
+ provider = gdk_content_provider_new_union (providers, 2);
+ gdk_clipboard_set_content (clipboard, provider);
+}
+
+void
+nautilus_clipboard_register (void)
+{
+ /*
+ * While it'is not a public API and the format is not documented, some apps
+ * have come to use this atom/mime type to integrate with our clipboard.
+ */
+ const gchar *nautilus_clipboard_mime_type = "x-special/gnome-copied-files";
+
+ gdk_content_register_serializer (NAUTILUS_TYPE_CLIPBOARD,
+ nautilus_clipboard_mime_type,
+ nautilus_clipboard_serialize,
+ NULL,
+ NULL);
+ gdk_content_register_deserializer (nautilus_clipboard_mime_type,
+ NAUTILUS_TYPE_CLIPBOARD,
+ nautilus_clipboard_deserialize,
+ NULL,
+ NULL);
+}
diff --git a/src/nautilus-clipboard.h b/src/nautilus-clipboard.h
new file mode 100644
index 0000000..5ac1522
--- /dev/null
+++ b/src/nautilus-clipboard.h
@@ -0,0 +1,47 @@
+
+/* fm-directory-view.h
+ *
+ * Copyright (C) 1999, 2000 Free Software Foundaton
+ * Copyright (C) 2000 Eazel, Inc.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library 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
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this program; if not, see <http://www.gnu.org/licenses/>.
+ *
+ * Author: Rebecca Schulman <rebecka@eazel.com>
+ */
+
+#pragma once
+
+#include <gtk/gtk.h>
+
+typedef struct _NautilusClipboard NautilusClipboard;
+#define NAUTILUS_TYPE_CLIPBOARD (nautilus_clipboard_get_type())
+GType nautilus_clipboard_get_type (void);
+
+#if 0 && NAUTILUS_DND_NEEDS_GTK4_REIMPLEMENTATION
+void nautilus_clipboard_clear_if_colliding_uris (GtkWidget *widget,
+ const GList *item_uris);
+#endif
+GList *nautilus_clipboard_peek_files (NautilusClipboard *clip);
+GList *nautilus_clipboard_get_uri_list (NautilusClipboard *clip);
+gboolean nautilus_clipboard_is_cut (NautilusClipboard *clip);
+
+NautilusClipboard *nautilus_clipboard_copy (NautilusClipboard *clip);
+void nautilus_clipboard_free (NautilusClipboard *clip);
+G_DEFINE_AUTOPTR_CLEANUP_FUNC (NautilusClipboard, nautilus_clipboard_free)
+
+void nautilus_clipboard_prepare_for_files (GdkClipboard *clipboard,
+ GList *files,
+ gboolean cut);
+
+void nautilus_clipboard_register (void);
diff --git a/src/nautilus-column-chooser.c b/src/nautilus-column-chooser.c
new file mode 100644
index 0000000..72e18bc
--- /dev/null
+++ b/src/nautilus-column-chooser.c
@@ -0,0 +1,597 @@
+/* nautilus-column-chooser.h - A column chooser widget
+ *
+ * Copyright (C) 2004 Novell, Inc.
+ *
+ * The Gnome Library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * The Gnome Library 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
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with the Gnome Library; see the column COPYING.LIB. If not,
+ * see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Dave Camp <dave@ximian.com>
+ */
+
+#include <config.h>
+#include "nautilus-column-chooser.h"
+
+#include <string.h>
+#include <gtk/gtk.h>
+#include <glib/gi18n.h>
+
+#include <nautilus-extension.h>
+
+#include "nautilus-column-utilities.h"
+
+struct _NautilusColumnChooser
+{
+ GtkBox parent;
+
+ GtkWidget *view;
+ GtkListStore *store;
+
+ GtkWidget *move_up_button;
+ GtkWidget *move_down_button;
+ GtkWidget *use_default_button;
+
+ NautilusFile *file;
+};
+
+enum
+{
+ COLUMN_VISIBLE,
+ COLUMN_LABEL,
+ COLUMN_NAME,
+ COLUMN_SENSITIVE,
+ NUM_COLUMNS
+};
+
+enum
+{
+ PROP_FILE = 1,
+ NUM_PROPERTIES
+};
+
+enum
+{
+ CHANGED,
+ USE_DEFAULT,
+ LAST_SIGNAL
+};
+static guint signals[LAST_SIGNAL];
+
+G_DEFINE_TYPE (NautilusColumnChooser, nautilus_column_chooser, GTK_TYPE_BOX);
+
+static void
+nautilus_column_chooser_set_property (GObject *object,
+ guint param_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ NautilusColumnChooser *chooser;
+
+ chooser = NAUTILUS_COLUMN_CHOOSER (object);
+
+ switch (param_id)
+ {
+ case PROP_FILE:
+ {
+ chooser->file = g_value_get_object (value);
+ }
+ break;
+
+ default:
+ {
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
+ }
+ break;
+ }
+}
+
+static void
+update_buttons (NautilusColumnChooser *chooser)
+{
+ GtkTreeSelection *selection;
+ GtkTreeIter iter;
+
+ selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (chooser->view));
+
+ if (gtk_tree_selection_get_selected (selection, NULL, &iter))
+ {
+ gboolean visible;
+ gboolean top;
+ gboolean bottom;
+ GtkTreePath *first;
+ GtkTreePath *path;
+
+ gtk_tree_model_get (GTK_TREE_MODEL (chooser->store),
+ &iter,
+ COLUMN_VISIBLE, &visible,
+ -1);
+
+ path = gtk_tree_model_get_path (GTK_TREE_MODEL (chooser->store),
+ &iter);
+ first = gtk_tree_path_new_first ();
+
+ top = (gtk_tree_path_compare (path, first) == 0);
+
+ gtk_tree_path_free (path);
+ gtk_tree_path_free (first);
+
+ bottom = !gtk_tree_model_iter_next (GTK_TREE_MODEL (chooser->store),
+ &iter);
+
+ gtk_widget_set_sensitive (chooser->move_up_button,
+ !top);
+ gtk_widget_set_sensitive (chooser->move_down_button,
+ !bottom);
+ }
+ else
+ {
+ gtk_widget_set_sensitive (chooser->move_up_button,
+ FALSE);
+ gtk_widget_set_sensitive (chooser->move_down_button,
+ FALSE);
+ }
+}
+
+static void
+list_changed (NautilusColumnChooser *chooser)
+{
+ update_buttons (chooser);
+ g_signal_emit (chooser, signals[CHANGED], 0);
+}
+
+static void
+toggle_path (NautilusColumnChooser *chooser,
+ GtkTreePath *path)
+{
+ GtkTreeIter iter;
+ gboolean visible;
+ g_autofree gchar *name = NULL;
+
+ gtk_tree_model_get_iter (GTK_TREE_MODEL (chooser->store),
+ &iter, path);
+ gtk_tree_model_get (GTK_TREE_MODEL (chooser->store), &iter,
+ COLUMN_VISIBLE, &visible,
+ COLUMN_NAME, &name, -1);
+
+ if (g_strcmp0 (name, "name") == 0)
+ {
+ /* Don't allow name column to be disabled. */
+ return;
+ }
+
+ gtk_list_store_set (chooser->store,
+ &iter, COLUMN_VISIBLE, !visible, -1);
+ list_changed (chooser);
+}
+
+
+static void
+visible_toggled_callback (GtkCellRendererToggle *cell,
+ char *path_string,
+ gpointer user_data)
+{
+ GtkTreePath *path;
+
+ path = gtk_tree_path_new_from_string (path_string);
+ toggle_path (NAUTILUS_COLUMN_CHOOSER (user_data), path);
+ gtk_tree_path_free (path);
+}
+
+static void
+view_row_activated_callback (GtkTreeView *tree_view,
+ GtkTreePath *path,
+ GtkTreeViewColumn *column,
+ gpointer user_data)
+{
+ toggle_path (NAUTILUS_COLUMN_CHOOSER (user_data), path);
+}
+
+static void
+selection_changed_callback (GtkTreeSelection *selection,
+ gpointer user_data)
+{
+ update_buttons (NAUTILUS_COLUMN_CHOOSER (user_data));
+}
+
+static void
+row_deleted_callback (GtkTreeModel *model,
+ GtkTreePath *path,
+ gpointer user_data)
+{
+ list_changed (NAUTILUS_COLUMN_CHOOSER (user_data));
+}
+
+static void
+move_up_clicked_callback (GtkWidget *button,
+ gpointer user_data)
+{
+ NautilusColumnChooser *chooser;
+ GtkTreeIter iter;
+ GtkTreeSelection *selection;
+
+ chooser = NAUTILUS_COLUMN_CHOOSER (user_data);
+
+ selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (chooser->view));
+
+ if (gtk_tree_selection_get_selected (selection, NULL, &iter))
+ {
+ GtkTreePath *path;
+ GtkTreeIter prev;
+
+ path = gtk_tree_model_get_path (GTK_TREE_MODEL (chooser->store), &iter);
+ gtk_tree_path_prev (path);
+ if (gtk_tree_model_get_iter (GTK_TREE_MODEL (chooser->store), &prev, path))
+ {
+ gtk_list_store_move_before (chooser->store,
+ &iter,
+ &prev);
+ }
+ gtk_tree_path_free (path);
+ }
+
+ list_changed (chooser);
+}
+
+static void
+move_down_clicked_callback (GtkWidget *button,
+ gpointer user_data)
+{
+ NautilusColumnChooser *chooser;
+ GtkTreeIter iter;
+ GtkTreeSelection *selection;
+
+ chooser = NAUTILUS_COLUMN_CHOOSER (user_data);
+
+ selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (chooser->view));
+
+ if (gtk_tree_selection_get_selected (selection, NULL, &iter))
+ {
+ GtkTreeIter next;
+
+ next = iter;
+
+ if (gtk_tree_model_iter_next (GTK_TREE_MODEL (chooser->store), &next))
+ {
+ gtk_list_store_move_after (chooser->store,
+ &iter,
+ &next);
+ }
+ }
+
+ list_changed (chooser);
+}
+
+static void
+use_default_clicked_callback (GtkWidget *button,
+ gpointer user_data)
+{
+ g_signal_emit (NAUTILUS_COLUMN_CHOOSER (user_data),
+ signals[USE_DEFAULT], 0);
+}
+
+static void
+populate_tree (NautilusColumnChooser *chooser)
+{
+ GList *columns;
+ GList *l;
+
+ columns = nautilus_get_columns_for_file (chooser->file);
+
+ for (l = columns; l != NULL; l = l->next)
+ {
+ GtkTreeIter iter;
+ NautilusColumn *column;
+ char *name;
+ char *label;
+ gboolean visible = FALSE;
+ gboolean sensitive = TRUE;
+
+ column = NAUTILUS_COLUMN (l->data);
+
+ g_object_get (G_OBJECT (column),
+ "name", &name, "label", &label,
+ NULL);
+
+ if (strcmp (name, "name") == 0)
+ {
+ visible = TRUE;
+ sensitive = FALSE;
+ }
+ if (strcmp (name, "starred") == 0)
+ {
+ g_free (name);
+ g_free (label);
+ continue;
+ }
+
+ gtk_list_store_append (chooser->store, &iter);
+ gtk_list_store_set (chooser->store, &iter,
+ COLUMN_VISIBLE, visible,
+ COLUMN_LABEL, label,
+ COLUMN_NAME, name,
+ COLUMN_SENSITIVE, sensitive,
+ -1);
+
+ g_free (name);
+ g_free (label);
+ }
+
+ nautilus_column_list_free (columns);
+}
+
+static void
+nautilus_column_chooser_constructed (GObject *object)
+{
+ NautilusColumnChooser *chooser;
+
+ chooser = NAUTILUS_COLUMN_CHOOSER (object);
+
+ populate_tree (chooser);
+
+ g_signal_connect (chooser->store, "row-deleted",
+ G_CALLBACK (row_deleted_callback), chooser);
+}
+
+static void
+set_visible_columns (NautilusColumnChooser *chooser,
+ char **visible_columns)
+{
+ GHashTable *visible_columns_hash;
+ GtkTreeIter iter;
+ int i;
+
+ visible_columns_hash = g_hash_table_new (g_str_hash, g_str_equal);
+ /* always show the name column */
+ g_hash_table_insert (visible_columns_hash, "name", "name");
+ for (i = 0; visible_columns[i] != NULL; ++i)
+ {
+ g_hash_table_insert (visible_columns_hash,
+ visible_columns[i],
+ visible_columns[i]);
+ }
+
+ if (gtk_tree_model_get_iter_first (GTK_TREE_MODEL (chooser->store),
+ &iter))
+ {
+ do
+ {
+ char *name;
+ gboolean visible;
+
+ gtk_tree_model_get (GTK_TREE_MODEL (chooser->store),
+ &iter,
+ COLUMN_NAME, &name,
+ -1);
+
+ visible = (g_hash_table_lookup (visible_columns_hash, name) != NULL);
+
+ gtk_list_store_set (chooser->store,
+ &iter,
+ COLUMN_VISIBLE, visible,
+ -1);
+ g_free (name);
+ }
+ while (gtk_tree_model_iter_next (GTK_TREE_MODEL (chooser->store), &iter));
+ }
+
+ g_hash_table_destroy (visible_columns_hash);
+}
+
+static char **
+get_column_names (NautilusColumnChooser *chooser,
+ gboolean only_visible)
+{
+ GPtrArray *ret;
+ GtkTreeIter iter;
+
+ ret = g_ptr_array_new ();
+ if (gtk_tree_model_get_iter_first (GTK_TREE_MODEL (chooser->store),
+ &iter))
+ {
+ do
+ {
+ char *name;
+ gboolean visible;
+ gtk_tree_model_get (GTK_TREE_MODEL (chooser->store),
+ &iter,
+ COLUMN_VISIBLE, &visible,
+ COLUMN_NAME, &name,
+ -1);
+ if (!only_visible || visible)
+ {
+ /* give ownership to the array */
+ g_ptr_array_add (ret, name);
+ }
+ else
+ {
+ g_free (name);
+ }
+ }
+ while (gtk_tree_model_iter_next (GTK_TREE_MODEL (chooser->store), &iter));
+ }
+ g_ptr_array_add (ret, NULL);
+
+ return (char **) g_ptr_array_free (ret, FALSE);
+}
+
+static gboolean
+get_column_iter (NautilusColumnChooser *chooser,
+ NautilusColumn *column,
+ GtkTreeIter *iter)
+{
+ char *column_name;
+
+ g_object_get (NAUTILUS_COLUMN (column), "name", &column_name, NULL);
+
+ if (gtk_tree_model_get_iter_first (GTK_TREE_MODEL (chooser->store),
+ iter))
+ {
+ do
+ {
+ char *name;
+
+ gtk_tree_model_get (GTK_TREE_MODEL (chooser->store),
+ iter,
+ COLUMN_NAME, &name,
+ -1);
+ if (!strcmp (name, column_name))
+ {
+ g_free (column_name);
+ g_free (name);
+ return TRUE;
+ }
+
+ g_free (name);
+ }
+ while (gtk_tree_model_iter_next (GTK_TREE_MODEL (chooser->store), iter));
+ }
+ g_free (column_name);
+ return FALSE;
+}
+
+static void
+set_column_order (NautilusColumnChooser *chooser,
+ char **column_order)
+{
+ GList *columns;
+ GList *l;
+ GtkTreePath *path;
+
+ columns = nautilus_get_columns_for_file (chooser->file);
+ columns = nautilus_sort_columns (columns, column_order);
+
+ g_signal_handlers_block_by_func (chooser->store,
+ G_CALLBACK (row_deleted_callback),
+ chooser);
+
+ path = gtk_tree_path_new_first ();
+ for (l = columns; l != NULL; l = l->next)
+ {
+ GtkTreeIter iter;
+
+ if (get_column_iter (chooser, NAUTILUS_COLUMN (l->data), &iter))
+ {
+ GtkTreeIter before;
+ if (path)
+ {
+ gtk_tree_model_get_iter (GTK_TREE_MODEL (chooser->store),
+ &before, path);
+ gtk_list_store_move_after (chooser->store,
+ &iter, &before);
+ gtk_tree_path_next (path);
+ }
+ else
+ {
+ gtk_list_store_move_after (chooser->store,
+ &iter, NULL);
+ }
+ }
+ }
+ gtk_tree_path_free (path);
+ g_signal_handlers_unblock_by_func (chooser->store,
+ G_CALLBACK (row_deleted_callback),
+ chooser);
+
+ nautilus_column_list_free (columns);
+}
+
+void
+nautilus_column_chooser_set_settings (NautilusColumnChooser *chooser,
+ char **visible_columns,
+ char **column_order)
+{
+ g_return_if_fail (NAUTILUS_IS_COLUMN_CHOOSER (chooser));
+ g_return_if_fail (visible_columns != NULL);
+ g_return_if_fail (column_order != NULL);
+
+ set_visible_columns (chooser, visible_columns);
+ set_column_order (chooser, column_order);
+
+ list_changed (chooser);
+}
+
+void
+nautilus_column_chooser_get_settings (NautilusColumnChooser *chooser,
+ char ***visible_columns,
+ char ***column_order)
+{
+ g_return_if_fail (NAUTILUS_IS_COLUMN_CHOOSER (chooser));
+ g_return_if_fail (visible_columns != NULL);
+ g_return_if_fail (column_order != NULL);
+
+ *visible_columns = get_column_names (chooser, TRUE);
+ *column_order = get_column_names (chooser, FALSE);
+}
+
+static void
+nautilus_column_chooser_class_init (NautilusColumnChooserClass *chooser_class)
+{
+ GtkWidgetClass *widget_class;
+ GObjectClass *oclass;
+
+ widget_class = GTK_WIDGET_CLASS (chooser_class);
+ oclass = G_OBJECT_CLASS (chooser_class);
+
+ oclass->set_property = nautilus_column_chooser_set_property;
+ oclass->constructed = nautilus_column_chooser_constructed;
+
+ gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/nautilus/ui/nautilus-column-chooser.ui");
+ gtk_widget_class_bind_template_child (widget_class, NautilusColumnChooser, view);
+ gtk_widget_class_bind_template_child (widget_class, NautilusColumnChooser, store);
+ gtk_widget_class_bind_template_child (widget_class, NautilusColumnChooser, move_up_button);
+ gtk_widget_class_bind_template_child (widget_class, NautilusColumnChooser, move_down_button);
+ gtk_widget_class_bind_template_child (widget_class, NautilusColumnChooser, use_default_button);
+ gtk_widget_class_bind_template_callback (widget_class, view_row_activated_callback);
+ gtk_widget_class_bind_template_callback (widget_class, selection_changed_callback);
+ gtk_widget_class_bind_template_callback (widget_class, visible_toggled_callback);
+ gtk_widget_class_bind_template_callback (widget_class, move_up_clicked_callback);
+ gtk_widget_class_bind_template_callback (widget_class, move_down_clicked_callback);
+ gtk_widget_class_bind_template_callback (widget_class, use_default_clicked_callback);
+
+ signals[CHANGED] = g_signal_new
+ ("changed",
+ G_TYPE_FROM_CLASS (chooser_class),
+ G_SIGNAL_RUN_LAST,
+ 0, NULL, NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+
+ signals[USE_DEFAULT] = g_signal_new
+ ("use-default",
+ G_TYPE_FROM_CLASS (chooser_class),
+ G_SIGNAL_RUN_LAST,
+ 0, NULL, NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+
+ g_object_class_install_property (oclass,
+ PROP_FILE,
+ g_param_spec_object ("file",
+ "File",
+ "The file this column chooser is for",
+ NAUTILUS_TYPE_FILE,
+ G_PARAM_CONSTRUCT_ONLY |
+ G_PARAM_WRITABLE));
+}
+
+static void
+nautilus_column_chooser_init (NautilusColumnChooser *chooser)
+{
+ gtk_widget_init_template (GTK_WIDGET (chooser));
+}
+
+GtkWidget *
+nautilus_column_chooser_new (NautilusFile *file)
+{
+ return g_object_new (NAUTILUS_TYPE_COLUMN_CHOOSER, "file", file, NULL);
+}
diff --git a/src/nautilus-column-chooser.h b/src/nautilus-column-chooser.h
new file mode 100644
index 0000000..c52efe4
--- /dev/null
+++ b/src/nautilus-column-chooser.h
@@ -0,0 +1,38 @@
+
+/* nautilus-column-choose.h - A column chooser widget
+
+ Copyright (C) 2004 Novell, Inc.
+
+ The Gnome Library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Library General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version.
+
+ The Gnome Library 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
+ Library General Public License for more details.
+
+ You should have received a copy of the GNU Library General Public
+ License along with the Gnome Library; see the column COPYING.LIB. If not,
+ see <http://www.gnu.org/licenses/>.
+
+ Authors: Dave Camp <dave@ximian.com>
+*/
+
+#pragma once
+
+#include <gtk/gtk.h>
+#include "nautilus-file.h"
+
+#define NAUTILUS_TYPE_COLUMN_CHOOSER nautilus_column_chooser_get_type()
+
+G_DECLARE_FINAL_TYPE (NautilusColumnChooser, nautilus_column_chooser, NAUTILUS, COLUMN_CHOOSER, GtkBox);
+
+GtkWidget *nautilus_column_chooser_new (NautilusFile *file);
+void nautilus_column_chooser_set_settings (NautilusColumnChooser *chooser,
+ char **visible_columns,
+ char **column_order);
+void nautilus_column_chooser_get_settings (NautilusColumnChooser *chooser,
+ char ***visible_columns,
+ char ***column_order);
diff --git a/src/nautilus-column-utilities.c b/src/nautilus-column-utilities.c
new file mode 100644
index 0000000..1132705
--- /dev/null
+++ b/src/nautilus-column-utilities.c
@@ -0,0 +1,416 @@
+/* nautilus-column-utilities.h - Utilities related to column specifications
+ *
+ * Copyright (C) 2004 Novell, Inc.
+ *
+ * The Gnome Library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * The Gnome Library 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
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with the Gnome Library; see the column COPYING.LIB. If not,
+ * see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Dave Camp <dave@ximian.com>
+ */
+
+#include <config.h>
+#include "nautilus-column-utilities.h"
+
+#include <string.h>
+#include <eel/eel-glib-extensions.h>
+#include <glib/gi18n.h>
+#include <nautilus-extension.h>
+#include "nautilus-module.h"
+
+static const char *default_column_order[] =
+{
+ "name",
+ "size",
+ "type",
+ "owner",
+ "group",
+ "permissions",
+ "detailed_type",
+ "where",
+ "date_modified_with_time",
+ "date_modified",
+ "date_accessed",
+ "date_created",
+ "recency",
+ "starred",
+ NULL
+};
+
+static GList *
+get_builtin_columns (void)
+{
+ GList *columns;
+
+ columns = g_list_append (NULL,
+ g_object_new (NAUTILUS_TYPE_COLUMN,
+ "name", "name",
+ "attribute", "name",
+ "label", _("Name"),
+ "description", _("The name and icon of the file."),
+ NULL));
+ columns = g_list_append (columns,
+ g_object_new (NAUTILUS_TYPE_COLUMN,
+ "name", "size",
+ "attribute", "size",
+ "label", _("Size"),
+ "description", _("The size of the file."),
+ NULL));
+ columns = g_list_append (columns,
+ g_object_new (NAUTILUS_TYPE_COLUMN,
+ "name", "type",
+ "attribute", "type",
+ "label", _("Type"),
+ "description", _("The type of the file."),
+ NULL));
+ columns = g_list_append (columns,
+ g_object_new (NAUTILUS_TYPE_COLUMN,
+ "name", "date_modified",
+ "attribute", "date_modified",
+ "label", _("Modified"),
+ "description", _("The date the file was modified."),
+ "default-sort-order", GTK_SORT_DESCENDING,
+ "xalign", 1.0,
+ NULL));
+ columns = g_list_append (columns,
+ g_object_new (NAUTILUS_TYPE_COLUMN,
+ "name", "detailed_type",
+ "attribute", "detailed_type",
+ "label", _("Detailed Type"),
+ "description", _("The detailed type of the file."),
+ NULL));
+ columns = g_list_append (columns,
+ g_object_new (NAUTILUS_TYPE_COLUMN,
+ "name", "date_accessed",
+ "attribute", "date_accessed",
+ "label", _("Accessed"),
+ "description", _("The date the file was accessed."),
+ "default-sort-order", GTK_SORT_DESCENDING,
+ "xalign", 1.0,
+ NULL));
+ columns = g_list_append (columns,
+ g_object_new (NAUTILUS_TYPE_COLUMN,
+ "name", "date_created",
+ "attribute", "date_created",
+ "label", _("Created"),
+ "description", _("The date the file was created."),
+ "default-sort-order", GTK_SORT_DESCENDING,
+ "xalign", 1.0,
+ NULL));
+
+ columns = g_list_append (columns,
+ g_object_new (NAUTILUS_TYPE_COLUMN,
+ "name", "owner",
+ "attribute", "owner",
+ "label", _("Owner"),
+ "description", _("The owner of the file."),
+ NULL));
+
+ columns = g_list_append (columns,
+ g_object_new (NAUTILUS_TYPE_COLUMN,
+ "name", "group",
+ "attribute", "group",
+ "label", _("Group"),
+ "description", _("The group of the file."),
+ NULL));
+
+ columns = g_list_append (columns,
+ g_object_new (NAUTILUS_TYPE_COLUMN,
+ "name", "permissions",
+ "attribute", "permissions",
+ "label", _("Permissions"),
+ "description", _("The permissions of the file."),
+ NULL));
+
+ columns = g_list_append (columns,
+ g_object_new (NAUTILUS_TYPE_COLUMN,
+ "name", "where",
+ "attribute", "where",
+ "label", _("Location"),
+ "description", _("The location of the file."),
+ NULL));
+
+ columns = g_list_append (columns,
+ g_object_new (NAUTILUS_TYPE_COLUMN,
+ "name", "date_modified_with_time",
+ "attribute", "date_modified_with_time",
+ "label", _("Modified — Time"),
+ "description", _("The date the file was modified."),
+ "xalign", 1.0,
+ NULL));
+
+ columns = g_list_append (columns,
+ g_object_new (NAUTILUS_TYPE_COLUMN,
+ "name", "recency",
+ "attribute", "recency",
+ "label", _("Recency"),
+ "description", _("The date the file was accessed by the user."),
+ "default-sort-order", GTK_SORT_DESCENDING,
+ "xalign", 1.0,
+ NULL));
+
+ columns = g_list_append (columns,
+ g_object_new (NAUTILUS_TYPE_COLUMN,
+ "name", "starred",
+ "attribute", "starred",
+ "label", _("Star"),
+ "description", _("Shows if file is starred."),
+ "default-sort-order", GTK_SORT_DESCENDING,
+ "xalign", 0.5,
+ NULL));
+
+ return columns;
+}
+
+static GList *
+get_extension_columns (void)
+{
+ GList *columns;
+ GList *providers;
+ GList *l;
+
+ providers = nautilus_module_get_extensions_for_type (NAUTILUS_TYPE_COLUMN_PROVIDER);
+
+ columns = NULL;
+
+ for (l = providers; l != NULL; l = l->next)
+ {
+ NautilusColumnProvider *provider;
+ GList *provider_columns;
+
+ provider = NAUTILUS_COLUMN_PROVIDER (l->data);
+ provider_columns = nautilus_column_provider_get_columns (provider);
+ columns = g_list_concat (columns, provider_columns);
+ }
+
+ nautilus_module_extension_list_free (providers);
+
+ return columns;
+}
+
+static GList *
+get_trash_columns (void)
+{
+ static GList *columns = NULL;
+
+ if (columns == NULL)
+ {
+ columns = g_list_append (columns,
+ g_object_new (NAUTILUS_TYPE_COLUMN,
+ "name", "trashed_on",
+ "attribute", "trashed_on",
+ "label", _("Trashed On"),
+ "description", _("Date when file was moved to the Trash"),
+ "xalign", 1.0,
+ NULL));
+ columns = g_list_append (columns,
+ g_object_new (NAUTILUS_TYPE_COLUMN,
+ "name", "trash_orig_path",
+ "attribute", "trash_orig_path",
+ "label", _("Original Location"),
+ "description", _("Original location of file before moved to the Trash"),
+ NULL));
+ }
+
+ return nautilus_column_list_copy (columns);
+}
+
+static GList *
+get_search_columns (void)
+{
+ static GList *columns = NULL;
+
+ if (columns == NULL)
+ {
+ columns = g_list_append (columns,
+ g_object_new (NAUTILUS_TYPE_COLUMN,
+ "name", "search_relevance",
+ "attribute", "search_relevance",
+ "label", _("Relevance"),
+ "description", _("Relevance rank for search"),
+ NULL));
+ }
+
+ return nautilus_column_list_copy (columns);
+}
+
+GList *
+nautilus_get_common_columns (void)
+{
+ static GList *columns = NULL;
+
+ if (!columns)
+ {
+ columns = g_list_concat (get_builtin_columns (),
+ get_extension_columns ());
+ }
+
+ return nautilus_column_list_copy (columns);
+}
+
+GList *
+nautilus_get_all_columns (void)
+{
+ GList *columns = NULL;
+
+ columns = g_list_concat (nautilus_get_common_columns (),
+ get_trash_columns ());
+ columns = g_list_concat (columns,
+ get_search_columns ());
+
+ return columns;
+}
+
+GList *
+nautilus_get_columns_for_file (NautilusFile *file)
+{
+ GList *columns;
+
+ columns = nautilus_get_common_columns ();
+
+ if (file != NULL && nautilus_file_is_in_trash (file))
+ {
+ columns = g_list_concat (columns,
+ get_trash_columns ());
+ }
+
+ return columns;
+}
+
+GList *
+nautilus_column_list_copy (GList *columns)
+{
+ GList *ret;
+ GList *l;
+
+ ret = g_list_copy (columns);
+
+ for (l = ret; l != NULL; l = l->next)
+ {
+ g_object_ref (l->data);
+ }
+
+ return ret;
+}
+
+void
+nautilus_column_list_free (GList *columns)
+{
+ GList *l;
+
+ for (l = columns; l != NULL; l = l->next)
+ {
+ g_object_unref (l->data);
+ }
+
+ g_list_free (columns);
+}
+
+static int
+strv_index (char **strv,
+ const char *str)
+{
+ int i;
+
+ for (i = 0; strv[i] != NULL; ++i)
+ {
+ if (strcmp (strv[i], str) == 0)
+ {
+ return i;
+ }
+ }
+
+ return -1;
+}
+
+static int
+column_compare (NautilusColumn *a,
+ NautilusColumn *b,
+ char **column_order)
+{
+ int index_a;
+ int index_b;
+ char *name_a;
+ char *name_b;
+ int ret;
+
+ g_object_get (G_OBJECT (a), "name", &name_a, NULL);
+ index_a = strv_index (column_order, name_a);
+
+ g_object_get (G_OBJECT (b), "name", &name_b, NULL);
+ index_b = strv_index (column_order, name_b);
+
+ if (index_a == index_b)
+ {
+ int pos_a;
+ int pos_b;
+
+ pos_a = strv_index ((char **) default_column_order, name_a);
+ pos_b = strv_index ((char **) default_column_order, name_b);
+
+ if (pos_a == pos_b)
+ {
+ char *label_a;
+ char *label_b;
+
+ g_object_get (G_OBJECT (a), "label", &label_a, NULL);
+ g_object_get (G_OBJECT (b), "label", &label_b, NULL);
+ ret = strcmp (label_a, label_b);
+ g_free (label_a);
+ g_free (label_b);
+ }
+ else if (pos_a == -1)
+ {
+ ret = 1;
+ }
+ else if (pos_b == -1)
+ {
+ ret = -1;
+ }
+ else
+ {
+ ret = index_a - index_b;
+ }
+ }
+ else if (index_a == -1)
+ {
+ ret = 1;
+ }
+ else if (index_b == -1)
+ {
+ ret = -1;
+ }
+ else
+ {
+ ret = index_a - index_b;
+ }
+
+ g_free (name_a);
+ g_free (name_b);
+
+ return ret;
+}
+
+GList *
+nautilus_sort_columns (GList *columns,
+ char **column_order)
+{
+ if (column_order == NULL)
+ {
+ return columns;
+ }
+
+ return g_list_sort_with_data (columns,
+ (GCompareDataFunc) column_compare,
+ column_order);
+}
diff --git a/src/nautilus-column-utilities.h b/src/nautilus-column-utilities.h
new file mode 100644
index 0000000..56a363f
--- /dev/null
+++ b/src/nautilus-column-utilities.h
@@ -0,0 +1,34 @@
+
+/* nautilus-column-utilities.h - Utilities related to column specifications
+
+ Copyright (C) 2004 Novell, Inc.
+
+ The Gnome Library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Library General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version.
+
+ The Gnome Library 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
+ Library General Public License for more details.
+
+ You should have received a copy of the GNU Library General Public
+ License along with the Gnome Library; see the column COPYING.LIB. If not,
+ see <http://www.gnu.org/licenses/>.
+
+ Authors: Dave Camp <dave@ximian.com>
+*/
+
+#pragma once
+
+#include "nautilus-file.h"
+
+GList *nautilus_get_all_columns (void);
+GList *nautilus_get_common_columns (void);
+GList *nautilus_get_columns_for_file (NautilusFile *file);
+GList *nautilus_column_list_copy (GList *columns);
+void nautilus_column_list_free (GList *columns);
+
+GList *nautilus_sort_columns (GList *columns,
+ char **column_order);
diff --git a/src/nautilus-compress-dialog-controller.c b/src/nautilus-compress-dialog-controller.c
new file mode 100644
index 0000000..9937beb
--- /dev/null
+++ b/src/nautilus-compress-dialog-controller.c
@@ -0,0 +1,612 @@
+/* nautilus-compress-dialog-controller.h
+ *
+ * Copyright (C) 2016 the Nautilus developers
+ *
+ * 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/>.
+ *
+ */
+
+#include <glib/gi18n.h>
+#include <gnome-autoar/gnome-autoar.h>
+#include <libadwaita-1/adwaita.h>
+
+#include <eel/eel-vfs-extensions.h>
+
+#include "nautilus-compress-dialog-controller.h"
+
+#include "nautilus-global-preferences.h"
+
+struct _NautilusCompressDialogController
+{
+ NautilusFileNameWidgetController parent_instance;
+
+ GtkWidget *compress_dialog;
+ GtkWidget *activate_button;
+ GtkWidget *error_label;
+ GtkWidget *name_entry;
+ GtkWidget *extension_dropdown;
+ GtkSizeGroup *extension_sizegroup;
+ GtkWidget *passphrase_label;
+ GtkWidget *passphrase_entry;
+
+ const char *extension;
+ gchar *passphrase;
+
+ gulong response_handler_id;
+};
+
+G_DEFINE_TYPE (NautilusCompressDialogController, nautilus_compress_dialog_controller, NAUTILUS_TYPE_FILE_NAME_WIDGET_CONTROLLER);
+
+#define NAUTILUS_TYPE_COMPRESS_ITEM (nautilus_compress_item_get_type ())
+G_DECLARE_FINAL_TYPE (NautilusCompressItem, nautilus_compress_item, NAUTILUS, COMPRESS_ITEM, GObject)
+
+struct _NautilusCompressItem
+{
+ GObject parent_instance;
+ NautilusCompressionFormat format;
+ char *extension;
+ char *description;
+};
+
+G_DEFINE_TYPE (NautilusCompressItem, nautilus_compress_item, G_TYPE_OBJECT);
+
+static void
+nautilus_compress_item_init (NautilusCompressItem *item)
+{
+}
+
+static void
+nautilus_compress_item_finalize (GObject *object)
+{
+ NautilusCompressItem *item = NAUTILUS_COMPRESS_ITEM (object);
+
+ g_free (item->extension);
+ g_free (item->description);
+
+ G_OBJECT_CLASS (nautilus_compress_item_parent_class)->finalize (object);
+}
+
+static void
+nautilus_compress_item_class_init (NautilusCompressItemClass *class)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (class);
+
+ object_class->finalize = nautilus_compress_item_finalize;
+}
+
+static NautilusCompressItem *
+nautilus_compress_item_new (NautilusCompressionFormat format,
+ const char *extension,
+ const char *description)
+{
+ NautilusCompressItem *item = g_object_new (NAUTILUS_TYPE_COMPRESS_ITEM, NULL);
+
+ item->format = format;
+ item->extension = g_strdup (extension);
+ item->description = g_strdup (description);
+
+ return item;
+}
+
+static gboolean
+nautilus_compress_dialog_controller_name_is_valid (NautilusFileNameWidgetController *self,
+ gchar *name,
+ gchar **error_message)
+{
+ gboolean is_valid;
+
+ is_valid = TRUE;
+ if (strlen (name) == 0)
+ {
+ is_valid = FALSE;
+ }
+ else if (strstr (name, "/") != NULL)
+ {
+ is_valid = FALSE;
+ *error_message = _("Archive names cannot contain “/”.");
+ }
+ else if (strcmp (name, ".") == 0)
+ {
+ is_valid = FALSE;
+ *error_message = _("An archive cannot be called “.”.");
+ }
+ else if (strcmp (name, "..") == 0)
+ {
+ is_valid = FALSE;
+ *error_message = _("An archive cannot be called “..”.");
+ }
+ else if (nautilus_file_name_widget_controller_is_name_too_long (self, name))
+ {
+ is_valid = FALSE;
+ *error_message = _("Archive name is too long.");
+ }
+
+ if (is_valid && g_str_has_prefix (name, "."))
+ {
+ /* We must warn about the side effect */
+ *error_message = _("Archives with “.” at the beginning of their name are hidden.");
+ }
+
+ return is_valid;
+}
+
+static gchar *
+nautilus_compress_dialog_controller_get_new_name (NautilusFileNameWidgetController *controller)
+{
+ NautilusCompressDialogController *self;
+ g_autofree gchar *basename = NULL;
+ gchar *error_message = NULL;
+ gboolean valid_name;
+
+ self = NAUTILUS_COMPRESS_DIALOG_CONTROLLER (controller);
+
+ basename = NAUTILUS_FILE_NAME_WIDGET_CONTROLLER_CLASS (nautilus_compress_dialog_controller_parent_class)->get_new_name (controller);
+ /* Do not check or add the extension if the name is invalid */
+ valid_name = nautilus_compress_dialog_controller_name_is_valid (controller,
+ basename,
+ &error_message);
+
+ if (!valid_name)
+ {
+ return g_strdup (basename);
+ }
+
+ if (g_str_has_suffix (basename, self->extension))
+ {
+ return g_strdup (basename);
+ }
+
+ return g_strconcat (basename, self->extension, NULL);
+}
+
+static void
+compress_dialog_controller_on_response (GtkDialog *dialog,
+ gint response_id,
+ gpointer user_data)
+{
+ NautilusCompressDialogController *controller;
+
+ controller = NAUTILUS_COMPRESS_DIALOG_CONTROLLER (user_data);
+
+ if (response_id != GTK_RESPONSE_OK)
+ {
+ g_signal_emit_by_name (controller, "cancelled");
+ }
+}
+
+static void
+update_selected_format (NautilusCompressDialogController *self)
+{
+ gboolean show_passphrase = FALSE;
+ guint selected;
+ GListModel *model;
+ NautilusCompressItem *item;
+
+ selected = gtk_drop_down_get_selected (GTK_DROP_DOWN (self->extension_dropdown));
+ if (selected == GTK_INVALID_LIST_POSITION)
+ {
+ return;
+ }
+
+ model = gtk_drop_down_get_model (GTK_DROP_DOWN (self->extension_dropdown));
+ item = g_list_model_get_item (model, selected);
+ if (item == NULL)
+ {
+ return;
+ }
+
+ if (item->format == NAUTILUS_COMPRESSION_ENCRYPTED_ZIP)
+ {
+ show_passphrase = TRUE;
+ }
+
+ self->extension = item->extension;
+
+ gtk_widget_set_visible (self->passphrase_label, show_passphrase);
+ gtk_widget_set_visible (self->passphrase_entry, show_passphrase);
+ if (!show_passphrase)
+ {
+ gtk_editable_set_text (GTK_EDITABLE (self->passphrase_entry), "");
+ gtk_entry_set_visibility (GTK_ENTRY (self->passphrase_entry), FALSE);
+ gtk_entry_set_icon_from_icon_name (GTK_ENTRY (self->passphrase_entry),
+ GTK_ENTRY_ICON_SECONDARY,
+ "view-conceal");
+ }
+
+ g_settings_set_enum (nautilus_compression_preferences,
+ NAUTILUS_PREFERENCES_DEFAULT_COMPRESSION_FORMAT,
+ item->format);
+
+ /* Since the extension changes when the button is toggled, force a
+ * verification of the new file name by simulating an entry change
+ */
+ gtk_widget_set_sensitive (self->activate_button, FALSE);
+ g_signal_emit_by_name (self->name_entry, "changed");
+}
+
+static void
+extension_dropdown_setup_item (GtkSignalListItemFactory *factory,
+ GtkListItem *item,
+ gpointer user_data)
+{
+ GtkWidget *title;
+
+ title = gtk_label_new ("");
+ gtk_label_set_xalign (GTK_LABEL (title), 0.0);
+
+ g_object_set_data (G_OBJECT (item), "title", title);
+ gtk_list_item_set_child (item, title);
+}
+
+
+static void
+extension_dropdown_setup_item_full (GtkSignalListItemFactory *factory,
+ GtkListItem *item,
+ gpointer user_data)
+{
+ GtkWidget *hbox, *vbox, *title, *subtitle, *checkmark;
+
+ title = gtk_label_new ("");
+ gtk_label_set_xalign (GTK_LABEL (title), 0.0);
+ gtk_widget_set_halign (title, GTK_ALIGN_START);
+
+ subtitle = gtk_label_new ("");
+ gtk_label_set_xalign (GTK_LABEL (subtitle), 0.0);
+ gtk_widget_add_css_class (subtitle, "dim-label");
+ gtk_widget_add_css_class (subtitle, "caption");
+
+ checkmark = gtk_image_new_from_icon_name ("object-select-symbolic");
+
+ hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 6);
+ vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 3);
+ gtk_widget_set_hexpand (vbox, TRUE);
+
+ gtk_box_append (GTK_BOX (hbox), vbox);
+ gtk_box_append (GTK_BOX (vbox), title);
+ gtk_box_append (GTK_BOX (vbox), subtitle);
+ gtk_box_append (GTK_BOX (hbox), checkmark);
+
+ g_object_set_data (G_OBJECT (item), "title", title);
+ g_object_set_data (G_OBJECT (item), "subtitle", subtitle);
+ g_object_set_data (G_OBJECT (item), "checkmark", checkmark);
+
+ gtk_list_item_set_child (item, hbox);
+}
+
+static void
+extension_dropdown_on_selected_item_notify (GtkDropDown *dropdown,
+ GParamSpec *pspec,
+ GtkListItem *item)
+{
+ GtkWidget *checkmark;
+
+ checkmark = g_object_get_data (G_OBJECT (item), "checkmark");
+
+ if (gtk_drop_down_get_selected_item (dropdown) == gtk_list_item_get_item (item))
+ {
+ gtk_widget_set_opacity (checkmark, 1.0);
+ }
+ else
+ {
+ gtk_widget_set_opacity (checkmark, 0.0);
+ }
+}
+
+static void
+extension_dropdown_bind (GtkSignalListItemFactory *factory,
+ GtkListItem *list_item,
+ gpointer user_data)
+{
+ NautilusCompressDialogController *self;
+ GtkWidget *title, *subtitle, *checkmark;
+ NautilusCompressItem *item;
+
+ self = NAUTILUS_COMPRESS_DIALOG_CONTROLLER (user_data);
+ item = gtk_list_item_get_item (list_item);
+
+ title = g_object_get_data (G_OBJECT (list_item), "title");
+ subtitle = g_object_get_data (G_OBJECT (list_item), "subtitle");
+ checkmark = g_object_get_data (G_OBJECT (list_item), "checkmark");
+
+ gtk_label_set_label (GTK_LABEL (title), item->extension);
+ gtk_size_group_add_widget (self->extension_sizegroup, title);
+
+ if (item->format == NAUTILUS_COMPRESSION_ENCRYPTED_ZIP)
+ {
+ gtk_widget_add_css_class (title, "encrypted_zip");
+ }
+
+ if (subtitle)
+ {
+ gtk_label_set_label (GTK_LABEL (subtitle), item->description);
+ }
+
+ if (checkmark)
+ {
+ g_signal_connect (self->extension_dropdown,
+ "notify::selected-item",
+ G_CALLBACK (extension_dropdown_on_selected_item_notify),
+ list_item);
+ extension_dropdown_on_selected_item_notify (GTK_DROP_DOWN (self->extension_dropdown),
+ NULL,
+ list_item);
+ }
+}
+
+static void
+extension_dropdown_unbind (GtkSignalListItemFactory *factory,
+ GtkListItem *item,
+ gpointer user_data)
+{
+ NautilusCompressDialogController *self;
+ GtkWidget *title;
+
+ self = NAUTILUS_COMPRESS_DIALOG_CONTROLLER (user_data);
+ g_signal_handlers_disconnect_by_func (self->extension_dropdown,
+ extension_dropdown_on_selected_item_notify,
+ item);
+
+ title = g_object_get_data (G_OBJECT (item), "title");
+ if (title)
+ {
+ gtk_widget_remove_css_class (title, "encrypted_zip");
+ }
+}
+
+static void
+passphrase_entry_on_changed (GtkEditable *editable,
+ gpointer user_data)
+{
+ NautilusCompressDialogController *self;
+ const gchar *error_message;
+
+ self = NAUTILUS_COMPRESS_DIALOG_CONTROLLER (user_data);
+
+ g_free (self->passphrase);
+ self->passphrase = g_strdup (gtk_editable_get_text (GTK_EDITABLE (self->passphrase_entry)));
+
+ /* Simulate a change of the name_entry to ensure the correct sensitivity of
+ * the activate_button, but only if the name_entry is valid in order to
+ * avoid changes of the error_revealer.
+ */
+ error_message = gtk_label_get_text (GTK_LABEL (self->error_label));
+ if (error_message[0] == '\0')
+ {
+ gtk_widget_set_sensitive (self->activate_button, FALSE);
+ g_signal_emit_by_name (self->name_entry, "changed");
+ }
+}
+
+static void
+passphrase_entry_on_icon_press (GtkEntry *entry,
+ GtkEntryIconPosition icon_pos,
+ gpointer user_data)
+{
+ NautilusCompressDialogController *self;
+ gboolean visibility;
+
+ self = NAUTILUS_COMPRESS_DIALOG_CONTROLLER (user_data);
+ visibility = gtk_entry_get_visibility (GTK_ENTRY (self->passphrase_entry));
+
+ gtk_entry_set_icon_from_icon_name (GTK_ENTRY (self->passphrase_entry),
+ GTK_ENTRY_ICON_SECONDARY,
+ visibility ? "view-conceal" : "view-reveal");
+ gtk_entry_set_visibility (GTK_ENTRY (self->passphrase_entry), !visibility);
+}
+
+static void
+activate_button_on_sensitive_notify (GObject *gobject,
+ GParamSpec *pspec,
+ gpointer user_data)
+{
+ NautilusCompressDialogController *self;
+ NautilusCompressionFormat format;
+
+ self = NAUTILUS_COMPRESS_DIALOG_CONTROLLER (user_data);
+ format = g_settings_get_enum (nautilus_compression_preferences,
+ NAUTILUS_PREFERENCES_DEFAULT_COMPRESSION_FORMAT);
+ if (format == NAUTILUS_COMPRESSION_ENCRYPTED_ZIP &&
+ (self->passphrase == NULL || self->passphrase[0] == '\0'))
+ {
+ /* Reset sensitivity of the activate_button if password is not set. */
+ gtk_widget_set_sensitive (self->activate_button, FALSE);
+ }
+}
+
+static void
+extension_dropdown_setup (NautilusCompressDialogController *self)
+{
+ GtkListItemFactory *factory, *list_factory;
+ GListStore *store;
+ NautilusCompressItem *item;
+ NautilusCompressionFormat format;
+ gint i;
+
+ store = g_list_store_new (NAUTILUS_TYPE_COMPRESS_ITEM);
+ item = nautilus_compress_item_new (NAUTILUS_COMPRESSION_ZIP,
+ ".zip",
+ _("Compatible with all operating systems."));
+ g_list_store_append (store, item);
+ g_object_unref (item);
+ item = nautilus_compress_item_new (NAUTILUS_COMPRESSION_ENCRYPTED_ZIP,
+ ".zip",
+ _("Password protected .zip, must be installed on Windows and Mac."));
+ g_list_store_append (store, item);
+ g_object_unref (item);
+ item = nautilus_compress_item_new (NAUTILUS_COMPRESSION_TAR_XZ,
+ ".tar.xz",
+ _("Smaller archives but Linux and Mac only."));
+ g_list_store_append (store, item);
+ g_object_unref (item);
+ item = nautilus_compress_item_new (NAUTILUS_COMPRESSION_7ZIP,
+ ".7z",
+ _("Smaller archives but must be installed on Windows and Mac."));
+ g_list_store_append (store, item);
+ g_object_unref (item);
+
+ factory = gtk_signal_list_item_factory_new ();
+ g_signal_connect_object (factory, "setup",
+ G_CALLBACK (extension_dropdown_setup_item), self, 0);
+ g_signal_connect_object (factory, "bind",
+ G_CALLBACK (extension_dropdown_bind), self, 0);
+ g_signal_connect_object (factory, "unbind",
+ G_CALLBACK (extension_dropdown_unbind), self, 0);
+
+ list_factory = gtk_signal_list_item_factory_new ();
+ g_signal_connect_object (list_factory, "setup",
+ G_CALLBACK (extension_dropdown_setup_item_full), self, 0);
+ g_signal_connect_object (list_factory, "bind",
+ G_CALLBACK (extension_dropdown_bind), self, 0);
+ g_signal_connect_object (list_factory, "unbind",
+ G_CALLBACK (extension_dropdown_unbind), self, 0);
+
+ gtk_drop_down_set_factory (GTK_DROP_DOWN (self->extension_dropdown), factory);
+ gtk_drop_down_set_list_factory (GTK_DROP_DOWN (self->extension_dropdown), list_factory);
+ gtk_drop_down_set_model (GTK_DROP_DOWN (self->extension_dropdown), G_LIST_MODEL (store));
+
+ format = g_settings_get_enum (nautilus_compression_preferences,
+ NAUTILUS_PREFERENCES_DEFAULT_COMPRESSION_FORMAT);
+ for (i = 0; i < g_list_model_get_n_items (G_LIST_MODEL (store)); i++)
+ {
+ item = g_list_model_get_item (G_LIST_MODEL (store), i);
+ if (item->format == format)
+ {
+ gtk_drop_down_set_selected (GTK_DROP_DOWN (self->extension_dropdown), i);
+ update_selected_format (self);
+ g_object_unref (item);
+ break;
+ }
+
+ g_object_unref (item);
+ }
+
+ g_object_unref (store);
+ g_object_unref (factory);
+ g_object_unref (list_factory);
+}
+
+NautilusCompressDialogController *
+nautilus_compress_dialog_controller_new (GtkWindow *parent_window,
+ NautilusDirectory *destination_directory,
+ gchar *initial_name)
+{
+ NautilusCompressDialogController *self;
+ g_autoptr (GtkBuilder) builder = NULL;
+ GtkWidget *compress_dialog;
+ GtkWidget *error_revealer;
+ GtkWidget *error_label;
+ GtkWidget *name_entry;
+ GtkWidget *activate_button;
+ GtkWidget *extension_dropdown;
+ GtkSizeGroup *extension_sizegroup;
+ GtkWidget *passphrase_label;
+ GtkWidget *passphrase_entry;
+
+ builder = gtk_builder_new_from_resource ("/org/gnome/nautilus/ui/nautilus-compress-dialog.ui");
+ compress_dialog = GTK_WIDGET (gtk_builder_get_object (builder, "compress_dialog"));
+ error_revealer = GTK_WIDGET (gtk_builder_get_object (builder, "error_revealer"));
+ error_label = GTK_WIDGET (gtk_builder_get_object (builder, "error_label"));
+ name_entry = GTK_WIDGET (gtk_builder_get_object (builder, "name_entry"));
+ activate_button = GTK_WIDGET (gtk_builder_get_object (builder, "activate_button"));
+ extension_dropdown = GTK_WIDGET (gtk_builder_get_object (builder, "extension_dropdown"));
+ extension_sizegroup = GTK_SIZE_GROUP (gtk_builder_get_object (builder, "extension_sizegroup"));
+ passphrase_label = GTK_WIDGET (gtk_builder_get_object (builder, "passphrase_label"));
+ passphrase_entry = GTK_WIDGET (gtk_builder_get_object (builder, "passphrase_entry"));
+
+ gtk_window_set_transient_for (GTK_WINDOW (compress_dialog),
+ parent_window);
+
+ self = g_object_new (NAUTILUS_TYPE_COMPRESS_DIALOG_CONTROLLER,
+ "error-revealer", error_revealer,
+ "error-label", error_label,
+ "name-entry", name_entry,
+ "activate-button", activate_button,
+ "containing-directory", destination_directory, NULL);
+
+ self->compress_dialog = compress_dialog;
+ self->activate_button = activate_button;
+ self->error_label = error_label;
+ self->name_entry = name_entry;
+ self->extension_dropdown = extension_dropdown;
+ self->extension_sizegroup = extension_sizegroup;
+ self->passphrase_label = passphrase_label;
+ self->passphrase_entry = passphrase_entry;
+
+ extension_dropdown_setup (self);
+
+ self->response_handler_id = g_signal_connect (compress_dialog,
+ "response",
+ (GCallback) compress_dialog_controller_on_response,
+ self);
+
+ g_signal_connect (self->passphrase_entry, "changed",
+ G_CALLBACK (passphrase_entry_on_changed), self);
+ g_signal_connect (self->passphrase_entry, "icon-press",
+ G_CALLBACK (passphrase_entry_on_icon_press), self);
+ g_signal_connect (self->activate_button, "notify::sensitive",
+ G_CALLBACK (activate_button_on_sensitive_notify), self);
+ g_signal_connect_swapped (self->extension_dropdown, "notify::selected-item",
+ G_CALLBACK (update_selected_format), self);
+
+ if (initial_name != NULL)
+ {
+ gtk_editable_set_text (GTK_EDITABLE (name_entry), initial_name);
+ }
+
+ gtk_widget_show (compress_dialog);
+
+ return self;
+}
+
+static void
+nautilus_compress_dialog_controller_init (NautilusCompressDialogController *self)
+{
+}
+
+static void
+nautilus_compress_dialog_controller_finalize (GObject *object)
+{
+ NautilusCompressDialogController *self;
+
+ self = NAUTILUS_COMPRESS_DIALOG_CONTROLLER (object);
+
+ if (self->compress_dialog != NULL)
+ {
+ g_clear_signal_handler (&self->response_handler_id, self->compress_dialog);
+ gtk_window_destroy (GTK_WINDOW (self->compress_dialog));
+ self->compress_dialog = NULL;
+ }
+
+ g_free (self->passphrase);
+
+ G_OBJECT_CLASS (nautilus_compress_dialog_controller_parent_class)->finalize (object);
+}
+
+static void
+nautilus_compress_dialog_controller_class_init (NautilusCompressDialogControllerClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ NautilusFileNameWidgetControllerClass *parent_class = NAUTILUS_FILE_NAME_WIDGET_CONTROLLER_CLASS (klass);
+
+ object_class->finalize = nautilus_compress_dialog_controller_finalize;
+
+ parent_class->get_new_name = nautilus_compress_dialog_controller_get_new_name;
+ parent_class->name_is_valid = nautilus_compress_dialog_controller_name_is_valid;
+}
+
+const gchar *
+nautilus_compress_dialog_controller_get_passphrase (NautilusCompressDialogController *self)
+{
+ return self->passphrase;
+}
diff --git a/src/nautilus-compress-dialog-controller.h b/src/nautilus-compress-dialog-controller.h
new file mode 100644
index 0000000..6c96d68
--- /dev/null
+++ b/src/nautilus-compress-dialog-controller.h
@@ -0,0 +1,34 @@
+/* nautilus-compress-dialog-controller.h
+ *
+ * Copyright (C) 2016 the Nautilus developers
+ *
+ * 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/>.
+ *
+ */
+
+#pragma once
+
+#include <glib.h>
+#include <gtk/gtk.h>
+
+#include "nautilus-file-name-widget-controller.h"
+#include "nautilus-directory.h"
+
+#define NAUTILUS_TYPE_COMPRESS_DIALOG_CONTROLLER nautilus_compress_dialog_controller_get_type ()
+G_DECLARE_FINAL_TYPE (NautilusCompressDialogController, nautilus_compress_dialog_controller, NAUTILUS, COMPRESS_DIALOG_CONTROLLER, NautilusFileNameWidgetController)
+
+NautilusCompressDialogController * nautilus_compress_dialog_controller_new (GtkWindow *parent_window,
+ NautilusDirectory *destination_directory,
+ gchar *initial_name);
+const gchar * nautilus_compress_dialog_controller_get_passphrase (NautilusCompressDialogController *controller);
diff --git a/src/nautilus-dbus-launcher.c b/src/nautilus-dbus-launcher.c
new file mode 100644
index 0000000..491f967
--- /dev/null
+++ b/src/nautilus-dbus-launcher.c
@@ -0,0 +1,231 @@
+/*
+ * Copyright (C) 2022 Corey Berla <corey@berla.me>
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#include "nautilus-dbus-launcher.h"
+
+#include <glib/gi18n.h>
+
+#include "nautilus-file.h"
+#include "nautilus-ui-utilities.h"
+
+
+typedef struct
+{
+ GDBusProxy *proxy;
+ gchar *error;
+ GCancellable *cancellable;
+ gboolean ping_on_creation;
+} NautilusDBusLauncherData;
+
+struct _NautilusDBusLauncher
+{
+ GObject parent;
+
+ NautilusDBusLauncherApp last_app_initialized;
+ NautilusDBusLauncherData *data[NAUTILUS_DBUS_LAUNCHER_N_APPS];
+};
+
+G_DEFINE_TYPE (NautilusDBusLauncher, nautilus_dbus_launcher, G_TYPE_OBJECT)
+
+static NautilusDBusLauncher *launcher = NULL;
+
+static void
+on_nautilus_dbus_launcher_call_finished (GObject *source_object,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ GtkWindow *window = user_data;
+ g_autoptr (GError) error = NULL;
+ g_autofree char *message = NULL;
+
+ g_dbus_proxy_call_finish (G_DBUS_PROXY (source_object), res, &error);
+ if (error != NULL)
+ {
+ g_warning ("Error calling proxy %s", error->message);
+ message = g_strdup_printf (_("Details: %s"), error->message);
+ show_dialog (_("There was an error launching the application."),
+ message,
+ window,
+ GTK_MESSAGE_ERROR);
+ }
+}
+
+static void
+on_nautilus_dbus_launcher_ping_finished (GObject *source_object,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ NautilusDBusLauncherData *data = user_data;
+ g_autoptr (GError) error = NULL;
+
+ g_dbus_proxy_call_finish (G_DBUS_PROXY (source_object), res, &error);
+
+ if (error != NULL)
+ {
+ data->error = g_strdup (error->message);
+ }
+
+ g_clear_object (&data->cancellable);
+}
+
+void
+nautilus_dbus_launcher_call (NautilusDBusLauncher *self,
+ NautilusDBusLauncherApp app,
+ const gchar *method_name,
+ GVariant *parameters,
+ GtkWindow *window)
+{
+ if (self->data[app]->proxy != NULL)
+ {
+ g_dbus_proxy_call (self->data[app]->proxy,
+ method_name,
+ parameters,
+ G_DBUS_CALL_FLAGS_NONE,
+ G_MAXINT,
+ NULL,
+ on_nautilus_dbus_launcher_call_finished,
+ window);
+ }
+ else if (window != NULL)
+ {
+ show_dialog (_("There was an error launching the application."),
+ _("Details: The proxy has not been created."),
+ window,
+ GTK_MESSAGE_ERROR);
+ }
+}
+
+static void
+on_nautilus_dbus_proxy_ready (GObject *source_object,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ NautilusDBusLauncherData *data = user_data;
+ g_autoptr (GError) error = NULL;
+
+ data->proxy = g_dbus_proxy_new_for_bus_finish (res, &error);
+
+ if (error != NULL)
+ {
+ g_warning ("Error creating proxy %s", error->message);
+ data->error = g_strdup (error->message);
+ g_clear_object (&data->cancellable);
+ }
+ else if (data->ping_on_creation)
+ {
+ g_dbus_proxy_call (data->proxy,
+ "org.freedesktop.DBus.Peer.Ping", NULL,
+ G_DBUS_CALL_FLAGS_NONE, G_MAXINT, data->cancellable,
+ on_nautilus_dbus_launcher_ping_finished, data);
+ }
+}
+
+static void
+nautilus_dbus_launcher_create_proxy (NautilusDBusLauncherData *data,
+ const gchar *name,
+ const gchar *object_path,
+ const gchar *interface)
+{
+ g_dbus_proxy_new_for_bus (G_BUS_TYPE_SESSION,
+ G_DBUS_PROXY_FLAGS_DO_NOT_AUTO_START_AT_CONSTRUCTION,
+ NULL,
+ name,
+ object_path,
+ interface,
+ data->cancellable,
+ on_nautilus_dbus_proxy_ready,
+ data);
+}
+
+gboolean nautilus_dbus_launcher_is_available (NautilusDBusLauncher *self,
+ NautilusDBusLauncherApp app)
+{
+ return self->data[app]->error == NULL && self->data[app]->proxy != NULL;
+}
+
+NautilusDBusLauncher *
+nautilus_dbus_launcher_get (void)
+{
+ return launcher;
+}
+
+NautilusDBusLauncher *
+nautilus_dbus_launcher_new (void)
+{
+ if (launcher != NULL)
+ {
+ return g_object_ref (launcher);
+ }
+ launcher = g_object_new (NAUTILUS_TYPE_DBUS_LAUNCHER, NULL);
+ g_object_add_weak_pointer (G_OBJECT (launcher), (gpointer) & launcher);
+
+ return launcher;
+}
+
+static void
+nautilus_dbus_launcher_finalize (GObject *object)
+{
+ NautilusDBusLauncher *self = NAUTILUS_DBUS_LAUNCHER (object);
+
+ for (gint i = 1; i <= self->last_app_initialized; i++)
+ {
+ g_clear_object (&self->data[i]->proxy);
+ g_free (self->data[i]->error);
+ g_cancellable_cancel (self->data[i]->cancellable);
+ g_clear_object (&self->data[i]->cancellable);
+ g_free (self->data[i]);
+ }
+
+ G_OBJECT_CLASS (nautilus_dbus_launcher_parent_class)->finalize (object);
+}
+
+static void
+nautilus_dbus_launcher_class_init (NautilusDBusLauncherClass *klass)
+{
+ GObjectClass *object_class;
+
+ object_class = G_OBJECT_CLASS (klass);
+
+ object_class->finalize = nautilus_dbus_launcher_finalize;
+}
+
+static void
+nautilus_dbus_launcher_data_init (NautilusDBusLauncher *self,
+ NautilusDBusLauncherApp app,
+ gboolean ping_on_creation)
+{
+ NautilusDBusLauncherData *data;
+ g_assert_true (app == self->last_app_initialized + 1);
+
+ data = g_new0 (NautilusDBusLauncherData, 1);
+ data->proxy = NULL;
+ data->error = NULL;
+ data->ping_on_creation = ping_on_creation;
+ data->cancellable = g_cancellable_new ();
+
+ self->data[app] = data;
+ self->last_app_initialized = app;
+}
+
+static void
+nautilus_dbus_launcher_init (NautilusDBusLauncher *self)
+{
+ nautilus_dbus_launcher_data_init (self, NAUTILUS_DBUS_LAUNCHER_SETTINGS, FALSE);
+ nautilus_dbus_launcher_data_init (self, NAUTILUS_DBUS_LAUNCHER_DISKS, TRUE);
+ nautilus_dbus_launcher_data_init (self, NAUTILUS_DBUS_LAUNCHER_CONSOLE, TRUE);
+
+ nautilus_dbus_launcher_create_proxy (self->data[NAUTILUS_DBUS_LAUNCHER_SETTINGS],
+ "org.gnome.Settings", "/org/gnome/Settings",
+ "org.gtk.Actions");
+
+ nautilus_dbus_launcher_create_proxy (self->data[NAUTILUS_DBUS_LAUNCHER_DISKS],
+ "org.gnome.DiskUtility", "/org/gnome/DiskUtility",
+ "org.gtk.Application");
+
+ nautilus_dbus_launcher_create_proxy (self->data[NAUTILUS_DBUS_LAUNCHER_CONSOLE],
+ "org.gnome.Console", "/org/gnome/Console",
+ "org.freedesktop.Application");
+}
diff --git a/src/nautilus-dbus-launcher.h b/src/nautilus-dbus-launcher.h
new file mode 100644
index 0000000..0e55337
--- /dev/null
+++ b/src/nautilus-dbus-launcher.h
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2022 Corey Berla <corey@berla.me>
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#pragma once
+
+#include <glib.h>
+#include <gtk/gtk.h>
+
+#include "nautilus-file.h"
+
+#define NAUTILUS_TYPE_DBUS_LAUNCHER (nautilus_dbus_launcher_get_type())
+
+G_DECLARE_FINAL_TYPE (NautilusDBusLauncher, nautilus_dbus_launcher, NAUTILUS, DBUS_LAUNCHER, GObject)
+
+
+typedef enum {
+ NAUTILUS_DBUS_APP_0,
+ NAUTILUS_DBUS_LAUNCHER_SETTINGS,
+ NAUTILUS_DBUS_LAUNCHER_DISKS,
+ NAUTILUS_DBUS_LAUNCHER_CONSOLE,
+ NAUTILUS_DBUS_LAUNCHER_N_APPS
+} NautilusDBusLauncherApp;
+
+NautilusDBusLauncher * nautilus_dbus_launcher_new (void); //to be called on `NautilusApplication::startup` only
+
+NautilusDBusLauncher * nautilus_dbus_launcher_get (void); //to be called by consumers; doesn't change reference count.
+
+gboolean nautilus_dbus_launcher_is_available (NautilusDBusLauncher *self,
+ NautilusDBusLauncherApp app);
+
+void nautilus_dbus_launcher_call (NautilusDBusLauncher *self,
+ NautilusDBusLauncherApp app,
+ const gchar *method_name,
+ GVariant *parameters,
+ GtkWindow *window);
+
diff --git a/src/nautilus-dbus-manager.c b/src/nautilus-dbus-manager.c
new file mode 100644
index 0000000..b4c13f1
--- /dev/null
+++ b/src/nautilus-dbus-manager.c
@@ -0,0 +1,660 @@
+/*
+ * nautilus-dbus-manager: nautilus DBus interface
+ *
+ * Copyright (C) 2010, Red Hat, Inc.
+ *
+ * Nautilus is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * Nautilus is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ *
+ * Author: Cosimo Cecchi <cosimoc@redhat.com>
+ *
+ */
+
+#include <config.h>
+
+#include "nautilus-dbus-manager.h"
+#include "nautilus-generated.h"
+#include "nautilus-generated2.h"
+
+#include "nautilus-file-operations.h"
+#include "nautilus-file-undo-manager.h"
+#include "nautilus-file.h"
+
+#define DEBUG_FLAG NAUTILUS_DEBUG_DBUS
+#include "nautilus-debug.h"
+
+struct _NautilusDBusManager
+{
+ GObject parent;
+
+ NautilusDBusFileOperations *file_operations;
+ NautilusDBusFileOperations2 *file_operations2;
+};
+
+G_DEFINE_TYPE (NautilusDBusManager, nautilus_dbus_manager, G_TYPE_OBJECT);
+
+static void
+nautilus_dbus_manager_dispose (GObject *object)
+{
+ NautilusDBusManager *self = (NautilusDBusManager *) object;
+
+ if (self->file_operations)
+ {
+ g_object_unref (self->file_operations);
+ self->file_operations = NULL;
+ }
+
+ if (self->file_operations2)
+ {
+ g_object_unref (self->file_operations2);
+ self->file_operations2 = NULL;
+ }
+
+ G_OBJECT_CLASS (nautilus_dbus_manager_parent_class)->dispose (object);
+}
+
+static void
+undo_redo_on_finished (gpointer user_data)
+{
+ NautilusFileUndoManager *undo_manager = NULL;
+ int *handler_id = (int *) user_data;
+
+ undo_manager = nautilus_file_undo_manager_get ();
+ g_signal_handler_disconnect (undo_manager, *handler_id);
+ g_application_release (g_application_get_default ());
+ g_free (handler_id);
+}
+
+static void
+handle_redo_internal (NautilusFileOperationsDBusData *dbus_data)
+{
+ NautilusFileUndoManager *undo_manager = NULL;
+ gint *handler_id = g_new0 (int, 1);
+
+ g_application_hold (g_application_get_default ());
+
+ undo_manager = nautilus_file_undo_manager_get ();
+ *handler_id = g_signal_connect_swapped (undo_manager, "undo-changed",
+ G_CALLBACK (undo_redo_on_finished),
+ handler_id);
+ nautilus_file_undo_manager_redo (NULL, dbus_data);
+}
+
+static gboolean
+handle_redo (NautilusDBusFileOperations *object,
+ GDBusMethodInvocation *invocation)
+{
+ handle_redo_internal (NULL);
+
+ nautilus_dbus_file_operations_complete_redo (object, invocation);
+ return TRUE; /* invocation was handled */
+}
+
+static gboolean
+handle_redo2 (NautilusDBusFileOperations2 *object,
+ GDBusMethodInvocation *invocation,
+ GVariant *platform_data)
+{
+ g_autoptr (NautilusFileOperationsDBusData) dbus_data = NULL;
+
+ dbus_data = nautilus_file_operations_dbus_data_new (platform_data);
+
+ handle_redo_internal (dbus_data);
+
+ nautilus_dbus_file_operations2_complete_redo (object, invocation);
+ return TRUE; /* invocation was handled */
+}
+
+static void
+handle_undo_internal (NautilusFileOperationsDBusData *dbus_data)
+{
+ NautilusFileUndoManager *undo_manager = NULL;
+ gint *handler_id = g_new0 (int, 1);
+
+ g_application_hold (g_application_get_default ());
+
+ undo_manager = nautilus_file_undo_manager_get ();
+ *handler_id = g_signal_connect_swapped (undo_manager, "undo-changed",
+ G_CALLBACK (undo_redo_on_finished),
+ handler_id);
+ nautilus_file_undo_manager_undo (NULL, dbus_data);
+}
+
+static gboolean
+handle_undo (NautilusDBusFileOperations *object,
+ GDBusMethodInvocation *invocation)
+{
+ handle_undo_internal (NULL);
+
+ nautilus_dbus_file_operations_complete_undo (object, invocation);
+ return TRUE; /* invocation was handled */
+}
+
+static gboolean
+handle_undo2 (NautilusDBusFileOperations2 *object,
+ GDBusMethodInvocation *invocation,
+ GVariant *platform_data)
+{
+ g_autoptr (NautilusFileOperationsDBusData) dbus_data = NULL;
+
+ dbus_data = nautilus_file_operations_dbus_data_new (platform_data);
+
+ handle_undo_internal (dbus_data);
+
+ nautilus_dbus_file_operations2_complete_undo (object, invocation);
+ return TRUE; /* invocation was handled */
+}
+
+static void
+create_folder_on_finished (GFile *new_file,
+ gboolean success,
+ gpointer callback_data)
+{
+ g_application_release (g_application_get_default ());
+}
+
+static void
+handle_create_folder_internal (const gchar *parent_uri,
+ const gchar *new_folder_name,
+ NautilusFileOperationsDBusData *dbus_data)
+{
+ g_application_hold (g_application_get_default ());
+ nautilus_file_operations_new_folder (NULL, dbus_data,
+ parent_uri, new_folder_name,
+ create_folder_on_finished, NULL);
+}
+
+static gboolean
+handle_create_folder (NautilusDBusFileOperations *object,
+ GDBusMethodInvocation *invocation,
+ const gchar *uri)
+{
+ g_autoptr (GFile) file = NULL;
+ g_autoptr (GFile) parent_file = NULL;
+ g_autofree gchar *basename = NULL;
+ g_autofree gchar *parent_file_uri = NULL;
+
+ file = g_file_new_for_uri (uri);
+ basename = g_file_get_basename (file);
+ parent_file = g_file_get_parent (file);
+ if (parent_file == NULL || basename == NULL)
+ {
+ g_dbus_method_invocation_return_error (invocation, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT, "Invalid uri: %s", uri);
+ return TRUE;
+ }
+ parent_file_uri = g_file_get_uri (parent_file);
+
+ handle_create_folder_internal (parent_file_uri, basename, NULL);
+
+ nautilus_dbus_file_operations_complete_create_folder (object, invocation);
+ return TRUE; /* invocation was handled */
+}
+
+static gboolean
+handle_create_folder2 (NautilusDBusFileOperations2 *object,
+ GDBusMethodInvocation *invocation,
+ const gchar *parent_uri,
+ const gchar *new_folder_name,
+ GVariant *platform_data)
+{
+ g_autoptr (NautilusFileOperationsDBusData) dbus_data = NULL;
+
+ dbus_data = nautilus_file_operations_dbus_data_new (platform_data);
+
+ handle_create_folder_internal (parent_uri, new_folder_name, dbus_data);
+
+ nautilus_dbus_file_operations2_complete_create_folder (object, invocation);
+ return TRUE; /* invocation was handled */
+}
+
+static void
+copy_move_on_finished (GHashTable *debutting_uris,
+ gboolean success,
+ gpointer callback_data)
+{
+ g_application_release (g_application_get_default ());
+}
+
+static void
+handle_copy_uris_internal (const char **sources,
+ const char *destination,
+ NautilusFileOperationsDBusData *dbus_data)
+{
+ GList *source_files = NULL;
+ gint idx;
+
+ for (idx = 0; sources[idx] != NULL; idx++)
+ {
+ source_files = g_list_prepend (source_files, g_strdup (sources[idx]));
+ }
+
+ g_application_hold (g_application_get_default ());
+ nautilus_file_operations_copy_move (source_files, destination,
+ GDK_ACTION_COPY, NULL, dbus_data,
+ copy_move_on_finished, NULL);
+
+ g_list_free_full (source_files, g_free);
+}
+
+static gboolean
+handle_copy_uris (NautilusDBusFileOperations *object,
+ GDBusMethodInvocation *invocation,
+ const gchar **sources,
+ const gchar *destination)
+{
+ handle_copy_uris_internal (sources, destination, NULL);
+
+ nautilus_dbus_file_operations_complete_copy_uris (object, invocation);
+ return TRUE; /* invocation was handled */
+}
+
+static gboolean
+handle_copy_uris2 (NautilusDBusFileOperations2 *object,
+ GDBusMethodInvocation *invocation,
+ const gchar **sources,
+ const gchar *destination,
+ GVariant *platform_data)
+{
+ g_autoptr (NautilusFileOperationsDBusData) dbus_data = NULL;
+
+ dbus_data = nautilus_file_operations_dbus_data_new (platform_data);
+
+ handle_copy_uris_internal (sources, destination, dbus_data);
+
+ nautilus_dbus_file_operations2_complete_copy_uris (object, invocation);
+ return TRUE; /* invocation was handled */
+}
+
+static void
+handle_move_uris_internal (const char **sources,
+ const char *destination,
+ NautilusFileOperationsDBusData *dbus_data)
+{
+ GList *source_files = NULL;
+ gint idx;
+
+ for (idx = 0; sources[idx] != NULL; idx++)
+ {
+ source_files = g_list_prepend (source_files, g_strdup (sources[idx]));
+ }
+
+ g_application_hold (g_application_get_default ());
+ nautilus_file_operations_copy_move (source_files, destination,
+ GDK_ACTION_MOVE, NULL, dbus_data,
+ copy_move_on_finished, NULL);
+
+ g_list_free_full (source_files, g_free);
+}
+
+static gboolean
+handle_move_uris (NautilusDBusFileOperations *object,
+ GDBusMethodInvocation *invocation,
+ const gchar **sources,
+ const gchar *destination)
+{
+ handle_move_uris_internal (sources, destination, NULL);
+
+ nautilus_dbus_file_operations_complete_copy_uris (object, invocation);
+ return TRUE; /* invocation was handled */
+}
+
+static gboolean
+handle_move_uris2 (NautilusDBusFileOperations2 *object,
+ GDBusMethodInvocation *invocation,
+ const gchar **sources,
+ const gchar *destination,
+ GVariant *platform_data)
+{
+ g_autoptr (NautilusFileOperationsDBusData) dbus_data = NULL;
+
+ dbus_data = nautilus_file_operations_dbus_data_new (platform_data);
+
+ handle_move_uris_internal (sources, destination, dbus_data);
+
+ nautilus_dbus_file_operations2_complete_copy_uris (object, invocation);
+ return TRUE; /* invocation was handled */
+}
+
+/* FIXME: Needs a callback for maintaining alive the application */
+static void
+handle_empty_trash_internal (gboolean ask_confirmation,
+ NautilusFileOperationsDBusData *dbus_data)
+{
+ nautilus_file_operations_empty_trash (NULL, ask_confirmation, dbus_data);
+}
+
+static gboolean
+handle_empty_trash (NautilusDBusFileOperations *object,
+ GDBusMethodInvocation *invocation)
+{
+ handle_empty_trash_internal (TRUE, NULL);
+
+ nautilus_dbus_file_operations_complete_empty_trash (object, invocation);
+ return TRUE; /* invocation was handled */
+}
+
+static gboolean
+handle_empty_trash2 (NautilusDBusFileOperations2 *object,
+ GDBusMethodInvocation *invocation,
+ gboolean ask_confirmation,
+ GVariant *platform_data)
+{
+ g_autoptr (NautilusFileOperationsDBusData) dbus_data = NULL;
+
+ dbus_data = nautilus_file_operations_dbus_data_new (platform_data);
+
+ handle_empty_trash_internal (ask_confirmation, dbus_data);
+
+ nautilus_dbus_file_operations2_complete_empty_trash (object, invocation);
+ return TRUE; /* invocation was handled */
+}
+
+static void
+trash_on_finished (GHashTable *debutting_uris,
+ gboolean user_cancel,
+ gpointer callback_data)
+{
+ g_application_release (g_application_get_default ());
+}
+
+static void
+handle_trash_uris_internal (const char **uris,
+ NautilusFileOperationsDBusData *dbus_data)
+{
+ g_autolist (GFile) source_files = NULL;
+ gint idx;
+
+ for (idx = 0; uris[idx] != NULL; idx++)
+ {
+ source_files = g_list_prepend (source_files,
+ g_file_new_for_uri (uris[idx]));
+ }
+
+ g_application_hold (g_application_get_default ());
+ nautilus_file_operations_trash_or_delete_async (source_files, NULL,
+ dbus_data,
+ trash_on_finished, NULL);
+}
+
+static gboolean
+handle_trash_files (NautilusDBusFileOperations *object,
+ GDBusMethodInvocation *invocation,
+ const gchar **sources)
+{
+ handle_trash_uris_internal (sources, NULL);
+
+ nautilus_dbus_file_operations_complete_trash_files (object, invocation);
+ return TRUE; /* invocation was handled */
+}
+
+static gboolean
+handle_trash_uris2 (NautilusDBusFileOperations2 *object,
+ GDBusMethodInvocation *invocation,
+ const gchar **uris,
+ GVariant *platform_data)
+{
+ g_autoptr (NautilusFileOperationsDBusData) dbus_data = NULL;
+
+ dbus_data = nautilus_file_operations_dbus_data_new (platform_data);
+
+ handle_trash_uris_internal (uris, dbus_data);
+
+ nautilus_dbus_file_operations2_complete_trash_uris (object, invocation);
+ return TRUE; /* invocation was handled */
+}
+
+static void
+delete_on_finished (GHashTable *debutting_uris,
+ gboolean user_cancel,
+ gpointer callback_data)
+{
+ g_application_release (g_application_get_default ());
+}
+
+static void
+handle_delete_uris_internal (const char **uris,
+ NautilusFileOperationsDBusData *dbus_data)
+{
+ g_autolist (GFile) source_files = NULL;
+ gint idx;
+
+ for (idx = 0; uris[idx] != NULL; idx++)
+ {
+ source_files = g_list_prepend (source_files,
+ g_file_new_for_uri (uris[idx]));
+ }
+
+ g_application_hold (g_application_get_default ());
+ nautilus_file_operations_delete_async (source_files, NULL,
+ dbus_data,
+ delete_on_finished, NULL);
+}
+
+static gboolean
+handle_delete_uris2 (NautilusDBusFileOperations2 *object,
+ GDBusMethodInvocation *invocation,
+ const gchar **uris,
+ GVariant *platform_data)
+{
+ g_autoptr (NautilusFileOperationsDBusData) dbus_data = NULL;
+
+ dbus_data = nautilus_file_operations_dbus_data_new (platform_data);
+
+ handle_delete_uris_internal (uris, dbus_data);
+
+ nautilus_dbus_file_operations2_complete_delete_uris (object, invocation);
+ return TRUE; /* invocation was handled */
+}
+
+static void
+rename_file_on_finished (NautilusFile *file,
+ GFile *result_location,
+ GError *error,
+ gpointer callback_data)
+{
+ g_application_release (g_application_get_default ());
+}
+
+static void
+handle_rename_uri_internal (const gchar *uri,
+ const gchar *new_name,
+ NautilusFileOperationsDBusData *dbus_data)
+{
+ NautilusFile *file = NULL;
+
+ file = nautilus_file_get_by_uri (uri);
+
+ g_application_hold (g_application_get_default ());
+ nautilus_file_rename (file, new_name,
+ rename_file_on_finished, NULL);
+}
+
+static gboolean
+handle_rename_file (NautilusDBusFileOperations *object,
+ GDBusMethodInvocation *invocation,
+ const gchar *uri,
+ const gchar *new_name)
+{
+ handle_rename_uri_internal (uri, new_name, NULL);
+
+ nautilus_dbus_file_operations_complete_rename_file (object, invocation);
+
+ return TRUE; /* invocation was handled */
+}
+
+static gboolean
+handle_rename_uri2 (NautilusDBusFileOperations2 *object,
+ GDBusMethodInvocation *invocation,
+ const gchar *uri,
+ const gchar *new_name,
+ GVariant *platform_data)
+{
+ g_autoptr (NautilusFileOperationsDBusData) dbus_data = NULL;
+
+ dbus_data = nautilus_file_operations_dbus_data_new (platform_data);
+
+ handle_rename_uri_internal (uri, new_name, dbus_data);
+
+ nautilus_dbus_file_operations2_complete_rename_uri (object, invocation);
+
+ return TRUE; /* invocation was handled */
+}
+
+static void
+undo_manager_changed (NautilusDBusManager *self)
+{
+ NautilusFileUndoManagerState undo_state;
+
+ undo_state = nautilus_file_undo_manager_get_state ();
+ nautilus_dbus_file_operations_set_undo_status (self->file_operations,
+ undo_state);
+ nautilus_dbus_file_operations2_set_undo_status (self->file_operations2,
+ undo_state);
+}
+
+static void
+nautilus_dbus_manager_init (NautilusDBusManager *self)
+{
+ G_GNUC_BEGIN_IGNORE_DEPRECATIONS
+ self->file_operations = nautilus_dbus_file_operations_skeleton_new ();
+ G_GNUC_END_IGNORE_DEPRECATIONS
+
+ self->file_operations2 = nautilus_dbus_file_operations2_skeleton_new ();
+
+ g_signal_connect (self->file_operations,
+ "handle-copy-uris",
+ G_CALLBACK (handle_copy_uris),
+ self);
+ g_signal_connect (self->file_operations2,
+ "handle-copy-uris",
+ G_CALLBACK (handle_copy_uris2),
+ self);
+ g_signal_connect (self->file_operations,
+ "handle-move-uris",
+ G_CALLBACK (handle_move_uris),
+ self);
+ g_signal_connect (self->file_operations2,
+ "handle-move-uris",
+ G_CALLBACK (handle_move_uris2),
+ self);
+ g_signal_connect (self->file_operations,
+ "handle-empty-trash",
+ G_CALLBACK (handle_empty_trash),
+ self);
+ g_signal_connect (self->file_operations2,
+ "handle-empty-trash",
+ G_CALLBACK (handle_empty_trash2),
+ self);
+ g_signal_connect (self->file_operations,
+ "handle-trash-files",
+ G_CALLBACK (handle_trash_files),
+ self);
+ g_signal_connect (self->file_operations2,
+ "handle-trash-uris",
+ G_CALLBACK (handle_trash_uris2),
+ self);
+ g_signal_connect (self->file_operations2,
+ "handle-delete-uris",
+ G_CALLBACK (handle_delete_uris2),
+ self);
+ g_signal_connect (self->file_operations,
+ "handle-create-folder",
+ G_CALLBACK (handle_create_folder),
+ self);
+ g_signal_connect (self->file_operations2,
+ "handle-create-folder",
+ G_CALLBACK (handle_create_folder2),
+ self);
+ g_signal_connect (self->file_operations,
+ "handle-rename-file",
+ G_CALLBACK (handle_rename_file),
+ self);
+ g_signal_connect (self->file_operations2,
+ "handle-rename-uri",
+ G_CALLBACK (handle_rename_uri2),
+ self);
+ g_signal_connect (self->file_operations,
+ "handle-undo",
+ G_CALLBACK (handle_undo),
+ self);
+ g_signal_connect (self->file_operations2,
+ "handle-undo",
+ G_CALLBACK (handle_undo2),
+ self);
+ g_signal_connect (self->file_operations,
+ "handle-redo",
+ G_CALLBACK (handle_redo),
+ self);
+ g_signal_connect (self->file_operations2,
+ "handle-redo",
+ G_CALLBACK (handle_redo2),
+ self);
+}
+
+static void
+nautilus_dbus_manager_class_init (NautilusDBusManagerClass *klass)
+{
+ GObjectClass *oclass = G_OBJECT_CLASS (klass);
+
+ oclass->dispose = nautilus_dbus_manager_dispose;
+}
+
+NautilusDBusManager *
+nautilus_dbus_manager_new (void)
+{
+ return g_object_new (NAUTILUS_TYPE_DBUS_MANAGER, NULL);
+}
+
+gboolean
+nautilus_dbus_manager_register (NautilusDBusManager *self,
+ GDBusConnection *connection,
+ GError **error)
+{
+ gboolean success1;
+ gboolean success2;
+ gboolean succes;
+
+ success1 = g_dbus_interface_skeleton_export (G_DBUS_INTERFACE_SKELETON (self->file_operations),
+ connection,
+ "/org/gnome/Nautilus" PROFILE,
+ error);
+
+ success2 = g_dbus_interface_skeleton_export (G_DBUS_INTERFACE_SKELETON (self->file_operations2),
+ connection,
+ "/org/gnome/Nautilus" PROFILE "/FileOperations2",
+ error);
+
+ succes = success1 && success2;
+
+ if (succes)
+ {
+ g_signal_connect_object (nautilus_file_undo_manager_get (),
+ "undo-changed",
+ G_CALLBACK (undo_manager_changed),
+ self,
+ G_CONNECT_SWAPPED);
+
+ undo_manager_changed (self);
+ }
+
+ return succes;
+}
+
+void
+nautilus_dbus_manager_unregister (NautilusDBusManager *self)
+{
+ g_dbus_interface_skeleton_unexport (G_DBUS_INTERFACE_SKELETON (self->file_operations));
+ g_dbus_interface_skeleton_unexport (G_DBUS_INTERFACE_SKELETON (self->file_operations2));
+
+ g_signal_handlers_disconnect_by_data (nautilus_file_undo_manager_get (), self);
+}
diff --git a/src/nautilus-dbus-manager.h b/src/nautilus-dbus-manager.h
new file mode 100644
index 0000000..7ff4f8e
--- /dev/null
+++ b/src/nautilus-dbus-manager.h
@@ -0,0 +1,36 @@
+/*
+ * nautilus-dbus-manager: nautilus DBus interface
+ *
+ * Copyright (C) 2010, Red Hat, Inc.
+ *
+ * Nautilus is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * Nautilus is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ *
+ * Author: Cosimo Cecchi <cosimoc@redhat.com>
+ *
+ */
+
+#pragma once
+
+#include <glib-object.h>
+#include <gio/gio.h>
+
+#define NAUTILUS_TYPE_DBUS_MANAGER (nautilus_dbus_manager_get_type())
+G_DECLARE_FINAL_TYPE (NautilusDBusManager, nautilus_dbus_manager, NAUTILUS, DBUS_MANAGER, GObject)
+
+NautilusDBusManager * nautilus_dbus_manager_new (void);
+
+gboolean nautilus_dbus_manager_register (NautilusDBusManager *self,
+ GDBusConnection *connection,
+ GError **error);
+void nautilus_dbus_manager_unregister (NautilusDBusManager *self);
diff --git a/src/nautilus-debug.c b/src/nautilus-debug.c
new file mode 100644
index 0000000..bbf7656
--- /dev/null
+++ b/src/nautilus-debug.c
@@ -0,0 +1,180 @@
+/*
+ * nautilus-debug: debug loggers for nautilus
+ *
+ * Copyright (C) 2007 Collabora Ltd.
+ * Copyright (C) 2007 Nokia Corporation
+ * Copyright (C) 2010 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ *
+ * Based on Empathy's empathy-debug.
+ */
+
+#include "config.h"
+
+#include <stdarg.h>
+#include <glib.h>
+
+#include "nautilus-debug.h"
+
+#include "nautilus-file.h"
+
+static DebugFlags flags = 0;
+static gboolean initialized = FALSE;
+
+static GDebugKey keys[] =
+{
+ { "Application", NAUTILUS_DEBUG_APPLICATION },
+ { "AsyncJobs", NAUTILUS_DEBUG_ASYNC_JOBS },
+ { "Bookmarks", NAUTILUS_DEBUG_BOOKMARKS },
+ { "DBus", NAUTILUS_DEBUG_DBUS },
+ { "DirectoryView", NAUTILUS_DEBUG_DIRECTORY_VIEW },
+ { "File", NAUTILUS_DEBUG_FILE },
+ { "IconView", NAUTILUS_DEBUG_GRID_VIEW },
+ { "ListView", NAUTILUS_DEBUG_LIST_VIEW },
+ { "Mime", NAUTILUS_DEBUG_MIME },
+ { "Places", NAUTILUS_DEBUG_PLACES },
+ { "Previewer", NAUTILUS_DEBUG_PREVIEWER },
+ { "Search", NAUTILUS_DEBUG_SEARCH },
+ { "SearchHit", NAUTILUS_DEBUG_SEARCH_HIT },
+ { "Smclient", NAUTILUS_DEBUG_SMCLIENT },
+ { "Window", NAUTILUS_DEBUG_WINDOW },
+ { "Undo", NAUTILUS_DEBUG_UNDO },
+ { "Thumbnails", NAUTILUS_DEBUG_THUMBNAILS },
+ { "TagManager", NAUTILUS_DEBUG_TAG_MANAGER },
+ { 0, }
+};
+
+static void
+nautilus_debug_set_flags_from_env (void)
+{
+ guint nkeys;
+ const gchar *flags_string;
+
+ for (nkeys = 0; keys[nkeys].value; nkeys++)
+ {
+ }
+
+ flags_string = g_getenv ("NAUTILUS_DEBUG");
+
+ if (flags_string)
+ {
+ nautilus_debug_set_flags (g_parse_debug_string (flags_string, keys, nkeys));
+ }
+
+ initialized = TRUE;
+}
+
+void
+nautilus_debug_set_flags (DebugFlags new_flags)
+{
+ flags |= new_flags;
+ initialized = TRUE;
+}
+
+gboolean
+nautilus_debug_flag_is_set (DebugFlags flag)
+{
+ return flag & flags;
+}
+
+void
+nautilus_debug (DebugFlags flag,
+ const gchar *format,
+ ...)
+{
+ va_list args;
+ va_start (args, format);
+ nautilus_debug_valist (flag, format, args);
+ va_end (args);
+}
+
+__attribute__((__format__ (__printf__, 2, 0)))
+void
+nautilus_debug_valist (DebugFlags flag,
+ const gchar *format,
+ va_list args)
+{
+ if (G_UNLIKELY (!initialized))
+ {
+ nautilus_debug_set_flags_from_env ();
+ }
+
+ if (flag & flags)
+ {
+ g_logv (G_LOG_DOMAIN, G_LOG_LEVEL_DEBUG, format, args);
+ }
+}
+
+__attribute__((__format__ (__printf__, 3, 0)))
+static void
+nautilus_debug_files_valist (DebugFlags flag,
+ GList *files,
+ const gchar *format,
+ va_list args)
+{
+ NautilusFile *file;
+ GList *l;
+ gchar *uri, *msg;
+
+ if (G_UNLIKELY (!initialized))
+ {
+ nautilus_debug_set_flags_from_env ();
+ }
+
+ if (!(flag & flags))
+ {
+ return;
+ }
+
+ msg = g_strdup_vprintf (format, args);
+
+ g_log (G_LOG_DOMAIN, G_LOG_LEVEL_DEBUG, "%s:", msg);
+
+ for (l = files; l != NULL; l = l->next)
+ {
+ file = l->data;
+ uri = nautilus_file_get_uri (file);
+
+ if (nautilus_file_is_gone (file))
+ {
+ gchar *new_uri;
+
+ /* Hack: this will create an invalid URI, but it's for
+ * display purposes only.
+ */
+ new_uri = g_strconcat (uri ? uri : "", " (gone)", NULL);
+ g_free (uri);
+ uri = new_uri;
+ }
+
+ g_log (G_LOG_DOMAIN, G_LOG_LEVEL_DEBUG, " %s", uri);
+ g_free (uri);
+ }
+
+ g_free (msg);
+}
+
+void
+nautilus_debug_files (DebugFlags flag,
+ GList *files,
+ const gchar *format,
+ ...)
+{
+ va_list args;
+
+ va_start (args, format);
+ nautilus_debug_files_valist (flag, files, format, args);
+ va_end (args);
+}
diff --git a/src/nautilus-debug.h b/src/nautilus-debug.h
new file mode 100644
index 0000000..9322a8d
--- /dev/null
+++ b/src/nautilus-debug.h
@@ -0,0 +1,78 @@
+/*
+ * nautilus-debug: debug loggers for nautilus
+ *
+ * Copyright (C) 2007 Collabora Ltd.
+ * Copyright (C) 2007 Nokia Corporation
+ * Copyright (C) 2010 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ *
+ * Based on Empathy's empathy-debug.
+ */
+
+#pragma once
+
+#include <config.h>
+#include <glib.h>
+
+G_BEGIN_DECLS
+
+typedef enum {
+ NAUTILUS_DEBUG_APPLICATION = 1 << 1,
+ NAUTILUS_DEBUG_ASYNC_JOBS = 1 << 2,
+ NAUTILUS_DEBUG_BOOKMARKS = 1 << 3,
+ NAUTILUS_DEBUG_DBUS = 1 << 4,
+ NAUTILUS_DEBUG_DIRECTORY_VIEW = 1 << 5,
+ NAUTILUS_DEBUG_FILE = 1 << 6,
+ NAUTILUS_DEBUG_GRID_VIEW = 1 << 7,
+ NAUTILUS_DEBUG_LIST_VIEW = 1 << 8,
+ NAUTILUS_DEBUG_MIME = 1 << 9,
+ NAUTILUS_DEBUG_PLACES = 1 << 10,
+ NAUTILUS_DEBUG_PREVIEWER = 1 << 11,
+ NAUTILUS_DEBUG_SMCLIENT = 1 << 12,
+ NAUTILUS_DEBUG_WINDOW = 1 << 13,
+ NAUTILUS_DEBUG_UNDO = 1 << 14,
+ NAUTILUS_DEBUG_SEARCH = 1 << 15,
+ NAUTILUS_DEBUG_SEARCH_HIT = 1 << 16,
+ NAUTILUS_DEBUG_THUMBNAILS = 1 << 17,
+ NAUTILUS_DEBUG_TAG_MANAGER = 1 << 18,
+} DebugFlags;
+
+void nautilus_debug_set_flags (DebugFlags flags);
+gboolean nautilus_debug_flag_is_set (DebugFlags flag);
+
+void nautilus_debug_valist (DebugFlags flag,
+ const gchar *format, va_list args);
+
+void nautilus_debug (DebugFlags flag, const gchar *format, ...)
+ G_GNUC_PRINTF (2, 3);
+
+void nautilus_debug_files (DebugFlags flag, GList *files,
+ const gchar *format, ...) G_GNUC_PRINTF (3, 4);
+
+#ifdef DEBUG_FLAG
+
+#define DEBUG(format, ...) \
+ nautilus_debug (DEBUG_FLAG, "%s: %s: " format, G_STRFUNC, G_STRLOC, \
+ ##__VA_ARGS__)
+
+#define DEBUG_FILES(files, format, ...) \
+ nautilus_debug_files (DEBUG_FLAG, files, "%s:" format, G_STRFUNC, \
+ ##__VA_ARGS__)
+
+#define DEBUGGING nautilus_debug_flag_is_set(DEBUG_FLAG)
+
+#endif /* DEBUG_FLAG */
+
+G_END_DECLS
diff --git a/src/nautilus-directory-async.c b/src/nautilus-directory-async.c
new file mode 100644
index 0000000..3e7f421
--- /dev/null
+++ b/src/nautilus-directory-async.c
@@ -0,0 +1,4755 @@
+/*
+ * nautilus-directory-async.c: Nautilus directory model state machine.
+ *
+ * Copyright (C) 1999, 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/>.
+ *
+ * Author: Darin Adler <darin@bentspoon.com>
+ */
+
+#include <libxml/parser.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#define DEBUG_FLAG NAUTILUS_DEBUG_ASYNC_JOBS
+
+#include "nautilus-debug.h"
+#include "nautilus-directory-notify.h"
+#include "nautilus-directory-private.h"
+#include "nautilus-enums.h"
+#include "nautilus-file-private.h"
+#include "nautilus-file-queue.h"
+#include "nautilus-global-preferences.h"
+#include "nautilus-metadata.h"
+#include "nautilus-profile.h"
+#include "nautilus-signaller.h"
+
+/* turn this on to check if async. job calls are balanced */
+#if 0
+#define DEBUG_ASYNC_JOBS
+#endif
+
+#define DIRECTORY_LOAD_ITEMS_PER_CALLBACK 100
+
+/* Keep async. jobs down to this number for all directories. */
+#define MAX_ASYNC_JOBS 10
+
+struct ThumbnailState
+{
+ NautilusDirectory *directory;
+ GCancellable *cancellable;
+ NautilusFile *file;
+};
+
+struct MountState
+{
+ NautilusDirectory *directory;
+ GCancellable *cancellable;
+ NautilusFile *file;
+};
+
+struct FilesystemInfoState
+{
+ NautilusDirectory *directory;
+ GCancellable *cancellable;
+ NautilusFile *file;
+};
+
+struct DirectoryLoadState
+{
+ NautilusDirectory *directory;
+ GCancellable *cancellable;
+ GFileEnumerator *enumerator;
+ GHashTable *load_mime_list_hash;
+ NautilusFile *load_directory_file;
+ int load_file_count;
+};
+
+struct MimeListState
+{
+ NautilusDirectory *directory;
+ NautilusFile *mime_list_file;
+ GCancellable *cancellable;
+ GFileEnumerator *enumerator;
+ GHashTable *mime_list_hash;
+};
+
+struct GetInfoState
+{
+ NautilusDirectory *directory;
+ GCancellable *cancellable;
+};
+
+struct NewFilesState
+{
+ NautilusDirectory *directory;
+ GCancellable *cancellable;
+ int count;
+};
+
+struct DirectoryCountState
+{
+ NautilusDirectory *directory;
+ NautilusFile *count_file;
+ GCancellable *cancellable;
+ GFileEnumerator *enumerator;
+ int file_count;
+};
+
+struct DeepCountState
+{
+ NautilusDirectory *directory;
+ GCancellable *cancellable;
+ GFileEnumerator *enumerator;
+ GFile *deep_count_location;
+ GList *deep_count_subdirectories;
+ GArray *seen_deep_count_inodes;
+ char *fs_id;
+};
+
+
+
+typedef struct
+{
+ NautilusFile *file; /* Which file, NULL means all. */
+ union
+ {
+ NautilusDirectoryCallback directory;
+ NautilusFileCallback file;
+ } callback;
+ gpointer callback_data;
+ Request request;
+ gboolean active; /* Set to FALSE when the callback is triggered and
+ * scheduled to be called at idle, its still kept
+ * in the list so we can kill it when the file
+ * goes away.
+ */
+} ReadyCallback;
+
+typedef struct
+{
+ NautilusFile *file; /* Which file, NULL means all. */
+ gboolean monitor_hidden_files; /* defines whether "all" includes hidden files */
+ gconstpointer client;
+ Request request;
+} Monitor;
+
+typedef struct
+{
+ NautilusDirectory *directory;
+ NautilusInfoProvider *provider;
+ NautilusOperationHandle *handle;
+ NautilusOperationResult result;
+} InfoProviderResponse;
+
+typedef gboolean (*RequestCheck) (Request);
+typedef gboolean (*FileCheck) (NautilusFile *);
+
+/* Current number of async. jobs. */
+static int async_job_count;
+static GHashTable *waiting_directories;
+#ifdef DEBUG_ASYNC_JOBS
+static GHashTable *async_jobs;
+#endif
+
+/* Forward declarations for functions that need them. */
+static void deep_count_load (DeepCountState *state,
+ GFile *location);
+static gboolean request_is_satisfied (NautilusDirectory *directory,
+ NautilusFile *file,
+ Request request);
+static void cancel_loading_attributes (NautilusDirectory *directory,
+ NautilusFileAttributes file_attributes);
+static void add_all_files_to_work_queue (NautilusDirectory *directory);
+static void move_file_to_low_priority_queue (NautilusDirectory *directory,
+ NautilusFile *file);
+static void move_file_to_extension_queue (NautilusDirectory *directory,
+ NautilusFile *file);
+static void nautilus_directory_invalidate_file_attributes (NautilusDirectory *directory,
+ NautilusFileAttributes file_attributes);
+
+/* Some helpers for case-insensitive strings.
+ * Move to nautilus-glib-extensions?
+ */
+
+static gboolean
+istr_equal (gconstpointer v,
+ gconstpointer v2)
+{
+ return g_ascii_strcasecmp (v, v2) == 0;
+}
+
+static guint
+istr_hash (gconstpointer key)
+{
+ const char *p;
+ guint h;
+
+ h = 0;
+ for (p = key; *p != '\0'; p++)
+ {
+ h = (h << 5) - h + g_ascii_tolower (*p);
+ }
+
+ return h;
+}
+
+static GHashTable *
+istr_set_new (void)
+{
+ return g_hash_table_new_full (istr_hash, istr_equal, g_free, NULL);
+}
+
+static void
+istr_set_insert (GHashTable *table,
+ const char *istr)
+{
+ char *key;
+
+ key = g_strdup (istr);
+ g_hash_table_replace (table, key, key);
+}
+
+static void
+add_istr_to_list (gpointer key,
+ gpointer value,
+ gpointer callback_data)
+{
+ GList **list;
+
+ list = callback_data;
+ *list = g_list_prepend (*list, g_strdup (key));
+}
+
+static GList *
+istr_set_get_as_list (GHashTable *table)
+{
+ GList *list;
+
+ list = NULL;
+ g_hash_table_foreach (table, add_istr_to_list, &list);
+ return list;
+}
+
+static void
+istr_set_destroy (GHashTable *table)
+{
+ g_hash_table_destroy (table);
+}
+
+static void
+request_counter_add_request (RequestCounter counter,
+ Request request)
+{
+ guint i;
+
+ for (i = 0; i < REQUEST_TYPE_LAST; i++)
+ {
+ if (REQUEST_WANTS_TYPE (request, i))
+ {
+ counter[i]++;
+ }
+ }
+}
+
+static void
+request_counter_remove_request (RequestCounter counter,
+ Request request)
+{
+ guint i;
+
+ for (i = 0; i < REQUEST_TYPE_LAST; i++)
+ {
+ if (REQUEST_WANTS_TYPE (request, i))
+ {
+ counter[i]--;
+ }
+ }
+}
+
+#if 0
+static void
+nautilus_directory_verify_request_counts (NautilusDirectory *directory)
+{
+ GList *l;
+ RequestCounter counters;
+ int i;
+ gboolean fail;
+ GHashTableIter monitor_iter;
+ gpointer value;
+
+ fail = FALSE;
+ for (i = 0; i < REQUEST_TYPE_LAST; i++)
+ {
+ counters[i] = 0;
+ }
+ g_hash_table_iter_init (&monitor_iter, directory->details->monitor_table);
+ while (g_hash_table_iter_next (&monitor_iter, NULL, &value))
+ {
+ for (l = value; l; l = l->next)
+ {
+ Monitor *monitor = l->data;
+ request_counter_add_request (counters, monitor->request);
+ }
+ }
+ for (i = 0; i < REQUEST_TYPE_LAST; i++)
+ {
+ if (counters[i] != directory->details->monitor_counters[i])
+ {
+ g_warning ("monitor counter for %i is wrong, expecting %d but found %d",
+ i, counters[i], directory->details->monitor_counters[i]);
+ fail = TRUE;
+ }
+ }
+ for (i = 0; i < REQUEST_TYPE_LAST; i++)
+ {
+ counters[i] = 0;
+ }
+ for (l = directory->details->call_when_ready_list; l != NULL; l = l->next)
+ {
+ ReadyCallback *callback = l->data;
+ request_counter_add_request (counters, callback->request);
+ }
+ for (i = 0; i < REQUEST_TYPE_LAST; i++)
+ {
+ if (counters[i] != directory->details->call_when_ready_counters[i])
+ {
+ g_warning ("call when ready counter for %i is wrong, expecting %d but found %d",
+ i, counters[i], directory->details->call_when_ready_counters[i]);
+ fail = TRUE;
+ }
+ }
+ g_assert (!fail);
+}
+#endif
+
+/* Start a job. This is really just a way of limiting the number of
+ * async. requests that we issue at any given time. Without this, the
+ * number of requests is unbounded.
+ */
+static gboolean
+async_job_start (NautilusDirectory *directory,
+ const char *job)
+{
+#ifdef DEBUG_ASYNC_JOBS
+ char *key;
+#endif
+
+ DEBUG ("starting %s in %p", job, directory->details->location);
+
+ g_assert (async_job_count >= 0);
+ g_assert (async_job_count <= MAX_ASYNC_JOBS);
+
+ if (async_job_count >= MAX_ASYNC_JOBS)
+ {
+ if (waiting_directories == NULL)
+ {
+ waiting_directories = g_hash_table_new (NULL, NULL);
+ }
+
+ g_hash_table_insert (waiting_directories,
+ directory,
+ directory);
+
+ return FALSE;
+ }
+
+#ifdef DEBUG_ASYNC_JOBS
+ {
+ char *uri;
+ if (async_jobs == NULL)
+ {
+ async_jobs = g_hash_table_new (g_str_hash, g_str_equal);
+ }
+ uri = nautilus_directory_get_uri (directory);
+ key = g_strconcat (uri, ": ", job, NULL);
+ if (g_hash_table_lookup (async_jobs, key) != NULL)
+ {
+ g_warning ("same job twice: %s in %s",
+ job, uri);
+ }
+ g_free (uri);
+ g_hash_table_insert (async_jobs, key, directory);
+ }
+#endif
+
+ async_job_count += 1;
+ return TRUE;
+}
+
+/* End a job. */
+static void
+async_job_end (NautilusDirectory *directory,
+ const char *job)
+{
+#ifdef DEBUG_ASYNC_JOBS
+ char *key;
+ gpointer table_key, value;
+#endif
+
+ DEBUG ("stopping %s in %p", job, directory->details->location);
+
+ g_assert (async_job_count > 0);
+
+#ifdef DEBUG_ASYNC_JOBS
+ {
+ char *uri;
+ uri = nautilus_directory_get_uri (directory);
+ g_assert (async_jobs != NULL);
+ key = g_strconcat (uri, ": ", job, NULL);
+ if (!g_hash_table_lookup_extended (async_jobs, key, &table_key, &value))
+ {
+ g_warning ("ending job we didn't start: %s in %s",
+ job, uri);
+ }
+ else
+ {
+ g_hash_table_remove (async_jobs, key);
+ g_free (table_key);
+ }
+ g_free (uri);
+ g_free (key);
+ }
+#endif
+
+ async_job_count -= 1;
+}
+
+/* Helper to get one value from a hash table. */
+static void
+get_one_value_callback (gpointer key,
+ gpointer value,
+ gpointer callback_data)
+{
+ gpointer *returned_value;
+
+ returned_value = callback_data;
+ *returned_value = value;
+}
+
+/* return a single value from a hash table. */
+static gpointer
+get_one_value (GHashTable *table)
+{
+ gpointer value;
+
+ value = NULL;
+ if (table != NULL)
+ {
+ g_hash_table_foreach (table, get_one_value_callback, &value);
+ }
+ return value;
+}
+
+/* Wake up directories that are "blocked" as long as there are job
+ * slots available.
+ */
+static void
+async_job_wake_up (void)
+{
+ static gboolean already_waking_up = FALSE;
+ gpointer value;
+
+ g_assert (async_job_count >= 0);
+ g_assert (async_job_count <= MAX_ASYNC_JOBS);
+
+ if (already_waking_up)
+ {
+ return;
+ }
+
+ already_waking_up = TRUE;
+ while (async_job_count < MAX_ASYNC_JOBS)
+ {
+ value = get_one_value (waiting_directories);
+ if (value == NULL)
+ {
+ break;
+ }
+ g_hash_table_remove (waiting_directories, value);
+ nautilus_directory_async_state_changed
+ (NAUTILUS_DIRECTORY (value));
+ }
+ already_waking_up = FALSE;
+}
+
+static void
+directory_count_cancel (NautilusDirectory *directory)
+{
+ if (directory->details->count_in_progress != NULL)
+ {
+ g_cancellable_cancel (directory->details->count_in_progress->cancellable);
+ directory->details->count_in_progress = NULL;
+ }
+}
+
+static void
+deep_count_cancel (NautilusDirectory *directory)
+{
+ if (directory->details->deep_count_in_progress != NULL)
+ {
+ g_assert (NAUTILUS_IS_FILE (directory->details->deep_count_file));
+
+ g_cancellable_cancel (directory->details->deep_count_in_progress->cancellable);
+
+ directory->details->deep_count_file->details->deep_counts_status = NAUTILUS_REQUEST_NOT_STARTED;
+
+ directory->details->deep_count_in_progress->directory = NULL;
+ directory->details->deep_count_in_progress = NULL;
+ directory->details->deep_count_file = NULL;
+
+ async_job_end (directory, "deep count");
+ }
+}
+
+static void
+mime_list_cancel (NautilusDirectory *directory)
+{
+ if (directory->details->mime_list_in_progress != NULL)
+ {
+ g_cancellable_cancel (directory->details->mime_list_in_progress->cancellable);
+ }
+}
+
+static void
+thumbnail_cancel (NautilusDirectory *directory)
+{
+ if (directory->details->thumbnail_state != NULL)
+ {
+ g_cancellable_cancel (directory->details->thumbnail_state->cancellable);
+ directory->details->thumbnail_state->directory = NULL;
+ directory->details->thumbnail_state = NULL;
+ async_job_end (directory, "thumbnail");
+ }
+}
+
+static void
+mount_cancel (NautilusDirectory *directory)
+{
+ if (directory->details->mount_state != NULL)
+ {
+ g_cancellable_cancel (directory->details->mount_state->cancellable);
+ directory->details->mount_state->directory = NULL;
+ directory->details->mount_state = NULL;
+ async_job_end (directory, "mount");
+ }
+}
+
+static void
+file_info_cancel (NautilusDirectory *directory)
+{
+ if (directory->details->get_info_in_progress != NULL)
+ {
+ g_cancellable_cancel (directory->details->get_info_in_progress->cancellable);
+ directory->details->get_info_in_progress->directory = NULL;
+ directory->details->get_info_in_progress = NULL;
+ directory->details->get_info_file = NULL;
+
+ async_job_end (directory, "file info");
+ }
+}
+
+static void
+new_files_cancel (NautilusDirectory *directory)
+{
+ GList *l;
+ NewFilesState *state;
+
+ if (directory->details->new_files_in_progress != NULL)
+ {
+ for (l = directory->details->new_files_in_progress; l != NULL; l = l->next)
+ {
+ state = l->data;
+ g_cancellable_cancel (state->cancellable);
+ state->directory = NULL;
+ }
+ g_list_free (directory->details->new_files_in_progress);
+ directory->details->new_files_in_progress = NULL;
+ }
+}
+
+static int
+monitor_key_compare (gconstpointer a,
+ gconstpointer data)
+{
+ const Monitor *monitor;
+ const Monitor *compare_monitor;
+
+ monitor = a;
+ compare_monitor = data;
+
+ if (monitor->client < compare_monitor->client)
+ {
+ return -1;
+ }
+ if (monitor->client > compare_monitor->client)
+ {
+ return +1;
+ }
+
+ if (monitor->file < compare_monitor->file)
+ {
+ return -1;
+ }
+ if (monitor->file > compare_monitor->file)
+ {
+ return +1;
+ }
+
+ return 0;
+}
+
+static Monitor *
+find_monitor (NautilusDirectory *directory,
+ NautilusFile *file,
+ gconstpointer client)
+{
+ GList *l;
+
+ l = g_hash_table_lookup (directory->details->monitor_table, file);
+
+ if (l)
+ {
+ Monitor key = {};
+ key.client = client;
+ key.file = file;
+
+ l = g_list_find_custom (l, &key, monitor_key_compare);
+ return l ? l->data : NULL;
+ }
+
+ return NULL;
+}
+
+static gboolean
+insert_new_monitor (NautilusDirectory *directory,
+ Monitor *monitor)
+{
+ GList *list;
+
+ if (find_monitor (directory, monitor->file, monitor->client) != NULL)
+ {
+ return FALSE;
+ }
+
+ list = g_hash_table_lookup (directory->details->monitor_table, monitor->file);
+ if (list == NULL)
+ {
+ list = g_list_append (list, monitor);
+ g_hash_table_insert (directory->details->monitor_table,
+ monitor->file,
+ list);
+ }
+ else
+ {
+ list = g_list_append (list, monitor);
+ }
+
+ request_counter_add_request (directory->details->monitor_counters,
+ monitor->request);
+ return TRUE;
+}
+
+static Monitor *
+remove_monitor_from_table (NautilusDirectory *directory,
+ NautilusFile *file,
+ gconstpointer client)
+{
+ GList *list, *l, *new_list;
+ Monitor *monitor = NULL;
+
+ list = g_hash_table_lookup (directory->details->monitor_table, file);
+ if (list)
+ {
+ Monitor key = {};
+ key.client = client;
+ key.file = file;
+
+ l = g_list_find_custom (list, &key, monitor_key_compare);
+ monitor = l ? l->data : NULL;
+ }
+
+ if (monitor != NULL)
+ {
+ new_list = g_list_delete_link (list, l);
+ if (new_list == NULL)
+ {
+ g_hash_table_remove (directory->details->monitor_table, file);
+ }
+ else
+ {
+ g_hash_table_replace (directory->details->monitor_table, file, new_list);
+ }
+ }
+
+ return monitor;
+}
+
+static void
+remove_monitor (NautilusDirectory *directory,
+ NautilusFile *file,
+ gconstpointer client)
+{
+ Monitor *monitor;
+
+ monitor = remove_monitor_from_table (directory, file, client);
+
+ if (monitor != NULL)
+ {
+ request_counter_remove_request (directory->details->monitor_counters,
+ monitor->request);
+ g_free (monitor);
+ }
+}
+
+Request
+nautilus_directory_set_up_request (NautilusFileAttributes file_attributes)
+{
+ Request request;
+
+ request = 0;
+
+ if ((file_attributes & NAUTILUS_FILE_ATTRIBUTE_DIRECTORY_ITEM_COUNT) != 0)
+ {
+ REQUEST_SET_TYPE (request, REQUEST_DIRECTORY_COUNT);
+ }
+
+ if ((file_attributes & NAUTILUS_FILE_ATTRIBUTE_DEEP_COUNTS) != 0)
+ {
+ REQUEST_SET_TYPE (request, REQUEST_DEEP_COUNT);
+ }
+
+ if ((file_attributes & NAUTILUS_FILE_ATTRIBUTE_DIRECTORY_ITEM_MIME_TYPES) != 0)
+ {
+ REQUEST_SET_TYPE (request, REQUEST_MIME_LIST);
+ }
+ if ((file_attributes & NAUTILUS_FILE_ATTRIBUTE_INFO) != 0)
+ {
+ REQUEST_SET_TYPE (request, REQUEST_FILE_INFO);
+ }
+
+ if ((file_attributes & NAUTILUS_FILE_ATTRIBUTE_EXTENSION_INFO) != 0)
+ {
+ REQUEST_SET_TYPE (request, REQUEST_EXTENSION_INFO);
+ }
+
+ if (file_attributes & NAUTILUS_FILE_ATTRIBUTE_THUMBNAIL)
+ {
+ REQUEST_SET_TYPE (request, REQUEST_THUMBNAIL);
+ REQUEST_SET_TYPE (request, REQUEST_FILE_INFO);
+ }
+
+ if (file_attributes & NAUTILUS_FILE_ATTRIBUTE_MOUNT)
+ {
+ REQUEST_SET_TYPE (request, REQUEST_MOUNT);
+ REQUEST_SET_TYPE (request, REQUEST_FILE_INFO);
+ }
+
+ if (file_attributes & NAUTILUS_FILE_ATTRIBUTE_FILESYSTEM_INFO)
+ {
+ REQUEST_SET_TYPE (request, REQUEST_FILESYSTEM_INFO);
+ }
+
+ return request;
+}
+
+static void
+mime_db_changed_callback (GObject *ignore,
+ NautilusDirectory *dir)
+{
+ NautilusFileAttributes attrs;
+
+ g_assert (dir != NULL);
+ g_assert (dir->details != NULL);
+
+ attrs = NAUTILUS_FILE_ATTRIBUTE_INFO |
+ NAUTILUS_FILE_ATTRIBUTE_DIRECTORY_ITEM_MIME_TYPES;
+
+ nautilus_directory_force_reload_internal (dir, attrs);
+}
+
+void
+nautilus_directory_monitor_add_internal (NautilusDirectory *directory,
+ NautilusFile *file,
+ gconstpointer client,
+ gboolean monitor_hidden_files,
+ NautilusFileAttributes file_attributes,
+ NautilusDirectoryCallback callback,
+ gpointer callback_data)
+{
+ Monitor *monitor;
+ GList *file_list;
+ char *file_uri = NULL;
+ char *dir_uri = NULL;
+
+ g_assert (NAUTILUS_IS_DIRECTORY (directory));
+
+ if (file != NULL)
+ {
+ file_uri = nautilus_file_get_uri (file);
+ }
+ if (directory != NULL)
+ {
+ dir_uri = nautilus_directory_get_uri (directory);
+ }
+ nautilus_profile_start ("uri %s file-uri %s client %p", dir_uri, file_uri, client);
+ g_free (dir_uri);
+ g_free (file_uri);
+
+ /* Replace any current monitor for this client/file pair. */
+ remove_monitor (directory, file, client);
+
+ /* Add the new monitor. */
+ monitor = g_new (Monitor, 1);
+ monitor->file = file;
+ monitor->monitor_hidden_files = monitor_hidden_files;
+ monitor->client = client;
+ monitor->request = nautilus_directory_set_up_request (file_attributes);
+
+ if (file == NULL)
+ {
+ REQUEST_SET_TYPE (monitor->request, REQUEST_FILE_LIST);
+ }
+
+ insert_new_monitor (directory, monitor);
+
+ if (callback != NULL)
+ {
+ file_list = nautilus_directory_get_file_list (directory);
+ (*callback)(directory, file_list, callback_data);
+ nautilus_file_list_free (file_list);
+ }
+
+ /* Start the "real" monitoring (FAM or whatever). */
+ /* We always monitor the whole directory since in practice
+ * nautilus almost always shows the whole directory anyway, and
+ * it allows us to avoid one file monitor per file in a directory.
+ */
+ if (directory->details->monitor == NULL)
+ {
+ directory->details->monitor = nautilus_monitor_directory (directory->details->location);
+ }
+
+
+ if (REQUEST_WANTS_TYPE (monitor->request, REQUEST_FILE_INFO) &&
+ directory->details->mime_db_monitor == 0)
+ {
+ directory->details->mime_db_monitor =
+ g_signal_connect_object (nautilus_signaller_get_current (),
+ "mime-data-changed",
+ G_CALLBACK (mime_db_changed_callback), directory, 0);
+ }
+
+ /* Put the monitor file or all the files on the work queue. */
+ if (file != NULL)
+ {
+ nautilus_directory_add_file_to_work_queue (directory, file);
+ }
+ else
+ {
+ add_all_files_to_work_queue (directory);
+ }
+
+ /* Kick off I/O. */
+ nautilus_directory_async_state_changed (directory);
+ nautilus_profile_end (NULL);
+}
+
+static void
+set_file_unconfirmed (NautilusFile *file,
+ gboolean unconfirmed)
+{
+ NautilusDirectory *directory;
+
+ g_assert (NAUTILUS_IS_FILE (file));
+ g_assert (unconfirmed == FALSE || unconfirmed == TRUE);
+
+ if (file->details->unconfirmed == unconfirmed)
+ {
+ return;
+ }
+ file->details->unconfirmed = unconfirmed;
+
+ directory = file->details->directory;
+ if (unconfirmed)
+ {
+ directory->details->confirmed_file_count--;
+ }
+ else
+ {
+ directory->details->confirmed_file_count++;
+ }
+}
+
+static gboolean show_hidden_files = TRUE;
+
+static void
+show_hidden_files_changed_callback (gpointer callback_data)
+{
+ show_hidden_files = g_settings_get_boolean (gtk_filechooser_preferences, NAUTILUS_PREFERENCES_SHOW_HIDDEN_FILES);
+}
+
+static gboolean
+should_skip_file (NautilusDirectory *directory,
+ GFileInfo *info)
+{
+ static gboolean show_hidden_files_changed_callback_installed = FALSE;
+
+ /* Add the callback once for the life of our process */
+ if (!show_hidden_files_changed_callback_installed)
+ {
+ g_signal_connect_swapped (gtk_filechooser_preferences,
+ "changed::" NAUTILUS_PREFERENCES_SHOW_HIDDEN_FILES,
+ G_CALLBACK (show_hidden_files_changed_callback),
+ NULL);
+
+ show_hidden_files_changed_callback_installed = TRUE;
+
+ /* Peek for the first time */
+ show_hidden_files_changed_callback (NULL);
+ }
+
+ if (!show_hidden_files &&
+ (g_file_info_get_is_hidden (info) ||
+ g_file_info_get_is_backup (info)))
+ {
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static void
+notify_files_changed_while_being_added (NautilusDirectory *directory)
+{
+ if (directory->details->files_changed_while_adding == NULL)
+ {
+ return;
+ }
+
+ directory->details->files_changed_while_adding =
+ g_list_reverse (directory->details->files_changed_while_adding);
+
+ nautilus_directory_notify_files_changed (directory->details->files_changed_while_adding);
+
+ g_clear_list (&directory->details->files_changed_while_adding, g_object_unref);
+}
+
+static gboolean
+dequeue_pending_idle_callback (gpointer callback_data)
+{
+ NautilusDirectory *directory;
+ GList *pending_file_info;
+ GList *node, *next;
+ NautilusFile *file;
+ GList *changed_files, *added_files;
+ GFileInfo *file_info;
+ const char *mimetype, *name;
+ DirectoryLoadState *dir_load_state;
+
+ directory = NAUTILUS_DIRECTORY (callback_data);
+
+ nautilus_directory_ref (directory);
+
+ nautilus_profile_start ("nitems %d", g_list_length (directory->details->pending_file_info));
+
+ directory->details->dequeue_pending_idle_id = 0;
+
+ /* Handle the files in the order we saw them. */
+ pending_file_info = g_list_reverse (directory->details->pending_file_info);
+ directory->details->pending_file_info = NULL;
+
+ /* If we are no longer monitoring, then throw away these. */
+ if (!nautilus_directory_is_file_list_monitored (directory))
+ {
+ nautilus_directory_async_state_changed (directory);
+ goto drain;
+ }
+
+ added_files = NULL;
+ changed_files = NULL;
+
+ dir_load_state = directory->details->directory_load_in_progress;
+
+ /* Build a list of NautilusFile objects. */
+ for (node = pending_file_info; node != NULL; node = node->next)
+ {
+ file_info = node->data;
+
+ name = g_file_info_get_name (file_info);
+
+ /* Update the file count. */
+ /* FIXME bugzilla.gnome.org 45063: This could count a
+ * file twice if we get it from both load_directory
+ * and from new_files_callback. Not too hard to fix by
+ * moving this into the actual callback instead of
+ * waiting for the idle function.
+ */
+ if (dir_load_state &&
+ !should_skip_file (directory, file_info))
+ {
+ dir_load_state->load_file_count += 1;
+
+ /* Add the MIME type to the set. */
+ mimetype = g_file_info_get_content_type (file_info);
+ if (mimetype != NULL)
+ {
+ istr_set_insert (dir_load_state->load_mime_list_hash,
+ mimetype);
+ }
+ }
+
+ /* check if the file already exists */
+ file = nautilus_directory_find_file_by_name (directory, name);
+ if (file != NULL)
+ {
+ /* file already exists in dir, check if we still need to
+ * emit file_added or if it changed */
+ set_file_unconfirmed (file, FALSE);
+ if (!file->details->is_added)
+ {
+ /* We consider this newly added even if its in the list.
+ * This can happen if someone called nautilus_file_get_by_uri()
+ * on a file in the folder before the add signal was
+ * emitted */
+ nautilus_file_ref (file);
+ file->details->is_added = TRUE;
+ added_files = g_list_prepend (added_files, file);
+ }
+ else if (nautilus_file_update_info (file, file_info))
+ {
+ /* File changed, notify about the change. */
+ nautilus_file_ref (file);
+ changed_files = g_list_prepend (changed_files, file);
+ }
+ }
+ else
+ {
+ /* new file, create a nautilus file object and add it to the list */
+ file = nautilus_file_new_from_info (directory, file_info);
+ nautilus_directory_add_file (directory, file);
+ file->details->is_added = TRUE;
+ added_files = g_list_prepend (added_files, file);
+ }
+ }
+
+ /* If we are done loading, then we assume that any unconfirmed
+ * files are gone.
+ */
+ if (directory->details->directory_loaded)
+ {
+ for (node = directory->details->file_list;
+ node != NULL; node = next)
+ {
+ file = NAUTILUS_FILE (node->data);
+ next = node->next;
+
+ if (file->details->unconfirmed)
+ {
+ nautilus_file_ref (file);
+ changed_files = g_list_prepend (changed_files, file);
+
+ nautilus_file_mark_gone (file);
+ }
+ }
+ }
+
+ /* Send the changed and added signals. */
+ nautilus_directory_emit_change_signals (directory, changed_files);
+ nautilus_file_list_free (changed_files);
+ nautilus_directory_emit_files_added (directory, added_files);
+ nautilus_file_list_free (added_files);
+
+ if (directory->details->directory_loaded &&
+ !directory->details->directory_loaded_sent_notification)
+ {
+ /* Send the done_loading signal. */
+ nautilus_directory_emit_done_loading (directory);
+
+ if (dir_load_state)
+ {
+ file = dir_load_state->load_directory_file;
+
+ file->details->directory_count = dir_load_state->load_file_count;
+ file->details->directory_count_is_up_to_date = TRUE;
+ file->details->got_directory_count = TRUE;
+
+ file->details->got_mime_list = TRUE;
+ file->details->mime_list_is_up_to_date = TRUE;
+ g_list_free_full (file->details->mime_list, g_free);
+ file->details->mime_list = istr_set_get_as_list
+ (dir_load_state->load_mime_list_hash);
+
+ nautilus_file_changed (file);
+ }
+
+ nautilus_directory_async_state_changed (directory);
+
+ directory->details->directory_loaded_sent_notification = TRUE;
+ }
+
+ /* Process changes received for files while they were still being added.
+ * See Bug 703179 and issue #1576 for a situation this happens. */
+ notify_files_changed_while_being_added (directory);
+
+drain:
+ g_list_free_full (pending_file_info, g_object_unref);
+
+ /* Get the state machine running again. */
+ nautilus_directory_async_state_changed (directory);
+
+ nautilus_profile_end (NULL);
+
+ nautilus_directory_unref (directory);
+ return FALSE;
+}
+
+void
+nautilus_directory_schedule_dequeue_pending (NautilusDirectory *directory)
+{
+ if (directory->details->dequeue_pending_idle_id == 0)
+ {
+ directory->details->dequeue_pending_idle_id
+ = g_idle_add (dequeue_pending_idle_callback, directory);
+ }
+}
+
+static void
+directory_load_one (NautilusDirectory *directory,
+ GFileInfo *info)
+{
+ if (info == NULL)
+ {
+ return;
+ }
+
+ if (g_file_info_get_name (info) == NULL)
+ {
+ char *uri;
+
+ uri = nautilus_directory_get_uri (directory);
+ g_warning ("Got GFileInfo with NULL name in %s, ignoring. This shouldn't happen unless the gvfs backend is broken.\n", uri);
+ g_free (uri);
+
+ return;
+ }
+
+ /* Arrange for the "loading" part of the work. */
+ g_object_ref (info);
+ directory->details->pending_file_info
+ = g_list_prepend (directory->details->pending_file_info, info);
+ nautilus_directory_schedule_dequeue_pending (directory);
+}
+
+static void
+directory_load_cancel (NautilusDirectory *directory)
+{
+ NautilusFile *file;
+ DirectoryLoadState *state;
+
+ state = directory->details->directory_load_in_progress;
+ if (state != NULL)
+ {
+ file = state->load_directory_file;
+ file->details->loading_directory = FALSE;
+ if (file->details->directory != directory)
+ {
+ nautilus_directory_async_state_changed (file->details->directory);
+ }
+
+ g_cancellable_cancel (state->cancellable);
+ state->directory = NULL;
+ directory->details->directory_load_in_progress = NULL;
+ async_job_end (directory, "file list");
+ }
+}
+
+static void
+file_list_cancel (NautilusDirectory *directory)
+{
+ directory_load_cancel (directory);
+
+ if (directory->details->dequeue_pending_idle_id != 0)
+ {
+ g_source_remove (directory->details->dequeue_pending_idle_id);
+ directory->details->dequeue_pending_idle_id = 0;
+ }
+
+ if (directory->details->pending_file_info != NULL)
+ {
+ g_list_free_full (directory->details->pending_file_info, g_object_unref);
+ directory->details->pending_file_info = NULL;
+ }
+}
+
+static void
+directory_load_done (NautilusDirectory *directory,
+ GError *error)
+{
+ GList *node;
+
+ nautilus_profile_start (NULL);
+ g_object_ref (directory);
+
+ directory->details->directory_loaded = TRUE;
+ directory->details->directory_loaded_sent_notification = FALSE;
+
+ if (error != NULL)
+ {
+ /* The load did not complete successfully. This means
+ * we don't know the status of the files in this directory.
+ * We clear the unconfirmed bit on each file here so that
+ * they won't be marked "gone" later -- we don't know enough
+ * about them to know whether they are really gone.
+ */
+ for (node = directory->details->file_list;
+ node != NULL; node = node->next)
+ {
+ set_file_unconfirmed (NAUTILUS_FILE (node->data), FALSE);
+ }
+
+ nautilus_directory_emit_load_error (directory, error);
+ }
+
+ /* Call the idle function right away. */
+ if (directory->details->dequeue_pending_idle_id != 0)
+ {
+ g_source_remove (directory->details->dequeue_pending_idle_id);
+ }
+ dequeue_pending_idle_callback (directory);
+
+ directory_load_cancel (directory);
+
+ g_object_unref (directory);
+ nautilus_profile_end (NULL);
+}
+
+void
+nautilus_directory_monitor_remove_internal (NautilusDirectory *directory,
+ NautilusFile *file,
+ gconstpointer client)
+{
+ g_assert (NAUTILUS_IS_DIRECTORY (directory));
+ g_assert (file == NULL || NAUTILUS_IS_FILE (file));
+ g_assert (client != NULL);
+
+ remove_monitor (directory, file, client);
+
+ if (directory->details->monitor != NULL
+ && g_hash_table_size (directory->details->monitor_table) == 0)
+ {
+ nautilus_monitor_cancel (directory->details->monitor);
+ directory->details->monitor = NULL;
+ }
+
+ /* XXX - do we need to remove anything from the work queue? */
+
+ nautilus_directory_async_state_changed (directory);
+}
+
+FileMonitors *
+nautilus_directory_remove_file_monitors (NautilusDirectory *directory,
+ NautilusFile *file)
+{
+ GList *result, *node;
+ Monitor *monitor;
+
+ g_assert (NAUTILUS_IS_DIRECTORY (directory));
+ g_assert (NAUTILUS_IS_FILE (file));
+ g_assert (file->details->directory == directory);
+
+ result = g_hash_table_lookup (directory->details->monitor_table, file);
+
+ if (result != NULL)
+ {
+ g_hash_table_remove (directory->details->monitor_table, file);
+
+ for (node = result; node; node = node->next)
+ {
+ monitor = node->data;
+ request_counter_remove_request (directory->details->monitor_counters,
+ monitor->request);
+ }
+ result = g_list_reverse (result);
+ }
+
+ /* XXX - do we need to remove anything from the work queue? */
+
+ nautilus_directory_async_state_changed (directory);
+
+ return (FileMonitors *) result;
+}
+
+void
+nautilus_directory_add_file_monitors (NautilusDirectory *directory,
+ NautilusFile *file,
+ FileMonitors *monitors)
+{
+ GList *l;
+ Monitor *monitor;
+
+ g_assert (NAUTILUS_IS_DIRECTORY (directory));
+ g_assert (NAUTILUS_IS_FILE (file));
+ g_assert (file->details->directory == directory);
+
+ if (monitors == NULL)
+ {
+ return;
+ }
+
+ for (l = (GList *) monitors; l != NULL; l = l->next)
+ {
+ monitor = l->data;
+
+ remove_monitor (directory, monitor->file, monitor->client);
+ insert_new_monitor (directory, monitor);
+ }
+
+ g_list_free ((GList *) monitors);
+
+ nautilus_directory_add_file_to_work_queue (directory, file);
+
+ nautilus_directory_async_state_changed (directory);
+}
+
+static int
+ready_callback_key_compare (gconstpointer a,
+ gconstpointer b)
+{
+ const ReadyCallback *callback_a, *callback_b;
+
+ callback_a = a;
+ callback_b = b;
+
+ if (callback_a->file < callback_b->file)
+ {
+ return -1;
+ }
+ if (callback_a->file > callback_b->file)
+ {
+ return 1;
+ }
+ if (callback_a->file == NULL)
+ {
+ /* ANSI C doesn't allow ordered compares of function pointers, so we cast them to
+ * normal pointers to make some overly pedantic compilers (*cough* HP-UX *cough*)
+ * compile this. Of course, on any compiler where ordered function pointers actually
+ * break this probably won't work, but at least it will compile on platforms where it
+ * works, but stupid compilers won't let you use it.
+ */
+ if ((void *) callback_a->callback.directory < (void *) callback_b->callback.directory)
+ {
+ return -1;
+ }
+ if ((void *) callback_a->callback.directory > (void *) callback_b->callback.directory)
+ {
+ return 1;
+ }
+ }
+ else
+ {
+ if ((void *) callback_a->callback.file < (void *) callback_b->callback.file)
+ {
+ return -1;
+ }
+ if ((void *) callback_a->callback.file > (void *) callback_b->callback.file)
+ {
+ return 1;
+ }
+ }
+ if (callback_a->callback_data < callback_b->callback_data)
+ {
+ return -1;
+ }
+ if (callback_a->callback_data > callback_b->callback_data)
+ {
+ return 1;
+ }
+ return 0;
+}
+
+static int
+ready_callback_key_compare_only_active (gconstpointer a,
+ gconstpointer b)
+{
+ const ReadyCallback *callback_a;
+
+ callback_a = a;
+
+ /* Non active callbacks never match */
+ if (!callback_a->active)
+ {
+ return -1;
+ }
+
+ return ready_callback_key_compare (a, b);
+}
+
+static void
+ready_callback_call (NautilusDirectory *directory,
+ const ReadyCallback *callback)
+{
+ GList *file_list;
+
+ /* Call the callback. */
+ if (callback->file != NULL)
+ {
+ if (callback->callback.file)
+ {
+ (*callback->callback.file)(callback->file,
+ callback->callback_data);
+ }
+ }
+ else if (callback->callback.directory != NULL)
+ {
+ if (directory == NULL ||
+ !REQUEST_WANTS_TYPE (callback->request, REQUEST_FILE_LIST))
+ {
+ file_list = NULL;
+ }
+ else
+ {
+ file_list = nautilus_directory_get_file_list (directory);
+ }
+
+ /* Pass back the file list if the user was waiting for it. */
+ (*callback->callback.directory)(directory,
+ file_list,
+ callback->callback_data);
+
+ nautilus_file_list_free (file_list);
+ }
+}
+
+void
+nautilus_directory_call_when_ready_internal (NautilusDirectory *directory,
+ NautilusFile *file,
+ NautilusFileAttributes file_attributes,
+ gboolean wait_for_file_list,
+ NautilusDirectoryCallback directory_callback,
+ NautilusFileCallback file_callback,
+ gpointer callback_data)
+{
+ ReadyCallback callback;
+
+ g_assert (directory == NULL || NAUTILUS_IS_DIRECTORY (directory));
+ g_assert (file == NULL || NAUTILUS_IS_FILE (file));
+ g_assert (file != NULL || directory_callback != NULL);
+
+ /* Construct a callback object. */
+ callback.active = TRUE;
+ callback.file = file;
+ if (file == NULL)
+ {
+ callback.callback.directory = directory_callback;
+ }
+ else
+ {
+ callback.callback.file = file_callback;
+ }
+ callback.callback_data = callback_data;
+ callback.request = nautilus_directory_set_up_request (file_attributes);
+ if (wait_for_file_list)
+ {
+ REQUEST_SET_TYPE (callback.request, REQUEST_FILE_LIST);
+ }
+
+ /* Handle the NULL case. */
+ if (directory == NULL)
+ {
+ ready_callback_call (NULL, &callback);
+ return;
+ }
+
+ /* Check if the callback is already there. */
+ if (g_list_find_custom (directory->details->call_when_ready_list,
+ &callback,
+ ready_callback_key_compare_only_active) != NULL)
+ {
+ if (file_callback != NULL && directory_callback != NULL)
+ {
+ g_warning ("tried to add a new callback while an old one was pending");
+ }
+ /* NULL callback means, just read it. Conflicts are ok. */
+ return;
+ }
+
+ /* Add the new callback to the list. */
+ directory->details->call_when_ready_list = g_list_prepend
+ (directory->details->call_when_ready_list,
+ g_memdup (&callback, sizeof (callback)));
+ request_counter_add_request (directory->details->call_when_ready_counters,
+ callback.request);
+
+ /* Put the callback file or all the files on the work queue. */
+ if (file != NULL)
+ {
+ nautilus_directory_add_file_to_work_queue (directory, file);
+ }
+ else
+ {
+ add_all_files_to_work_queue (directory);
+ }
+
+ nautilus_directory_async_state_changed (directory);
+}
+
+gboolean
+nautilus_directory_check_if_ready_internal (NautilusDirectory *directory,
+ NautilusFile *file,
+ NautilusFileAttributes file_attributes)
+{
+ Request request;
+
+ g_assert (NAUTILUS_IS_DIRECTORY (directory));
+
+ request = nautilus_directory_set_up_request (file_attributes);
+ return request_is_satisfied (directory, file, request);
+}
+
+static void
+remove_callback_link_keep_data (NautilusDirectory *directory,
+ GList *link)
+{
+ ReadyCallback *callback;
+
+ callback = link->data;
+
+ directory->details->call_when_ready_list = g_list_remove_link
+ (directory->details->call_when_ready_list, link);
+
+ request_counter_remove_request (directory->details->call_when_ready_counters,
+ callback->request);
+ g_list_free_1 (link);
+}
+
+static void
+remove_callback_link (NautilusDirectory *directory,
+ GList *link)
+{
+ ReadyCallback *callback;
+
+ callback = link->data;
+ remove_callback_link_keep_data (directory, link);
+ g_free (callback);
+}
+
+void
+nautilus_directory_cancel_callback_internal (NautilusDirectory *directory,
+ NautilusFile *file,
+ NautilusDirectoryCallback directory_callback,
+ NautilusFileCallback file_callback,
+ gpointer callback_data)
+{
+ ReadyCallback callback;
+ GList *node;
+
+ if (directory == NULL)
+ {
+ return;
+ }
+
+ g_assert (NAUTILUS_IS_DIRECTORY (directory));
+ g_assert (file == NULL || NAUTILUS_IS_FILE (file));
+ g_assert (file != NULL || directory_callback != NULL);
+ g_assert (file == NULL || file_callback != NULL);
+
+ /* Construct a callback object. */
+ callback.file = file;
+ if (file == NULL)
+ {
+ callback.callback.directory = directory_callback;
+ }
+ else
+ {
+ callback.callback.file = file_callback;
+ }
+ callback.callback_data = callback_data;
+
+ /* Remove all queued callback from the list (including non-active). */
+ do
+ {
+ node = g_list_find_custom (directory->details->call_when_ready_list,
+ &callback,
+ ready_callback_key_compare);
+ if (node != NULL)
+ {
+ remove_callback_link (directory, node);
+
+ nautilus_directory_async_state_changed (directory);
+ }
+ }
+ while (node != NULL);
+}
+
+static void
+new_files_state_unref (NewFilesState *state)
+{
+ state->count--;
+
+ if (state->count == 0)
+ {
+ if (state->directory)
+ {
+ state->directory->details->new_files_in_progress =
+ g_list_remove (state->directory->details->new_files_in_progress,
+ state);
+ }
+
+ g_object_unref (state->cancellable);
+ g_free (state);
+ }
+}
+
+static void
+new_files_callback (GObject *source_object,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ NautilusDirectory *directory;
+ GFileInfo *info;
+ NewFilesState *state;
+
+ state = user_data;
+
+ if (state->directory == NULL)
+ {
+ /* Operation was cancelled. Bail out */
+ new_files_state_unref (state);
+ return;
+ }
+
+ directory = nautilus_directory_ref (state->directory);
+
+ /* Queue up the new file. */
+ info = g_file_query_info_finish (G_FILE (source_object), res, NULL);
+ if (info != NULL)
+ {
+ directory_load_one (directory, info);
+ g_object_unref (info);
+ }
+
+ new_files_state_unref (state);
+
+ nautilus_directory_unref (directory);
+}
+
+void
+nautilus_directory_get_info_for_new_files (NautilusDirectory *directory,
+ GList *location_list)
+{
+ NewFilesState *state;
+ GFile *location;
+ GList *l;
+
+ if (location_list == NULL)
+ {
+ return;
+ }
+
+ state = g_new (NewFilesState, 1);
+ state->directory = directory;
+ state->cancellable = g_cancellable_new ();
+ state->count = 0;
+
+ for (l = location_list; l != NULL; l = l->next)
+ {
+ location = l->data;
+
+ state->count++;
+
+ g_file_query_info_async (location,
+ NAUTILUS_FILE_DEFAULT_ATTRIBUTES,
+ 0,
+ G_PRIORITY_DEFAULT,
+ state->cancellable,
+ new_files_callback, state);
+ }
+
+ directory->details->new_files_in_progress
+ = g_list_prepend (directory->details->new_files_in_progress,
+ state);
+}
+
+void
+nautilus_async_destroying_file (NautilusFile *file)
+{
+ NautilusDirectory *directory;
+ gboolean changed;
+ GList *node, *next;
+ ReadyCallback *callback;
+ Monitor *monitor;
+
+ directory = file->details->directory;
+ changed = FALSE;
+
+ /* Check for callbacks. */
+ for (node = directory->details->call_when_ready_list; node != NULL; node = next)
+ {
+ next = node->next;
+ callback = node->data;
+
+ if (callback->file == file)
+ {
+ /* Client should have cancelled callback. */
+ if (callback->active)
+ {
+ g_warning ("destroyed file has call_when_ready pending");
+ }
+ remove_callback_link (directory, node);
+ changed = TRUE;
+ }
+ }
+
+ /* Check for monitors. */
+ node = g_hash_table_lookup (directory->details->monitor_table, file);
+ if (node != NULL)
+ {
+ /* Client should have removed monitor earlier. */
+ g_warning ("destroyed file still being monitored");
+ for (; node; node = next)
+ {
+ next = node->next;
+ monitor = node->data;
+
+ remove_monitor (directory, monitor->file, monitor->client);
+ }
+ changed = TRUE;
+ }
+
+ /* Check if it's a file that's currently being worked on.
+ * If so, make that NULL so it gets canceled right away.
+ */
+ if (directory->details->count_in_progress != NULL &&
+ directory->details->count_in_progress->count_file == file)
+ {
+ directory->details->count_in_progress->count_file = NULL;
+ changed = TRUE;
+ }
+ if (directory->details->deep_count_file == file)
+ {
+ directory->details->deep_count_file = NULL;
+ changed = TRUE;
+ }
+ if (directory->details->mime_list_in_progress != NULL &&
+ directory->details->mime_list_in_progress->mime_list_file == file)
+ {
+ directory->details->mime_list_in_progress->mime_list_file = NULL;
+ changed = TRUE;
+ }
+ if (directory->details->get_info_file == file)
+ {
+ directory->details->get_info_file = NULL;
+ changed = TRUE;
+ }
+ if (directory->details->extension_info_file == file)
+ {
+ directory->details->extension_info_file = NULL;
+ changed = TRUE;
+ }
+
+ if (directory->details->thumbnail_state != NULL &&
+ directory->details->thumbnail_state->file == file)
+ {
+ directory->details->thumbnail_state->file = NULL;
+ changed = TRUE;
+ }
+
+ if (directory->details->mount_state != NULL &&
+ directory->details->mount_state->file == file)
+ {
+ directory->details->mount_state->file = NULL;
+ changed = TRUE;
+ }
+
+ if (directory->details->filesystem_info_state != NULL &&
+ directory->details->filesystem_info_state->file == file)
+ {
+ directory->details->filesystem_info_state->file = NULL;
+ changed = TRUE;
+ }
+
+ /* Let the directory take care of the rest. */
+ if (changed)
+ {
+ nautilus_directory_async_state_changed (directory);
+ }
+}
+
+static gboolean
+lacks_directory_count (NautilusFile *file)
+{
+ return !file->details->directory_count_is_up_to_date
+ && nautilus_file_should_show_directory_item_count (file);
+}
+
+static gboolean
+should_get_directory_count_now (NautilusFile *file)
+{
+ return lacks_directory_count (file)
+ && !file->details->loading_directory;
+}
+
+static gboolean
+lacks_info (NautilusFile *file)
+{
+ return !file->details->file_info_is_up_to_date
+ && !file->details->is_gone;
+}
+
+static gboolean
+lacks_filesystem_info (NautilusFile *file)
+{
+ return !file->details->filesystem_info_is_up_to_date;
+}
+
+static gboolean
+lacks_deep_count (NautilusFile *file)
+{
+ return file->details->deep_counts_status != NAUTILUS_REQUEST_DONE;
+}
+
+static gboolean
+lacks_mime_list (NautilusFile *file)
+{
+ return !file->details->mime_list_is_up_to_date;
+}
+
+static gboolean
+should_get_mime_list (NautilusFile *file)
+{
+ return lacks_mime_list (file)
+ && !file->details->loading_directory;
+}
+
+static gboolean
+lacks_extension_info (NautilusFile *file)
+{
+ return file->details->pending_info_providers != NULL;
+}
+
+static gboolean
+lacks_thumbnail (NautilusFile *file)
+{
+ return nautilus_file_should_show_thumbnail (file) &&
+ file->details->thumbnail_path != NULL &&
+ !file->details->thumbnail_is_up_to_date;
+}
+
+static gboolean
+lacks_mount (NautilusFile *file)
+{
+ return (!file->details->mount_is_up_to_date &&
+ (
+ /* Unix mountpoint, could be a GMount */
+ file->details->is_mountpoint ||
+
+ /* The toplevel directory of something */
+ (file->details->type == G_FILE_TYPE_DIRECTORY &&
+ nautilus_file_is_self_owned (file)) ||
+
+ /* Mountable, could be a mountpoint */
+ (file->details->type == G_FILE_TYPE_MOUNTABLE)
+
+ )
+ );
+}
+
+static gboolean
+has_problem (NautilusDirectory *directory,
+ NautilusFile *file,
+ FileCheck problem)
+{
+ GList *node;
+
+ if (file != NULL)
+ {
+ return (*problem)(file);
+ }
+
+ for (node = directory->details->file_list; node != NULL; node = node->next)
+ {
+ if ((*problem)(node->data))
+ {
+ return TRUE;
+ }
+ }
+
+ return FALSE;
+}
+
+static gboolean
+request_is_satisfied (NautilusDirectory *directory,
+ NautilusFile *file,
+ Request request)
+{
+ if (REQUEST_WANTS_TYPE (request, REQUEST_FILE_LIST) &&
+ !(directory->details->directory_loaded &&
+ directory->details->directory_loaded_sent_notification))
+ {
+ return FALSE;
+ }
+
+ if (REQUEST_WANTS_TYPE (request, REQUEST_DIRECTORY_COUNT))
+ {
+ if (has_problem (directory, file, lacks_directory_count))
+ {
+ return FALSE;
+ }
+ }
+
+ if (REQUEST_WANTS_TYPE (request, REQUEST_FILE_INFO))
+ {
+ if (has_problem (directory, file, lacks_info))
+ {
+ return FALSE;
+ }
+ }
+
+ if (REQUEST_WANTS_TYPE (request, REQUEST_FILESYSTEM_INFO))
+ {
+ if (has_problem (directory, file, lacks_filesystem_info))
+ {
+ return FALSE;
+ }
+ }
+
+ if (REQUEST_WANTS_TYPE (request, REQUEST_DEEP_COUNT))
+ {
+ if (has_problem (directory, file, lacks_deep_count))
+ {
+ return FALSE;
+ }
+ }
+
+ if (REQUEST_WANTS_TYPE (request, REQUEST_THUMBNAIL))
+ {
+ if (has_problem (directory, file, lacks_thumbnail))
+ {
+ return FALSE;
+ }
+ }
+
+ if (REQUEST_WANTS_TYPE (request, REQUEST_MOUNT))
+ {
+ if (has_problem (directory, file, lacks_mount))
+ {
+ return FALSE;
+ }
+ }
+
+ if (REQUEST_WANTS_TYPE (request, REQUEST_MIME_LIST))
+ {
+ if (has_problem (directory, file, lacks_mime_list))
+ {
+ return FALSE;
+ }
+ }
+
+ return TRUE;
+}
+
+static gboolean
+call_ready_callbacks_at_idle (gpointer callback_data)
+{
+ NautilusDirectory *directory;
+ GList *node, *next;
+ ReadyCallback *callback;
+
+ directory = NAUTILUS_DIRECTORY (callback_data);
+ directory->details->call_ready_idle_id = 0;
+
+ nautilus_directory_ref (directory);
+
+ callback = NULL;
+ while (1)
+ {
+ /* Check if any callbacks are non-active and call them if they are. */
+ for (node = directory->details->call_when_ready_list;
+ node != NULL; node = next)
+ {
+ next = node->next;
+ callback = node->data;
+ if (!callback->active)
+ {
+ /* Non-active, remove and call */
+ break;
+ }
+ }
+ if (node == NULL)
+ {
+ break;
+ }
+
+ /* Callbacks are one-shots, so remove it now. */
+ remove_callback_link_keep_data (directory, node);
+
+ /* Call the callback. */
+ ready_callback_call (directory, callback);
+ g_free (callback);
+ }
+
+ nautilus_directory_async_state_changed (directory);
+
+ nautilus_directory_unref (directory);
+
+ return FALSE;
+}
+
+static void
+schedule_call_ready_callbacks (NautilusDirectory *directory)
+{
+ if (directory->details->call_ready_idle_id == 0)
+ {
+ directory->details->call_ready_idle_id
+ = g_idle_add (call_ready_callbacks_at_idle, directory);
+ }
+}
+
+/* Marks all callbacks that are ready as non-active and
+ * calls them at idle time, unless they are removed
+ * before then */
+static gboolean
+call_ready_callbacks (NautilusDirectory *directory)
+{
+ gboolean found_any;
+ GList *node, *next;
+ ReadyCallback *callback;
+
+ found_any = FALSE;
+
+ /* Check if any callbacks are satisifed and mark them for call them if they are. */
+ for (node = directory->details->call_when_ready_list;
+ node != NULL; node = next)
+ {
+ next = node->next;
+ callback = node->data;
+ if (callback->active &&
+ request_is_satisfied (directory, callback->file, callback->request))
+ {
+ callback->active = FALSE;
+ found_any = TRUE;
+ }
+ }
+
+ if (found_any)
+ {
+ schedule_call_ready_callbacks (directory);
+ }
+
+ return found_any;
+}
+
+static GList *
+lookup_monitors (GHashTable *monitor_table,
+ NautilusFile *file)
+{
+ /* To find monitors monitoring all files, use lookup_all_files_monitors. */
+ g_return_val_if_fail (file, NULL);
+
+ return g_hash_table_lookup (monitor_table, file);
+}
+
+static GList *
+lookup_all_files_monitors (GHashTable *monitor_table)
+{
+ /* monitor->file == NULL means monitor all files. */
+ return g_hash_table_lookup (monitor_table, NULL);
+}
+
+gboolean
+nautilus_directory_has_active_request_for_file (NautilusDirectory *directory,
+ NautilusFile *file)
+{
+ GList *node;
+ ReadyCallback *callback;
+
+ for (node = directory->details->call_when_ready_list;
+ node != NULL; node = node->next)
+ {
+ callback = node->data;
+ if (callback->file == file ||
+ callback->file == NULL)
+ {
+ return TRUE;
+ }
+ }
+
+ if (lookup_monitors (directory->details->monitor_table, file) != NULL)
+ {
+ return TRUE;
+ }
+ if (lookup_all_files_monitors (directory->details->monitor_table) != NULL)
+ {
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+
+/* This checks if there's a request for monitoring the file list. */
+gboolean
+nautilus_directory_is_anyone_monitoring_file_list (NautilusDirectory *directory)
+{
+ if (directory->details->call_when_ready_counters[REQUEST_FILE_LIST] > 0)
+ {
+ return TRUE;
+ }
+
+ if (directory->details->monitor_counters[REQUEST_FILE_LIST] > 0)
+ {
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+/* This checks if the file list being monitored. */
+gboolean
+nautilus_directory_is_file_list_monitored (NautilusDirectory *directory)
+{
+ return directory->details->file_list_monitored;
+}
+
+static void
+mark_all_files_unconfirmed (NautilusDirectory *directory)
+{
+ GList *node;
+ NautilusFile *file;
+
+ for (node = directory->details->file_list; node != NULL; node = node->next)
+ {
+ file = node->data;
+ set_file_unconfirmed (file, TRUE);
+ }
+}
+
+static void
+directory_load_state_free (DirectoryLoadState *state)
+{
+ if (state->enumerator)
+ {
+ if (!g_file_enumerator_is_closed (state->enumerator))
+ {
+ g_file_enumerator_close_async (state->enumerator,
+ 0, NULL, NULL, NULL);
+ }
+ g_object_unref (state->enumerator);
+ }
+
+ if (state->load_mime_list_hash != NULL)
+ {
+ istr_set_destroy (state->load_mime_list_hash);
+ }
+ nautilus_file_unref (state->load_directory_file);
+ g_object_unref (state->cancellable);
+ g_free (state);
+}
+
+static void
+more_files_callback (GObject *source_object,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ DirectoryLoadState *state;
+ NautilusDirectory *directory;
+ GError *error;
+ GList *files, *l;
+ GFileInfo *info;
+
+ state = user_data;
+
+ if (state->directory == NULL)
+ {
+ /* Operation was cancelled. Bail out */
+ directory_load_state_free (state);
+ return;
+ }
+
+ directory = nautilus_directory_ref (state->directory);
+
+ g_assert (directory->details->directory_load_in_progress != NULL);
+ g_assert (directory->details->directory_load_in_progress == state);
+
+ error = NULL;
+ files = g_file_enumerator_next_files_finish (state->enumerator,
+ res, &error);
+
+ for (l = files; l != NULL; l = l->next)
+ {
+ info = l->data;
+ directory_load_one (directory, info);
+ g_object_unref (info);
+ }
+
+ if (files == NULL)
+ {
+ directory_load_done (directory, error);
+ directory_load_state_free (state);
+ }
+ else
+ {
+ g_file_enumerator_next_files_async (state->enumerator,
+ DIRECTORY_LOAD_ITEMS_PER_CALLBACK,
+ G_PRIORITY_DEFAULT,
+ state->cancellable,
+ more_files_callback,
+ state);
+ }
+
+ nautilus_directory_unref (directory);
+
+ if (error)
+ {
+ g_error_free (error);
+ }
+
+ g_list_free (files);
+}
+
+static void
+enumerate_children_callback (GObject *source_object,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ DirectoryLoadState *state;
+ GFileEnumerator *enumerator;
+ GError *error;
+
+ state = user_data;
+
+ if (state->directory == NULL)
+ {
+ /* Operation was cancelled. Bail out */
+ directory_load_state_free (state);
+ return;
+ }
+
+ error = NULL;
+ enumerator = g_file_enumerate_children_finish (G_FILE (source_object),
+ res, &error);
+
+ if (enumerator == NULL)
+ {
+ directory_load_done (state->directory, error);
+ g_error_free (error);
+ directory_load_state_free (state);
+ return;
+ }
+ else
+ {
+ state->enumerator = enumerator;
+ g_file_enumerator_next_files_async (state->enumerator,
+ DIRECTORY_LOAD_ITEMS_PER_CALLBACK,
+ G_PRIORITY_DEFAULT,
+ state->cancellable,
+ more_files_callback,
+ state);
+ }
+}
+
+
+/* Start monitoring the file list if it isn't already. */
+static void
+start_monitoring_file_list (NautilusDirectory *directory)
+{
+ DirectoryLoadState *state;
+
+ if (!directory->details->file_list_monitored)
+ {
+ g_assert (!directory->details->directory_load_in_progress);
+ directory->details->file_list_monitored = TRUE;
+ nautilus_file_list_ref (directory->details->file_list);
+ }
+
+ if (directory->details->directory_loaded ||
+ directory->details->directory_load_in_progress != NULL)
+ {
+ return;
+ }
+
+ if (!async_job_start (directory, "file list"))
+ {
+ return;
+ }
+
+ mark_all_files_unconfirmed (directory);
+
+ state = g_new0 (DirectoryLoadState, 1);
+ state->directory = directory;
+ state->cancellable = g_cancellable_new ();
+ state->load_mime_list_hash = istr_set_new ();
+ state->load_file_count = 0;
+
+ g_assert (directory->details->location != NULL);
+ state->load_directory_file =
+ nautilus_directory_get_corresponding_file (directory);
+ state->load_directory_file->details->loading_directory = TRUE;
+
+
+ DEBUG ("load_directory called to monitor file list of %p", directory->details->location);
+
+ directory->details->directory_load_in_progress = state;
+
+ g_file_enumerate_children_async (directory->details->location,
+ NAUTILUS_FILE_DEFAULT_ATTRIBUTES,
+ 0, /* flags */
+ G_PRIORITY_DEFAULT, /* prio */
+ state->cancellable,
+ enumerate_children_callback,
+ state);
+}
+
+/* Stop monitoring the file list if it is being monitored. */
+void
+nautilus_directory_stop_monitoring_file_list (NautilusDirectory *directory)
+{
+ if (!directory->details->file_list_monitored)
+ {
+ g_assert (directory->details->directory_load_in_progress == NULL);
+ return;
+ }
+
+ directory->details->file_list_monitored = FALSE;
+ file_list_cancel (directory);
+ nautilus_file_list_unref (directory->details->file_list);
+ directory->details->directory_loaded = FALSE;
+}
+
+static void
+file_list_start_or_stop (NautilusDirectory *directory)
+{
+ if (nautilus_directory_is_anyone_monitoring_file_list (directory))
+ {
+ start_monitoring_file_list (directory);
+ }
+ else
+ {
+ nautilus_directory_stop_monitoring_file_list (directory);
+ }
+}
+
+void
+nautilus_file_invalidate_count_and_mime_list (NautilusFile *file)
+{
+ NautilusFileAttributes attributes;
+
+ attributes = NAUTILUS_FILE_ATTRIBUTE_DIRECTORY_ITEM_COUNT |
+ NAUTILUS_FILE_ATTRIBUTE_DIRECTORY_ITEM_MIME_TYPES;
+
+ nautilus_file_invalidate_attributes (file, attributes);
+}
+
+
+/* Reset count and mime list. Invalidating deep counts is handled by
+ * itself elsewhere because it's a relatively heavyweight and
+ * special-purpose operation (see bug 5863). Also, the shallow count
+ * needs to be refreshed when filtering changes, but the deep count
+ * deliberately does not take filtering into account.
+ */
+void
+nautilus_directory_invalidate_count_and_mime_list (NautilusDirectory *directory)
+{
+ NautilusFile *file;
+
+ file = nautilus_directory_get_existing_corresponding_file (directory);
+ if (file != NULL)
+ {
+ nautilus_file_invalidate_count_and_mime_list (file);
+ }
+
+ nautilus_file_unref (file);
+}
+
+static void
+nautilus_directory_invalidate_file_attributes (NautilusDirectory *directory,
+ NautilusFileAttributes file_attributes)
+{
+ GList *node;
+
+ cancel_loading_attributes (directory, file_attributes);
+
+ for (node = directory->details->file_list; node != NULL; node = node->next)
+ {
+ nautilus_file_invalidate_attributes_internal (NAUTILUS_FILE (node->data),
+ file_attributes);
+ }
+
+ if (directory->details->as_file != NULL)
+ {
+ nautilus_file_invalidate_attributes_internal (directory->details->as_file,
+ file_attributes);
+ }
+}
+
+void
+nautilus_directory_force_reload_internal (NautilusDirectory *directory,
+ NautilusFileAttributes file_attributes)
+{
+ nautilus_profile_start (NULL);
+
+ /* invalidate attributes that are getting reloaded for all files */
+ nautilus_directory_invalidate_file_attributes (directory, file_attributes);
+
+ /* Start a new directory load. */
+ file_list_cancel (directory);
+ directory->details->directory_loaded = FALSE;
+
+ /* Start a new directory count. */
+ nautilus_directory_invalidate_count_and_mime_list (directory);
+
+ add_all_files_to_work_queue (directory);
+ nautilus_directory_async_state_changed (directory);
+
+ nautilus_profile_end (NULL);
+}
+
+static gboolean
+monitor_includes_file (const Monitor *monitor,
+ NautilusFile *file)
+{
+ if (monitor->file == file)
+ {
+ return TRUE;
+ }
+ /* monitor->file == NULL means monitor all files. */
+ if (monitor->file != NULL)
+ {
+ return FALSE;
+ }
+ if (file == file->details->directory->details->as_file)
+ {
+ return FALSE;
+ }
+ return nautilus_file_should_show (file,
+ monitor->monitor_hidden_files);
+}
+
+static gboolean
+is_wanted_by_monitor (NautilusFile *file,
+ GList *monitors,
+ RequestType request_type_wanted)
+{
+ GList *node;
+
+ for (node = monitors; node; node = node->next)
+ {
+ Monitor *monitor = node->data;
+ if (REQUEST_WANTS_TYPE (monitor->request, request_type_wanted))
+ {
+ if (monitor_includes_file (monitor, file))
+ {
+ return TRUE;
+ }
+ }
+ }
+
+ return FALSE;
+}
+
+static gboolean
+is_needy (NautilusFile *file,
+ FileCheck check_missing,
+ RequestType request_type_wanted)
+{
+ NautilusDirectory *directory;
+ GList *node;
+ ReadyCallback *callback;
+
+ if (!(*check_missing)(file))
+ {
+ return FALSE;
+ }
+
+ directory = file->details->directory;
+ if (directory->details->call_when_ready_counters[request_type_wanted] > 0)
+ {
+ for (node = directory->details->call_when_ready_list;
+ node != NULL; node = node->next)
+ {
+ callback = node->data;
+ if (callback->active &&
+ REQUEST_WANTS_TYPE (callback->request, request_type_wanted))
+ {
+ if (callback->file == file)
+ {
+ return TRUE;
+ }
+ if (callback->file == NULL
+ && file != directory->details->as_file)
+ {
+ return TRUE;
+ }
+ }
+ }
+ }
+
+ if (directory->details->monitor_counters[request_type_wanted] > 0)
+ {
+ GList *monitors;
+
+ monitors = lookup_monitors (directory->details->monitor_table, file);
+ if (is_wanted_by_monitor (file, monitors, request_type_wanted))
+ {
+ return TRUE;
+ }
+
+ monitors = lookup_all_files_monitors (directory->details->monitor_table);
+ if (is_wanted_by_monitor (file, monitors, request_type_wanted))
+ {
+ return TRUE;
+ }
+ }
+
+ return FALSE;
+}
+
+static void
+directory_count_stop (NautilusDirectory *directory)
+{
+ NautilusFile *file;
+
+ if (directory->details->count_in_progress != NULL)
+ {
+ file = directory->details->count_in_progress->count_file;
+ if (file != NULL)
+ {
+ g_assert (NAUTILUS_IS_FILE (file));
+ g_assert (file->details->directory == directory);
+ if (is_needy (file,
+ should_get_directory_count_now,
+ REQUEST_DIRECTORY_COUNT))
+ {
+ return;
+ }
+ }
+
+ /* The count is not wanted, so stop it. */
+ directory_count_cancel (directory);
+ }
+}
+
+static guint
+count_non_skipped_files (GList *list)
+{
+ guint count;
+ GList *node;
+ GFileInfo *info;
+
+ count = 0;
+ for (node = list; node != NULL; node = node->next)
+ {
+ info = node->data;
+ if (!should_skip_file (NULL, info))
+ {
+ count += 1;
+ }
+ }
+ return count;
+}
+
+static void
+count_children_done (NautilusDirectory *directory,
+ NautilusFile *count_file,
+ gboolean succeeded,
+ int count)
+{
+ g_assert (NAUTILUS_IS_FILE (count_file));
+
+ count_file->details->directory_count_is_up_to_date = TRUE;
+
+ /* Record either a failure or success. */
+ if (!succeeded)
+ {
+ count_file->details->directory_count_failed = TRUE;
+ count_file->details->got_directory_count = FALSE;
+ count_file->details->directory_count = 0;
+ }
+ else
+ {
+ count_file->details->directory_count_failed = FALSE;
+ count_file->details->got_directory_count = TRUE;
+ count_file->details->directory_count = count;
+ }
+ directory->details->count_in_progress = NULL;
+
+ /* Send file-changed even if count failed, so interested parties can
+ * distinguish between unknowable and not-yet-known cases.
+ */
+ nautilus_file_changed (count_file);
+
+ /* Start up the next one. */
+ async_job_end (directory, "directory count");
+ nautilus_directory_async_state_changed (directory);
+}
+
+static void
+directory_count_state_free (DirectoryCountState *state)
+{
+ if (state->enumerator)
+ {
+ if (!g_file_enumerator_is_closed (state->enumerator))
+ {
+ g_file_enumerator_close_async (state->enumerator,
+ 0, NULL, NULL, NULL);
+ }
+ g_object_unref (state->enumerator);
+ }
+ g_object_unref (state->cancellable);
+ nautilus_directory_unref (state->directory);
+ g_free (state);
+}
+
+static void
+count_more_files_callback (GObject *source_object,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ DirectoryCountState *state;
+ NautilusDirectory *directory;
+ GError *error;
+ GList *files;
+
+ state = user_data;
+ directory = state->directory;
+
+ if (g_cancellable_is_cancelled (state->cancellable))
+ {
+ /* Operation was cancelled. Bail out */
+
+ async_job_end (directory, "directory count");
+ nautilus_directory_async_state_changed (directory);
+
+ directory_count_state_free (state);
+
+ return;
+ }
+
+ g_assert (directory->details->count_in_progress != NULL);
+ g_assert (directory->details->count_in_progress == state);
+
+ error = NULL;
+ files = g_file_enumerator_next_files_finish (state->enumerator,
+ res, &error);
+
+ state->file_count += count_non_skipped_files (files);
+
+ if (files == NULL)
+ {
+ count_children_done (directory, state->count_file,
+ TRUE, state->file_count);
+ directory_count_state_free (state);
+ }
+ else
+ {
+ g_file_enumerator_next_files_async (state->enumerator,
+ DIRECTORY_LOAD_ITEMS_PER_CALLBACK,
+ G_PRIORITY_DEFAULT,
+ state->cancellable,
+ count_more_files_callback,
+ state);
+ }
+
+ g_list_free_full (files, g_object_unref);
+
+ if (error)
+ {
+ g_error_free (error);
+ }
+}
+
+static void
+count_children_callback (GObject *source_object,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ DirectoryCountState *state;
+ GFileEnumerator *enumerator;
+ NautilusDirectory *directory;
+ GError *error;
+
+ state = user_data;
+
+ if (g_cancellable_is_cancelled (state->cancellable))
+ {
+ /* Operation was cancelled. Bail out */
+ directory = state->directory;
+
+ async_job_end (directory, "directory count");
+ nautilus_directory_async_state_changed (directory);
+
+ directory_count_state_free (state);
+
+ return;
+ }
+
+ error = NULL;
+ enumerator = g_file_enumerate_children_finish (G_FILE (source_object),
+ res, &error);
+
+ if (enumerator == NULL)
+ {
+ count_children_done (state->directory,
+ state->count_file,
+ FALSE, 0);
+ g_error_free (error);
+ directory_count_state_free (state);
+ return;
+ }
+ else
+ {
+ state->enumerator = enumerator;
+ g_file_enumerator_next_files_async (state->enumerator,
+ DIRECTORY_LOAD_ITEMS_PER_CALLBACK,
+ G_PRIORITY_DEFAULT,
+ state->cancellable,
+ count_more_files_callback,
+ state);
+ }
+}
+
+static void
+directory_count_start (NautilusDirectory *directory,
+ NautilusFile *file,
+ gboolean *doing_io)
+{
+ DirectoryCountState *state;
+ GFile *location;
+
+ if (directory->details->count_in_progress != NULL)
+ {
+ *doing_io = TRUE;
+ return;
+ }
+
+ if (!is_needy (file,
+ should_get_directory_count_now,
+ REQUEST_DIRECTORY_COUNT))
+ {
+ return;
+ }
+ *doing_io = TRUE;
+
+ if (!nautilus_file_is_directory (file))
+ {
+ file->details->directory_count_is_up_to_date = TRUE;
+ file->details->directory_count_failed = FALSE;
+ file->details->got_directory_count = FALSE;
+
+ nautilus_directory_async_state_changed (directory);
+ return;
+ }
+
+ if (!async_job_start (directory, "directory count"))
+ {
+ return;
+ }
+
+ /* Start counting. */
+ state = g_new0 (DirectoryCountState, 1);
+ state->count_file = file;
+ state->directory = nautilus_directory_ref (directory);
+ state->cancellable = g_cancellable_new ();
+
+ directory->details->count_in_progress = state;
+
+ location = nautilus_file_get_location (file);
+
+ {
+ g_autofree char *uri = NULL;
+ uri = g_file_get_uri (location);
+ DEBUG ("load_directory called to get shallow file count for %s", uri);
+ }
+
+ g_file_enumerate_children_async (location,
+ G_FILE_ATTRIBUTE_STANDARD_NAME ","
+ G_FILE_ATTRIBUTE_STANDARD_IS_HIDDEN ","
+ G_FILE_ATTRIBUTE_STANDARD_IS_BACKUP,
+ G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS, /* flags */
+ G_PRIORITY_DEFAULT, /* prio */
+ state->cancellable,
+ count_children_callback,
+ state);
+ g_object_unref (location);
+}
+
+static inline gboolean
+seen_inode (DeepCountState *state,
+ GFileInfo *info)
+{
+ guint64 inode, inode2;
+ guint i;
+
+ inode = g_file_info_get_attribute_uint64 (info, G_FILE_ATTRIBUTE_UNIX_INODE);
+
+ if (inode != 0)
+ {
+ for (i = 0; i < state->seen_deep_count_inodes->len; i++)
+ {
+ inode2 = g_array_index (state->seen_deep_count_inodes, guint64, i);
+ if (inode == inode2)
+ {
+ return TRUE;
+ }
+ }
+ }
+
+ return FALSE;
+}
+
+static inline void
+mark_inode_as_seen (DeepCountState *state,
+ GFileInfo *info)
+{
+ guint64 inode;
+
+ inode = g_file_info_get_attribute_uint64 (info, G_FILE_ATTRIBUTE_UNIX_INODE);
+ if (inode != 0)
+ {
+ g_array_append_val (state->seen_deep_count_inodes, inode);
+ }
+}
+
+static void
+deep_count_one (DeepCountState *state,
+ GFileInfo *info)
+{
+ NautilusFile *file;
+ GFile *subdir;
+ gboolean is_seen_inode;
+ const char *fs_id;
+
+ if (should_skip_file (NULL, info))
+ {
+ return;
+ }
+
+ is_seen_inode = seen_inode (state, info);
+ if (!is_seen_inode)
+ {
+ mark_inode_as_seen (state, info);
+ }
+
+ file = state->directory->details->deep_count_file;
+
+ if (g_file_info_get_file_type (info) == G_FILE_TYPE_DIRECTORY)
+ {
+ /* Count the directory. */
+ file->details->deep_directory_count += 1;
+
+ /* Record the fact that we have to descend into this directory. */
+ fs_id = g_file_info_get_attribute_string (info, G_FILE_ATTRIBUTE_ID_FILESYSTEM);
+ if (g_strcmp0 (fs_id, state->fs_id) == 0)
+ {
+ /* only if it is on the same filesystem */
+ subdir = g_file_get_child (state->deep_count_location, g_file_info_get_name (info));
+ state->deep_count_subdirectories = g_list_prepend
+ (state->deep_count_subdirectories, subdir);
+ }
+ }
+ else
+ {
+ /* Even non-regular files count as files. */
+ file->details->deep_file_count += 1;
+ }
+
+ /* Count the size. */
+ if (!is_seen_inode && g_file_info_has_attribute (info, G_FILE_ATTRIBUTE_STANDARD_SIZE))
+ {
+ file->details->deep_size += g_file_info_get_size (info);
+ }
+}
+
+static void
+deep_count_state_free (DeepCountState *state)
+{
+ if (state->enumerator)
+ {
+ if (!g_file_enumerator_is_closed (state->enumerator))
+ {
+ g_file_enumerator_close_async (state->enumerator,
+ 0, NULL, NULL, NULL);
+ }
+ g_object_unref (state->enumerator);
+ }
+ g_object_unref (state->cancellable);
+ if (state->deep_count_location)
+ {
+ g_object_unref (state->deep_count_location);
+ }
+ g_list_free_full (state->deep_count_subdirectories, g_object_unref);
+ g_array_free (state->seen_deep_count_inodes, TRUE);
+ g_free (state->fs_id);
+ g_free (state);
+}
+
+static void
+deep_count_next_dir (DeepCountState *state)
+{
+ GFile *location;
+ NautilusFile *file;
+ NautilusDirectory *directory;
+ gboolean done;
+
+ directory = state->directory;
+
+ g_object_unref (state->deep_count_location);
+ state->deep_count_location = NULL;
+
+ done = FALSE;
+ file = directory->details->deep_count_file;
+
+ if (state->deep_count_subdirectories != NULL)
+ {
+ /* Work on a new directory. */
+ location = state->deep_count_subdirectories->data;
+ state->deep_count_subdirectories = g_list_remove
+ (state->deep_count_subdirectories, location);
+ deep_count_load (state, location);
+ g_object_unref (location);
+ }
+ else
+ {
+ file->details->deep_counts_status = NAUTILUS_REQUEST_DONE;
+ directory->details->deep_count_file = NULL;
+ directory->details->deep_count_in_progress = NULL;
+ deep_count_state_free (state);
+ done = TRUE;
+ }
+
+ nautilus_file_updated_deep_count_in_progress (file);
+
+ if (done)
+ {
+ nautilus_file_changed (file);
+ async_job_end (directory, "deep count");
+ nautilus_directory_async_state_changed (directory);
+ }
+}
+
+static void
+deep_count_more_files_callback (GObject *source_object,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ DeepCountState *state;
+ NautilusDirectory *directory;
+ GList *files, *l;
+ GFileInfo *info;
+
+ state = user_data;
+
+ if (state->directory == NULL)
+ {
+ /* Operation was cancelled. Bail out */
+ deep_count_state_free (state);
+ return;
+ }
+
+ directory = nautilus_directory_ref (state->directory);
+
+ g_assert (directory->details->deep_count_in_progress != NULL);
+ g_assert (directory->details->deep_count_in_progress == state);
+
+ files = g_file_enumerator_next_files_finish (state->enumerator,
+ res, NULL);
+
+ for (l = files; l != NULL; l = l->next)
+ {
+ info = l->data;
+ deep_count_one (state, info);
+ g_object_unref (info);
+ }
+
+ if (files == NULL)
+ {
+ g_file_enumerator_close_async (state->enumerator, 0, NULL, NULL, NULL);
+ g_object_unref (state->enumerator);
+ state->enumerator = NULL;
+
+ deep_count_next_dir (state);
+ }
+ else
+ {
+ g_file_enumerator_next_files_async (state->enumerator,
+ DIRECTORY_LOAD_ITEMS_PER_CALLBACK,
+ G_PRIORITY_LOW,
+ state->cancellable,
+ deep_count_more_files_callback,
+ state);
+ }
+
+ g_list_free (files);
+
+ nautilus_directory_unref (directory);
+}
+
+static void
+deep_count_callback (GObject *source_object,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ DeepCountState *state;
+ GFileEnumerator *enumerator;
+ NautilusFile *file;
+
+ state = user_data;
+
+ if (state->directory == NULL)
+ {
+ /* Operation was cancelled. Bail out */
+ deep_count_state_free (state);
+ return;
+ }
+
+ file = state->directory->details->deep_count_file;
+
+ enumerator = g_file_enumerate_children_finish (G_FILE (source_object), res, NULL);
+
+ if (enumerator == NULL)
+ {
+ file->details->deep_unreadable_count += 1;
+
+ deep_count_next_dir (state);
+ }
+ else
+ {
+ state->enumerator = enumerator;
+ g_file_enumerator_next_files_async (state->enumerator,
+ DIRECTORY_LOAD_ITEMS_PER_CALLBACK,
+ G_PRIORITY_LOW,
+ state->cancellable,
+ deep_count_more_files_callback,
+ state);
+ }
+}
+
+
+static void
+deep_count_load (DeepCountState *state,
+ GFile *location)
+{
+ state->deep_count_location = g_object_ref (location);
+
+ DEBUG ("load_directory called to get deep file count for %p", location);
+ g_file_enumerate_children_async (state->deep_count_location,
+ G_FILE_ATTRIBUTE_STANDARD_NAME ","
+ G_FILE_ATTRIBUTE_STANDARD_TYPE ","
+ G_FILE_ATTRIBUTE_STANDARD_SIZE ","
+ G_FILE_ATTRIBUTE_STANDARD_IS_HIDDEN ","
+ G_FILE_ATTRIBUTE_STANDARD_IS_BACKUP ","
+ G_FILE_ATTRIBUTE_ID_FILESYSTEM ","
+ G_FILE_ATTRIBUTE_UNIX_INODE,
+ G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS, /* flags */
+ G_PRIORITY_LOW, /* prio */
+ state->cancellable,
+ deep_count_callback,
+ state);
+}
+
+static void
+deep_count_stop (NautilusDirectory *directory)
+{
+ NautilusFile *file;
+
+ if (directory->details->deep_count_in_progress != NULL)
+ {
+ file = directory->details->deep_count_file;
+ if (file != NULL)
+ {
+ g_assert (NAUTILUS_IS_FILE (file));
+ g_assert (file->details->directory == directory);
+ if (is_needy (file,
+ lacks_deep_count,
+ REQUEST_DEEP_COUNT))
+ {
+ return;
+ }
+ }
+
+ /* The count is not wanted, so stop it. */
+ deep_count_cancel (directory);
+ }
+}
+
+static void
+deep_count_got_info (GObject *source_object,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ GFileInfo *info;
+ const char *id;
+ GFile *file = (GFile *) source_object;
+ DeepCountState *state = (DeepCountState *) user_data;
+
+ info = g_file_query_info_finish (file, res, NULL);
+ if (info != NULL)
+ {
+ id = g_file_info_get_attribute_string (info, G_FILE_ATTRIBUTE_ID_FILESYSTEM);
+ state->fs_id = g_strdup (id);
+ g_object_unref (info);
+ }
+ deep_count_load (state, file);
+}
+
+static void
+deep_count_start (NautilusDirectory *directory,
+ NautilusFile *file,
+ gboolean *doing_io)
+{
+ GFile *location;
+ DeepCountState *state;
+
+ if (directory->details->deep_count_in_progress != NULL)
+ {
+ *doing_io = TRUE;
+ return;
+ }
+
+ if (!is_needy (file,
+ lacks_deep_count,
+ REQUEST_DEEP_COUNT))
+ {
+ return;
+ }
+ *doing_io = TRUE;
+
+ if (!nautilus_file_is_directory (file))
+ {
+ file->details->deep_counts_status = NAUTILUS_REQUEST_DONE;
+
+ nautilus_directory_async_state_changed (directory);
+ return;
+ }
+
+ if (!async_job_start (directory, "deep count"))
+ {
+ return;
+ }
+
+ /* Start counting. */
+ file->details->deep_counts_status = NAUTILUS_REQUEST_IN_PROGRESS;
+ file->details->deep_directory_count = 0;
+ file->details->deep_file_count = 0;
+ file->details->deep_unreadable_count = 0;
+ file->details->deep_size = 0;
+ directory->details->deep_count_file = file;
+
+ state = g_new0 (DeepCountState, 1);
+ state->directory = directory;
+ state->cancellable = g_cancellable_new ();
+ state->seen_deep_count_inodes = g_array_new (FALSE, TRUE, sizeof (guint64));
+ state->fs_id = NULL;
+
+ directory->details->deep_count_in_progress = state;
+
+ location = nautilus_file_get_location (file);
+ g_file_query_info_async (location,
+ G_FILE_ATTRIBUTE_ID_FILESYSTEM,
+ G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
+ G_PRIORITY_DEFAULT,
+ NULL,
+ deep_count_got_info,
+ state);
+ g_object_unref (location);
+}
+
+static void
+mime_list_stop (NautilusDirectory *directory)
+{
+ NautilusFile *file;
+
+ if (directory->details->mime_list_in_progress != NULL)
+ {
+ file = directory->details->mime_list_in_progress->mime_list_file;
+ if (file != NULL)
+ {
+ g_assert (NAUTILUS_IS_FILE (file));
+ g_assert (file->details->directory == directory);
+ if (is_needy (file,
+ should_get_mime_list,
+ REQUEST_MIME_LIST))
+ {
+ return;
+ }
+ }
+
+ /* The count is not wanted, so stop it. */
+ mime_list_cancel (directory);
+ }
+}
+
+static void
+mime_list_state_free (MimeListState *state)
+{
+ if (state->enumerator)
+ {
+ if (!g_file_enumerator_is_closed (state->enumerator))
+ {
+ g_file_enumerator_close_async (state->enumerator,
+ 0, NULL, NULL, NULL);
+ }
+ g_object_unref (state->enumerator);
+ }
+ g_object_unref (state->cancellable);
+ istr_set_destroy (state->mime_list_hash);
+ nautilus_directory_unref (state->directory);
+ g_free (state);
+}
+
+
+static void
+mime_list_done (MimeListState *state,
+ gboolean success)
+{
+ NautilusFile *file;
+ NautilusDirectory *directory;
+
+ directory = state->directory;
+ g_assert (directory != NULL);
+
+ file = state->mime_list_file;
+
+ file->details->mime_list_is_up_to_date = TRUE;
+ g_list_free_full (file->details->mime_list, g_free);
+ if (success)
+ {
+ file->details->mime_list_failed = TRUE;
+ file->details->mime_list = NULL;
+ }
+ else
+ {
+ file->details->got_mime_list = TRUE;
+ file->details->mime_list = istr_set_get_as_list (state->mime_list_hash);
+ }
+ directory->details->mime_list_in_progress = NULL;
+
+ /* Send file-changed even if getting the item type list
+ * failed, so interested parties can distinguish between
+ * unknowable and not-yet-known cases.
+ */
+ nautilus_file_changed (file);
+
+ /* Start up the next one. */
+ async_job_end (directory, "MIME list");
+ nautilus_directory_async_state_changed (directory);
+}
+
+static void
+mime_list_one (MimeListState *state,
+ GFileInfo *info)
+{
+ const char *mime_type;
+
+ if (should_skip_file (NULL, info))
+ {
+ g_object_unref (info);
+ return;
+ }
+
+ mime_type = g_file_info_get_content_type (info);
+ if (mime_type != NULL)
+ {
+ istr_set_insert (state->mime_list_hash, mime_type);
+ }
+}
+
+static void
+mime_list_callback (GObject *source_object,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ MimeListState *state;
+ NautilusDirectory *directory;
+ GError *error;
+ GList *files, *l;
+ GFileInfo *info;
+
+ state = user_data;
+ directory = state->directory;
+
+ if (g_cancellable_is_cancelled (state->cancellable))
+ {
+ /* Operation was cancelled. Bail out */
+ directory->details->mime_list_in_progress = NULL;
+
+ async_job_end (directory, "MIME list");
+ nautilus_directory_async_state_changed (directory);
+
+ mime_list_state_free (state);
+
+ return;
+ }
+
+ g_assert (directory->details->mime_list_in_progress != NULL);
+ g_assert (directory->details->mime_list_in_progress == state);
+
+ error = NULL;
+ files = g_file_enumerator_next_files_finish (state->enumerator,
+ res, &error);
+
+ for (l = files; l != NULL; l = l->next)
+ {
+ info = l->data;
+ mime_list_one (state, info);
+ g_object_unref (info);
+ }
+
+ if (files == NULL)
+ {
+ mime_list_done (state, error != NULL);
+ mime_list_state_free (state);
+ }
+ else
+ {
+ g_file_enumerator_next_files_async (state->enumerator,
+ DIRECTORY_LOAD_ITEMS_PER_CALLBACK,
+ G_PRIORITY_DEFAULT,
+ state->cancellable,
+ mime_list_callback,
+ state);
+ }
+
+ g_list_free (files);
+
+ if (error)
+ {
+ g_error_free (error);
+ }
+}
+
+static void
+list_mime_enum_callback (GObject *source_object,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ MimeListState *state;
+ GFileEnumerator *enumerator;
+ NautilusDirectory *directory;
+ GError *error;
+
+ state = user_data;
+
+ if (g_cancellable_is_cancelled (state->cancellable))
+ {
+ /* Operation was cancelled. Bail out */
+ directory = state->directory;
+ directory->details->mime_list_in_progress = NULL;
+
+ async_job_end (directory, "MIME list");
+ nautilus_directory_async_state_changed (directory);
+
+ mime_list_state_free (state);
+
+ return;
+ }
+
+ error = NULL;
+ enumerator = g_file_enumerate_children_finish (G_FILE (source_object),
+ res, &error);
+
+ if (enumerator == NULL)
+ {
+ mime_list_done (state, FALSE);
+ g_error_free (error);
+ mime_list_state_free (state);
+ return;
+ }
+ else
+ {
+ state->enumerator = enumerator;
+ g_file_enumerator_next_files_async (state->enumerator,
+ DIRECTORY_LOAD_ITEMS_PER_CALLBACK,
+ G_PRIORITY_DEFAULT,
+ state->cancellable,
+ mime_list_callback,
+ state);
+ }
+}
+
+static void
+mime_list_start (NautilusDirectory *directory,
+ NautilusFile *file,
+ gboolean *doing_io)
+{
+ MimeListState *state;
+ GFile *location;
+
+ mime_list_stop (directory);
+
+ if (directory->details->mime_list_in_progress != NULL)
+ {
+ *doing_io = TRUE;
+ return;
+ }
+
+ /* Figure out which file to get a mime list for. */
+ if (!is_needy (file,
+ should_get_mime_list,
+ REQUEST_MIME_LIST))
+ {
+ return;
+ }
+ *doing_io = TRUE;
+
+ if (!nautilus_file_is_directory (file))
+ {
+ g_list_free (file->details->mime_list);
+ file->details->mime_list_failed = FALSE;
+ file->details->got_mime_list = FALSE;
+ file->details->mime_list_is_up_to_date = TRUE;
+
+ nautilus_directory_async_state_changed (directory);
+ return;
+ }
+
+ if (!async_job_start (directory, "MIME list"))
+ {
+ return;
+ }
+
+
+ state = g_new0 (MimeListState, 1);
+ state->mime_list_file = file;
+ state->directory = nautilus_directory_ref (directory);
+ state->cancellable = g_cancellable_new ();
+ state->mime_list_hash = istr_set_new ();
+
+ directory->details->mime_list_in_progress = state;
+
+ location = nautilus_file_get_location (file);
+
+ {
+ g_autofree char *uri = NULL;
+ uri = g_file_get_uri (location);
+ DEBUG ("load_directory called to get MIME list of %s", uri);
+ }
+
+ g_file_enumerate_children_async (location,
+ G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE,
+ 0, /* flags */
+ G_PRIORITY_LOW, /* prio */
+ state->cancellable,
+ list_mime_enum_callback,
+ state);
+ g_object_unref (location);
+}
+
+static void
+get_info_state_free (GetInfoState *state)
+{
+ g_object_unref (state->cancellable);
+ g_free (state);
+}
+
+static void
+query_info_callback (GObject *source_object,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ NautilusDirectory *directory;
+ NautilusFile *get_info_file;
+ GFileInfo *info;
+ GetInfoState *state;
+ GError *error;
+
+ state = user_data;
+
+ if (state->directory == NULL)
+ {
+ /* Operation was cancelled. Bail out */
+ get_info_state_free (state);
+ return;
+ }
+
+ directory = nautilus_directory_ref (state->directory);
+
+ get_info_file = directory->details->get_info_file;
+ g_assert (NAUTILUS_IS_FILE (get_info_file));
+
+ directory->details->get_info_file = NULL;
+ directory->details->get_info_in_progress = NULL;
+
+ /* ref here because we might be removing the last ref when we
+ * mark the file gone below, but we need to keep a ref at
+ * least long enough to send the change notification.
+ */
+ nautilus_file_ref (get_info_file);
+
+ error = NULL;
+ info = g_file_query_info_finish (G_FILE (source_object), res, &error);
+
+ if (info == NULL)
+ {
+ if (error->domain == G_IO_ERROR && error->code == G_IO_ERROR_NOT_FOUND)
+ {
+ /* mark file as gone */
+ nautilus_file_mark_gone (get_info_file);
+ }
+ get_info_file->details->file_info_is_up_to_date = TRUE;
+ nautilus_file_clear_info (get_info_file);
+ get_info_file->details->get_info_failed = TRUE;
+ get_info_file->details->get_info_error = error;
+ }
+ else
+ {
+ nautilus_file_update_info (get_info_file, info);
+ g_object_unref (info);
+ }
+
+ nautilus_file_changed (get_info_file);
+ nautilus_file_unref (get_info_file);
+
+ async_job_end (directory, "file info");
+ nautilus_directory_async_state_changed (directory);
+
+ nautilus_directory_unref (directory);
+
+ get_info_state_free (state);
+}
+
+static void
+file_info_stop (NautilusDirectory *directory)
+{
+ NautilusFile *file;
+
+ if (directory->details->get_info_in_progress != NULL)
+ {
+ file = directory->details->get_info_file;
+ if (file != NULL)
+ {
+ g_assert (NAUTILUS_IS_FILE (file));
+ g_assert (file->details->directory == directory);
+ if (is_needy (file, lacks_info, REQUEST_FILE_INFO))
+ {
+ return;
+ }
+ }
+
+ /* The info is not wanted, so stop it. */
+ file_info_cancel (directory);
+ }
+}
+
+static void
+file_info_start (NautilusDirectory *directory,
+ NautilusFile *file,
+ gboolean *doing_io)
+{
+ GFile *location;
+ GetInfoState *state;
+
+ file_info_stop (directory);
+
+ if (directory->details->get_info_in_progress != NULL)
+ {
+ *doing_io = TRUE;
+ return;
+ }
+
+ if (!is_needy (file, lacks_info, REQUEST_FILE_INFO))
+ {
+ return;
+ }
+ *doing_io = TRUE;
+
+ if (!async_job_start (directory, "file info"))
+ {
+ return;
+ }
+
+ directory->details->get_info_file = file;
+ file->details->get_info_failed = FALSE;
+ if (file->details->get_info_error)
+ {
+ g_error_free (file->details->get_info_error);
+ file->details->get_info_error = NULL;
+ }
+
+ state = g_new (GetInfoState, 1);
+ state->directory = directory;
+ state->cancellable = g_cancellable_new ();
+
+ directory->details->get_info_in_progress = state;
+
+ location = nautilus_file_get_location (file);
+ g_file_query_info_async (location,
+ NAUTILUS_FILE_DEFAULT_ATTRIBUTES,
+ 0,
+ G_PRIORITY_DEFAULT,
+ state->cancellable, query_info_callback, state);
+ g_object_unref (location);
+}
+
+static void
+thumbnail_done (NautilusDirectory *directory,
+ NautilusFile *file,
+ GdkPixbuf *pixbuf)
+{
+ const char *thumb_mtime_str;
+ time_t thumb_mtime = 0;
+
+ file->details->thumbnail_is_up_to_date = TRUE;
+ if (file->details->thumbnail)
+ {
+ g_object_unref (file->details->thumbnail);
+ file->details->thumbnail = NULL;
+ }
+
+ if (pixbuf)
+ {
+ thumb_mtime_str = gdk_pixbuf_get_option (pixbuf, "tEXt::Thumb::MTime");
+ if (thumb_mtime_str)
+ {
+ thumb_mtime = atol (thumb_mtime_str);
+ }
+
+ if (thumb_mtime == 0 ||
+ thumb_mtime == file->details->mtime)
+ {
+ file->details->thumbnail = g_object_ref (pixbuf);
+ file->details->thumbnail_mtime = thumb_mtime;
+ }
+ else
+ {
+ g_free (file->details->thumbnail_path);
+ file->details->thumbnail_path = NULL;
+ }
+ }
+
+ nautilus_directory_async_state_changed (directory);
+}
+
+static void
+thumbnail_stop (NautilusDirectory *directory)
+{
+ NautilusFile *file;
+
+ if (directory->details->thumbnail_state != NULL)
+ {
+ file = directory->details->thumbnail_state->file;
+
+ if (file != NULL)
+ {
+ g_assert (NAUTILUS_IS_FILE (file));
+ g_assert (file->details->directory == directory);
+ if (is_needy (file,
+ lacks_thumbnail,
+ REQUEST_THUMBNAIL))
+ {
+ return;
+ }
+ }
+
+ /* The link info is not wanted, so stop it. */
+ thumbnail_cancel (directory);
+ }
+}
+
+static void
+thumbnail_got_pixbuf (NautilusDirectory *directory,
+ NautilusFile *file,
+ GdkPixbuf *pixbuf)
+{
+ nautilus_directory_ref (directory);
+
+ nautilus_file_ref (file);
+ thumbnail_done (directory, file, pixbuf);
+ nautilus_file_changed (file);
+ nautilus_file_unref (file);
+
+ if (pixbuf)
+ {
+ g_object_unref (pixbuf);
+ }
+
+ nautilus_directory_unref (directory);
+}
+
+static void
+thumbnail_state_free (ThumbnailState *state)
+{
+ g_object_unref (state->cancellable);
+ g_free (state);
+}
+
+/* scale very large images down to the max. size we need */
+static void
+thumbnail_loader_size_prepared (GdkPixbufLoader *loader,
+ int width,
+ int height,
+ gpointer user_data)
+{
+ int max_thumbnail_size;
+ double aspect_ratio;
+
+ aspect_ratio = ((double) width) / height;
+
+ /* cf. nautilus_file_get_icon() */
+ max_thumbnail_size = NAUTILUS_GRID_ICON_SIZE_EXTRA_LARGE * NAUTILUS_GRID_ICON_SIZE_MEDIUM / NAUTILUS_GRID_ICON_SIZE_SMALL;
+ if (MAX (width, height) > max_thumbnail_size)
+ {
+ if (width > height)
+ {
+ width = max_thumbnail_size;
+ height = width / aspect_ratio;
+ }
+ else
+ {
+ height = max_thumbnail_size;
+ width = height * aspect_ratio;
+ }
+
+ gdk_pixbuf_loader_set_size (loader, width, height);
+ }
+}
+
+static GdkPixbuf *
+get_pixbuf_for_content (goffset file_len,
+ char *file_contents)
+{
+ gboolean res;
+ GdkPixbuf *pixbuf, *pixbuf2;
+ GdkPixbufLoader *loader;
+ gsize chunk_len;
+ pixbuf = NULL;
+
+ loader = gdk_pixbuf_loader_new ();
+ g_signal_connect (loader, "size-prepared",
+ G_CALLBACK (thumbnail_loader_size_prepared),
+ NULL);
+
+ /* For some reason we have to write in chunks, or gdk-pixbuf fails */
+ res = TRUE;
+ while (res && file_len > 0)
+ {
+ chunk_len = file_len;
+ res = gdk_pixbuf_loader_write (loader, (guchar *) file_contents, chunk_len, NULL);
+ file_contents += chunk_len;
+ file_len -= chunk_len;
+ }
+ if (res)
+ {
+ res = gdk_pixbuf_loader_close (loader, NULL);
+ }
+ if (res)
+ {
+ pixbuf = g_object_ref (gdk_pixbuf_loader_get_pixbuf (loader));
+ }
+ g_object_unref (G_OBJECT (loader));
+
+ if (pixbuf)
+ {
+ pixbuf2 = gdk_pixbuf_apply_embedded_orientation (pixbuf);
+ g_object_unref (pixbuf);
+ pixbuf = pixbuf2;
+ }
+ return pixbuf;
+}
+
+
+static void
+thumbnail_read_callback (GObject *source_object,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ ThumbnailState *state;
+ gsize file_size;
+ char *file_contents;
+ gboolean result;
+ NautilusDirectory *directory;
+ GdkPixbuf *pixbuf;
+
+ state = user_data;
+
+ if (state->directory == NULL)
+ {
+ /* Operation was cancelled. Bail out */
+ thumbnail_state_free (state);
+ return;
+ }
+
+ directory = nautilus_directory_ref (state->directory);
+
+ result = g_file_load_contents_finish (G_FILE (source_object),
+ res,
+ &file_contents, &file_size,
+ NULL, NULL);
+
+ pixbuf = NULL;
+ if (result)
+ {
+ pixbuf = get_pixbuf_for_content (file_size, file_contents);
+ g_free (file_contents);
+ }
+
+ state->directory->details->thumbnail_state = NULL;
+ async_job_end (state->directory, "thumbnail");
+
+ thumbnail_got_pixbuf (state->directory, state->file, pixbuf);
+
+ thumbnail_state_free (state);
+
+ nautilus_directory_unref (directory);
+}
+
+static void
+thumbnail_start (NautilusDirectory *directory,
+ NautilusFile *file,
+ gboolean *doing_io)
+{
+ GFile *location;
+ ThumbnailState *state;
+
+ if (directory->details->thumbnail_state != NULL)
+ {
+ *doing_io = TRUE;
+ return;
+ }
+
+ if (!is_needy (file,
+ lacks_thumbnail,
+ REQUEST_THUMBNAIL))
+ {
+ return;
+ }
+ *doing_io = TRUE;
+
+ if (!async_job_start (directory, "thumbnail"))
+ {
+ return;
+ }
+
+ state = g_new0 (ThumbnailState, 1);
+ state->directory = directory;
+ state->file = file;
+ state->cancellable = g_cancellable_new ();
+
+ location = g_file_new_for_path (file->details->thumbnail_path);
+
+ directory->details->thumbnail_state = state;
+
+ g_file_load_contents_async (location,
+ state->cancellable,
+ thumbnail_read_callback,
+ state);
+ g_object_unref (location);
+}
+
+static void
+mount_stop (NautilusDirectory *directory)
+{
+ NautilusFile *file;
+
+ if (directory->details->mount_state != NULL)
+ {
+ file = directory->details->mount_state->file;
+
+ if (file != NULL)
+ {
+ g_assert (NAUTILUS_IS_FILE (file));
+ g_assert (file->details->directory == directory);
+ if (is_needy (file,
+ lacks_mount,
+ REQUEST_MOUNT))
+ {
+ return;
+ }
+ }
+
+ /* The link info is not wanted, so stop it. */
+ mount_cancel (directory);
+ }
+}
+
+static void
+mount_state_free (MountState *state)
+{
+ g_object_unref (state->cancellable);
+ g_free (state);
+}
+
+static void
+got_mount (MountState *state,
+ GMount *mount)
+{
+ NautilusDirectory *directory;
+ NautilusFile *file;
+
+ directory = nautilus_directory_ref (state->directory);
+
+ state->directory->details->mount_state = NULL;
+ async_job_end (state->directory, "mount");
+
+ file = nautilus_file_ref (state->file);
+
+ file->details->mount_is_up_to_date = TRUE;
+ nautilus_file_set_mount (file, mount);
+
+ nautilus_directory_async_state_changed (directory);
+ nautilus_file_changed (file);
+
+ nautilus_file_unref (file);
+
+ nautilus_directory_unref (directory);
+
+ mount_state_free (state);
+}
+
+static void
+find_enclosing_mount_callback (GObject *source_object,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ GMount *mount;
+ MountState *state;
+ GFile *location, *root;
+
+ state = user_data;
+ if (state->directory == NULL)
+ {
+ /* Operation was cancelled. Bail out */
+ mount_state_free (state);
+ return;
+ }
+
+ mount = g_file_find_enclosing_mount_finish (G_FILE (source_object),
+ res, NULL);
+
+ if (mount)
+ {
+ root = g_mount_get_root (mount);
+ location = nautilus_file_get_location (state->file);
+ if (!g_file_equal (location, root))
+ {
+ g_object_unref (mount);
+ mount = NULL;
+ }
+ g_object_unref (root);
+ g_object_unref (location);
+ }
+
+ got_mount (state, mount);
+
+ if (mount)
+ {
+ g_object_unref (mount);
+ }
+}
+
+static GMount *
+get_mount_at (GFile *target)
+{
+ GVolumeMonitor *monitor;
+ GFile *root;
+ GList *mounts, *l;
+ GMount *found;
+
+ monitor = g_volume_monitor_get ();
+ mounts = g_volume_monitor_get_mounts (monitor);
+
+ found = NULL;
+ for (l = mounts; l != NULL; l = l->next)
+ {
+ GMount *mount = G_MOUNT (l->data);
+
+ if (g_mount_is_shadowed (mount))
+ {
+ continue;
+ }
+
+ root = g_mount_get_root (mount);
+
+ if (g_file_equal (target, root))
+ {
+ found = g_object_ref (mount);
+ break;
+ }
+
+ g_object_unref (root);
+ }
+
+ g_list_free_full (mounts, g_object_unref);
+
+ g_object_unref (monitor);
+
+ return found;
+}
+
+static void
+mount_start (NautilusDirectory *directory,
+ NautilusFile *file,
+ gboolean *doing_io)
+{
+ GFile *location;
+ MountState *state;
+
+ if (directory->details->mount_state != NULL)
+ {
+ *doing_io = TRUE;
+ return;
+ }
+
+ if (!is_needy (file,
+ lacks_mount,
+ REQUEST_MOUNT))
+ {
+ return;
+ }
+ *doing_io = TRUE;
+
+ if (!async_job_start (directory, "mount"))
+ {
+ return;
+ }
+
+ state = g_new0 (MountState, 1);
+ state->directory = directory;
+ state->file = file;
+ state->cancellable = g_cancellable_new ();
+
+ location = nautilus_file_get_location (file);
+
+ directory->details->mount_state = state;
+
+ if (file->details->type == G_FILE_TYPE_MOUNTABLE)
+ {
+ GFile *target;
+ GMount *mount;
+
+ mount = NULL;
+ target = nautilus_file_get_activation_location (file);
+ if (target != NULL)
+ {
+ mount = get_mount_at (target);
+ g_object_unref (target);
+ }
+
+ got_mount (state, mount);
+
+ if (mount)
+ {
+ g_object_unref (mount);
+ }
+ }
+ else
+ {
+ g_file_find_enclosing_mount_async (location,
+ G_PRIORITY_DEFAULT,
+ state->cancellable,
+ find_enclosing_mount_callback,
+ state);
+ }
+ g_object_unref (location);
+}
+
+static void
+filesystem_info_cancel (NautilusDirectory *directory)
+{
+ if (directory->details->filesystem_info_state != NULL)
+ {
+ g_cancellable_cancel (directory->details->filesystem_info_state->cancellable);
+ directory->details->filesystem_info_state->directory = NULL;
+ directory->details->filesystem_info_state = NULL;
+ async_job_end (directory, "filesystem info");
+ }
+}
+
+static void
+filesystem_info_stop (NautilusDirectory *directory)
+{
+ NautilusFile *file;
+
+ if (directory->details->filesystem_info_state != NULL)
+ {
+ file = directory->details->filesystem_info_state->file;
+
+ if (file != NULL)
+ {
+ g_assert (NAUTILUS_IS_FILE (file));
+ g_assert (file->details->directory == directory);
+ if (is_needy (file,
+ lacks_filesystem_info,
+ REQUEST_FILESYSTEM_INFO))
+ {
+ return;
+ }
+ }
+
+ /* The filesystem info is not wanted, so stop it. */
+ filesystem_info_cancel (directory);
+ }
+}
+
+static void
+filesystem_info_state_free (FilesystemInfoState *state)
+{
+ g_object_unref (state->cancellable);
+ g_free (state);
+}
+
+static void
+got_filesystem_info (FilesystemInfoState *state,
+ GFileInfo *info)
+{
+ NautilusDirectory *directory;
+ NautilusFile *file;
+ const char *filesystem_type;
+
+ /* careful here, info may be NULL */
+
+ directory = nautilus_directory_ref (state->directory);
+
+ state->directory->details->filesystem_info_state = NULL;
+ async_job_end (state->directory, "filesystem info");
+
+ file = nautilus_file_ref (state->file);
+
+ file->details->filesystem_info_is_up_to_date = TRUE;
+ if (info != NULL)
+ {
+ file->details->filesystem_use_preview =
+ g_file_info_get_attribute_uint32 (info, G_FILE_ATTRIBUTE_FILESYSTEM_USE_PREVIEW);
+ file->details->filesystem_readonly =
+ g_file_info_get_attribute_boolean (info, G_FILE_ATTRIBUTE_FILESYSTEM_READONLY);
+ filesystem_type = g_file_info_get_attribute_string (info, G_FILE_ATTRIBUTE_FILESYSTEM_TYPE);
+ file->details->filesystem_remote = g_file_info_get_attribute_boolean (info, G_FILE_ATTRIBUTE_FILESYSTEM_REMOTE);
+ if (g_strcmp0 (file->details->filesystem_type, filesystem_type) != 0)
+ {
+ g_clear_pointer (&file->details->filesystem_type, g_ref_string_release);
+ file->details->filesystem_type = g_ref_string_new_intern (filesystem_type);
+ }
+ }
+
+ nautilus_directory_async_state_changed (directory);
+ nautilus_file_changed (file);
+
+ nautilus_file_unref (file);
+
+ nautilus_directory_unref (directory);
+
+ filesystem_info_state_free (state);
+}
+
+static void
+query_filesystem_info_callback (GObject *source_object,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ GFileInfo *info;
+ FilesystemInfoState *state;
+
+ state = user_data;
+ if (state->directory == NULL)
+ {
+ /* Operation was cancelled. Bail out */
+ filesystem_info_state_free (state);
+ return;
+ }
+
+ info = g_file_query_filesystem_info_finish (G_FILE (source_object), res, NULL);
+
+ got_filesystem_info (state, info);
+
+ if (info != NULL)
+ {
+ g_object_unref (info);
+ }
+}
+
+static void
+filesystem_info_start (NautilusDirectory *directory,
+ NautilusFile *file,
+ gboolean *doing_io)
+{
+ GFile *location;
+ FilesystemInfoState *state;
+
+ if (directory->details->filesystem_info_state != NULL)
+ {
+ *doing_io = TRUE;
+ return;
+ }
+
+ if (!is_needy (file,
+ lacks_filesystem_info,
+ REQUEST_FILESYSTEM_INFO))
+ {
+ return;
+ }
+ *doing_io = TRUE;
+
+ if (!async_job_start (directory, "filesystem info"))
+ {
+ return;
+ }
+
+ state = g_new0 (FilesystemInfoState, 1);
+ state->directory = directory;
+ state->file = file;
+ state->cancellable = g_cancellable_new ();
+
+ location = nautilus_file_get_location (file);
+
+ directory->details->filesystem_info_state = state;
+
+ g_file_query_filesystem_info_async (location,
+ G_FILE_ATTRIBUTE_FILESYSTEM_READONLY ","
+ G_FILE_ATTRIBUTE_FILESYSTEM_USE_PREVIEW ","
+ G_FILE_ATTRIBUTE_FILESYSTEM_TYPE ","
+ G_FILE_ATTRIBUTE_FILESYSTEM_REMOTE,
+ G_PRIORITY_DEFAULT,
+ state->cancellable,
+ query_filesystem_info_callback,
+ state);
+ g_object_unref (location);
+}
+
+static void
+extension_info_cancel (NautilusDirectory *directory)
+{
+ if (directory->details->extension_info_in_progress != NULL)
+ {
+ if (directory->details->extension_info_idle)
+ {
+ g_source_remove (directory->details->extension_info_idle);
+ }
+ else
+ {
+ nautilus_info_provider_cancel_update
+ (directory->details->extension_info_provider,
+ directory->details->extension_info_in_progress);
+ }
+
+ directory->details->extension_info_in_progress = NULL;
+ directory->details->extension_info_file = NULL;
+ directory->details->extension_info_provider = NULL;
+ directory->details->extension_info_idle = 0;
+
+ async_job_end (directory, "extension info");
+ }
+}
+
+static void
+extension_info_stop (NautilusDirectory *directory)
+{
+ if (directory->details->extension_info_in_progress != NULL)
+ {
+ NautilusFile *file;
+
+ file = directory->details->extension_info_file;
+ if (file != NULL)
+ {
+ g_assert (NAUTILUS_IS_FILE (file));
+ g_assert (file->details->directory == directory);
+ if (is_needy (file, lacks_extension_info, REQUEST_EXTENSION_INFO))
+ {
+ return;
+ }
+ }
+
+ /* The info is not wanted, so stop it. */
+ extension_info_cancel (directory);
+ }
+}
+
+static void
+finish_info_provider (NautilusDirectory *directory,
+ NautilusFile *file,
+ NautilusInfoProvider *provider)
+{
+ file->details->pending_info_providers =
+ g_list_remove (file->details->pending_info_providers,
+ provider);
+ g_object_unref (provider);
+
+ nautilus_directory_async_state_changed (directory);
+
+ if (file->details->pending_info_providers == NULL)
+ {
+ nautilus_file_info_providers_done (file);
+ }
+}
+
+
+static gboolean
+info_provider_idle_callback (gpointer user_data)
+{
+ InfoProviderResponse *response;
+ NautilusDirectory *directory;
+
+ response = user_data;
+ directory = response->directory;
+
+ if (response->handle != directory->details->extension_info_in_progress
+ || response->provider != directory->details->extension_info_provider)
+ {
+ g_warning ("Unexpected plugin response. This probably indicates a bug in a Nautilus extension: handle=%p", response->handle);
+ }
+ else
+ {
+ NautilusFile *file;
+ async_job_end (directory, "extension info");
+
+ file = directory->details->extension_info_file;
+
+ directory->details->extension_info_file = NULL;
+ directory->details->extension_info_provider = NULL;
+ directory->details->extension_info_in_progress = NULL;
+ directory->details->extension_info_idle = 0;
+
+ finish_info_provider (directory, file, response->provider);
+ }
+
+ return FALSE;
+}
+
+static void
+info_provider_callback (NautilusInfoProvider *provider,
+ NautilusOperationHandle *handle,
+ NautilusOperationResult result,
+ gpointer user_data)
+{
+ InfoProviderResponse *response;
+
+ response = g_new0 (InfoProviderResponse, 1);
+ response->provider = provider;
+ response->handle = handle;
+ response->result = result;
+ response->directory = NAUTILUS_DIRECTORY (user_data);
+
+ response->directory->details->extension_info_idle =
+ g_idle_add_full (G_PRIORITY_DEFAULT_IDLE,
+ info_provider_idle_callback, response,
+ g_free);
+}
+
+static void
+extension_info_start (NautilusDirectory *directory,
+ NautilusFile *file,
+ gboolean *doing_io)
+{
+ NautilusInfoProvider *provider;
+ NautilusOperationResult result;
+ NautilusOperationHandle *handle;
+ GClosure *update_complete;
+
+ if (directory->details->extension_info_in_progress != NULL)
+ {
+ *doing_io = TRUE;
+ return;
+ }
+
+ if (!is_needy (file, lacks_extension_info, REQUEST_EXTENSION_INFO))
+ {
+ return;
+ }
+ *doing_io = TRUE;
+
+ if (!async_job_start (directory, "extension info"))
+ {
+ return;
+ }
+
+ provider = file->details->pending_info_providers->data;
+
+ update_complete = g_cclosure_new (G_CALLBACK (info_provider_callback),
+ directory,
+ NULL);
+ g_closure_set_marshal (update_complete,
+ g_cclosure_marshal_generic);
+
+ result = nautilus_info_provider_update_file_info
+ (provider,
+ NAUTILUS_FILE_INFO (file),
+ update_complete,
+ &handle);
+
+ g_closure_unref (update_complete);
+
+ if (result == NAUTILUS_OPERATION_COMPLETE ||
+ result == NAUTILUS_OPERATION_FAILED)
+ {
+ finish_info_provider (directory, file, provider);
+ async_job_end (directory, "extension info");
+ }
+ else
+ {
+ directory->details->extension_info_in_progress = handle;
+ directory->details->extension_info_provider = provider;
+ directory->details->extension_info_file = file;
+ }
+}
+
+static void
+start_or_stop_io (NautilusDirectory *directory)
+{
+ NautilusFile *file;
+ gboolean doing_io;
+
+ /* Start or stop reading files. */
+ file_list_start_or_stop (directory);
+
+ /* Stop any no longer wanted attribute fetches. */
+ file_info_stop (directory);
+ directory_count_stop (directory);
+ deep_count_stop (directory);
+ mime_list_stop (directory);
+ extension_info_stop (directory);
+ mount_stop (directory);
+ thumbnail_stop (directory);
+ filesystem_info_stop (directory);
+
+ doing_io = FALSE;
+ /* Take files that are all done off the queue. */
+ while (!nautilus_file_queue_is_empty (directory->details->high_priority_queue))
+ {
+ file = nautilus_file_queue_head (directory->details->high_priority_queue);
+
+ /* Start getting attributes if possible */
+ file_info_start (directory, file, &doing_io);
+
+ if (doing_io)
+ {
+ return;
+ }
+
+ move_file_to_low_priority_queue (directory, file);
+ }
+
+ /* High priority queue must be empty */
+ while (!nautilus_file_queue_is_empty (directory->details->low_priority_queue))
+ {
+ file = nautilus_file_queue_head (directory->details->low_priority_queue);
+
+ /* Start getting attributes if possible */
+ mount_start (directory, file, &doing_io);
+ directory_count_start (directory, file, &doing_io);
+ deep_count_start (directory, file, &doing_io);
+ mime_list_start (directory, file, &doing_io);
+ thumbnail_start (directory, file, &doing_io);
+ filesystem_info_start (directory, file, &doing_io);
+
+ if (doing_io)
+ {
+ return;
+ }
+
+ move_file_to_extension_queue (directory, file);
+ }
+
+ /* Low priority queue must be empty */
+ while (!nautilus_file_queue_is_empty (directory->details->extension_queue))
+ {
+ file = nautilus_file_queue_head (directory->details->extension_queue);
+
+ /* Start getting attributes if possible */
+ extension_info_start (directory, file, &doing_io);
+ if (doing_io)
+ {
+ return;
+ }
+
+ nautilus_directory_remove_file_from_work_queue (directory, file);
+ }
+}
+
+/* Call this when the monitor or call when ready list changes,
+ * or when some I/O is completed.
+ */
+void
+nautilus_directory_async_state_changed (NautilusDirectory *directory)
+{
+ /* Check if any callbacks are satisfied and call them if they
+ * are. Do this last so that any changes done in start or stop
+ * I/O functions immediately (not in callbacks) are taken into
+ * consideration. If any callbacks are called, consider the
+ * I/O state again so that we can release or cancel I/O that
+ * is not longer needed once the callbacks are satisfied.
+ */
+
+ if (directory->details->in_async_service_loop)
+ {
+ directory->details->state_changed = TRUE;
+ return;
+ }
+ directory->details->in_async_service_loop = TRUE;
+ nautilus_directory_ref (directory);
+ do
+ {
+ directory->details->state_changed = FALSE;
+ start_or_stop_io (directory);
+ if (call_ready_callbacks (directory))
+ {
+ directory->details->state_changed = TRUE;
+ }
+ }
+ while (directory->details->state_changed);
+ directory->details->in_async_service_loop = FALSE;
+ nautilus_directory_unref (directory);
+
+ /* Check if any directories should wake up. */
+ async_job_wake_up ();
+}
+
+void
+nautilus_directory_cancel (NautilusDirectory *directory)
+{
+ /* Arbitrary order (kept alphabetical). */
+ deep_count_cancel (directory);
+ directory_count_cancel (directory);
+ file_info_cancel (directory);
+ file_list_cancel (directory);
+ mime_list_cancel (directory);
+ new_files_cancel (directory);
+ extension_info_cancel (directory);
+ thumbnail_cancel (directory);
+ mount_cancel (directory);
+ filesystem_info_cancel (directory);
+
+ /* We aren't waiting for anything any more. */
+ if (waiting_directories != NULL)
+ {
+ g_hash_table_remove (waiting_directories, directory);
+ }
+
+ /* Check if any directories should wake up. */
+ async_job_wake_up ();
+}
+
+static void
+cancel_directory_count_for_file (NautilusDirectory *directory,
+ NautilusFile *file)
+{
+ if (directory->details->count_in_progress != NULL &&
+ directory->details->count_in_progress->count_file == file)
+ {
+ directory_count_cancel (directory);
+ }
+}
+
+static void
+cancel_deep_counts_for_file (NautilusDirectory *directory,
+ NautilusFile *file)
+{
+ if (directory->details->deep_count_file == file)
+ {
+ deep_count_cancel (directory);
+ }
+}
+
+static void
+cancel_mime_list_for_file (NautilusDirectory *directory,
+ NautilusFile *file)
+{
+ if (directory->details->mime_list_in_progress != NULL &&
+ directory->details->mime_list_in_progress->mime_list_file == file)
+ {
+ mime_list_cancel (directory);
+ }
+}
+
+static void
+cancel_file_info_for_file (NautilusDirectory *directory,
+ NautilusFile *file)
+{
+ if (directory->details->get_info_file == file)
+ {
+ file_info_cancel (directory);
+ }
+}
+
+static void
+cancel_thumbnail_for_file (NautilusDirectory *directory,
+ NautilusFile *file)
+{
+ if (directory->details->thumbnail_state != NULL &&
+ directory->details->thumbnail_state->file == file)
+ {
+ thumbnail_cancel (directory);
+ }
+}
+
+static void
+cancel_mount_for_file (NautilusDirectory *directory,
+ NautilusFile *file)
+{
+ if (directory->details->mount_state != NULL &&
+ directory->details->mount_state->file == file)
+ {
+ mount_cancel (directory);
+ }
+}
+
+static void
+cancel_filesystem_info_for_file (NautilusDirectory *directory,
+ NautilusFile *file)
+{
+ if (directory->details->filesystem_info_state != NULL &&
+ directory->details->filesystem_info_state->file == file)
+ {
+ filesystem_info_cancel (directory);
+ }
+}
+
+static void
+cancel_loading_attributes (NautilusDirectory *directory,
+ NautilusFileAttributes file_attributes)
+{
+ Request request;
+
+ request = nautilus_directory_set_up_request (file_attributes);
+
+ if (REQUEST_WANTS_TYPE (request, REQUEST_DIRECTORY_COUNT))
+ {
+ directory_count_cancel (directory);
+ }
+ if (REQUEST_WANTS_TYPE (request, REQUEST_DEEP_COUNT))
+ {
+ deep_count_cancel (directory);
+ }
+ if (REQUEST_WANTS_TYPE (request, REQUEST_MIME_LIST))
+ {
+ mime_list_cancel (directory);
+ }
+ if (REQUEST_WANTS_TYPE (request, REQUEST_FILE_INFO))
+ {
+ file_info_cancel (directory);
+ }
+ if (REQUEST_WANTS_TYPE (request, REQUEST_FILESYSTEM_INFO))
+ {
+ filesystem_info_cancel (directory);
+ }
+ if (REQUEST_WANTS_TYPE (request, REQUEST_EXTENSION_INFO))
+ {
+ extension_info_cancel (directory);
+ }
+
+ if (REQUEST_WANTS_TYPE (request, REQUEST_THUMBNAIL))
+ {
+ thumbnail_cancel (directory);
+ }
+
+ if (REQUEST_WANTS_TYPE (request, REQUEST_MOUNT))
+ {
+ mount_cancel (directory);
+ }
+
+ nautilus_directory_async_state_changed (directory);
+}
+
+void
+nautilus_directory_cancel_loading_file_attributes (NautilusDirectory *directory,
+ NautilusFile *file,
+ NautilusFileAttributes file_attributes)
+{
+ Request request;
+
+ nautilus_directory_remove_file_from_work_queue (directory, file);
+
+ request = nautilus_directory_set_up_request (file_attributes);
+
+ if (REQUEST_WANTS_TYPE (request, REQUEST_DIRECTORY_COUNT))
+ {
+ cancel_directory_count_for_file (directory, file);
+ }
+ if (REQUEST_WANTS_TYPE (request, REQUEST_DEEP_COUNT))
+ {
+ cancel_deep_counts_for_file (directory, file);
+ }
+ if (REQUEST_WANTS_TYPE (request, REQUEST_MIME_LIST))
+ {
+ cancel_mime_list_for_file (directory, file);
+ }
+ if (REQUEST_WANTS_TYPE (request, REQUEST_FILE_INFO))
+ {
+ cancel_file_info_for_file (directory, file);
+ }
+ if (REQUEST_WANTS_TYPE (request, REQUEST_FILESYSTEM_INFO))
+ {
+ cancel_filesystem_info_for_file (directory, file);
+ }
+ if (REQUEST_WANTS_TYPE (request, REQUEST_THUMBNAIL))
+ {
+ cancel_thumbnail_for_file (directory, file);
+ }
+ if (REQUEST_WANTS_TYPE (request, REQUEST_MOUNT))
+ {
+ cancel_mount_for_file (directory, file);
+ }
+
+ nautilus_directory_async_state_changed (directory);
+}
+
+void
+nautilus_directory_add_file_to_work_queue (NautilusDirectory *directory,
+ NautilusFile *file)
+{
+ g_return_if_fail (file->details->directory == directory);
+
+ nautilus_file_queue_enqueue (directory->details->high_priority_queue,
+ file);
+}
+
+
+static void
+add_all_files_to_work_queue (NautilusDirectory *directory)
+{
+ GList *node;
+ NautilusFile *file;
+
+ for (node = directory->details->file_list; node != NULL; node = node->next)
+ {
+ file = NAUTILUS_FILE (node->data);
+
+ nautilus_directory_add_file_to_work_queue (directory, file);
+ }
+}
+
+void
+nautilus_directory_remove_file_from_work_queue (NautilusDirectory *directory,
+ NautilusFile *file)
+{
+ nautilus_file_queue_remove (directory->details->high_priority_queue,
+ file);
+ nautilus_file_queue_remove (directory->details->low_priority_queue,
+ file);
+ nautilus_file_queue_remove (directory->details->extension_queue,
+ file);
+}
+
+
+static void
+move_file_to_low_priority_queue (NautilusDirectory *directory,
+ NautilusFile *file)
+{
+ /* Must add before removing to avoid ref underflow */
+ nautilus_file_queue_enqueue (directory->details->low_priority_queue,
+ file);
+ nautilus_file_queue_remove (directory->details->high_priority_queue,
+ file);
+}
+
+static void
+move_file_to_extension_queue (NautilusDirectory *directory,
+ NautilusFile *file)
+{
+ /* Must add before removing to avoid ref underflow */
+ nautilus_file_queue_enqueue (directory->details->extension_queue,
+ file);
+ nautilus_file_queue_remove (directory->details->low_priority_queue,
+ file);
+}
diff --git a/src/nautilus-directory-notify.h b/src/nautilus-directory-notify.h
new file mode 100644
index 0000000..4b5030d
--- /dev/null
+++ b/src/nautilus-directory-notify.h
@@ -0,0 +1,57 @@
+/*
+ nautilus-directory-notify.h: Nautilus directory notify calls.
+
+ 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/>.
+
+ Author: Darin Adler <darin@bentspoon.com>
+*/
+
+#pragma once
+
+#include <gio/gio.h>
+
+#include "nautilus-types.h"
+
+typedef struct {
+ char *from_uri;
+ char *to_uri;
+} URIPair;
+
+typedef struct {
+ GFile *from;
+ GFile *to;
+} GFilePair;
+
+/* Almost-public change notification calls */
+void nautilus_directory_notify_files_added (GList *files);
+void nautilus_directory_notify_files_moved (GList *file_pairs);
+void nautilus_directory_notify_files_changed (GList *files);
+void nautilus_directory_notify_files_removed (GList *files);
+
+void nautilus_directory_schedule_metadata_copy (GList *file_pairs);
+void nautilus_directory_schedule_metadata_move (GList *file_pairs);
+void nautilus_directory_schedule_metadata_remove (GList *files);
+
+void nautilus_directory_schedule_metadata_copy_by_uri (GList *uri_pairs);
+void nautilus_directory_schedule_metadata_move_by_uri (GList *uri_pairs);
+void nautilus_directory_schedule_metadata_remove_by_uri (GList *uris);
+
+/* Change notification hack.
+ * This is called when code modifies the file and it needs to trigger
+ * a notification. Eventually this should become private, but for now
+ * it needs to be used for code like the thumbnail generation.
+ */
+void nautilus_file_changed (NautilusFile *file);
diff --git a/src/nautilus-directory-private.h b/src/nautilus-directory-private.h
new file mode 100644
index 0000000..0f8fb1b
--- /dev/null
+++ b/src/nautilus-directory-private.h
@@ -0,0 +1,230 @@
+/*
+ nautilus-directory-private.h: Nautilus directory model.
+
+ Copyright (C) 1999, 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/>.
+
+ Author: Darin Adler <darin@bentspoon.com>
+*/
+
+#pragma once
+
+#include <eel/eel-vfs-extensions.h>
+#include <gio/gio.h>
+#include <nautilus-extension.h>
+
+#include "nautilus-directory.h"
+#include "nautilus-file.h"
+
+typedef struct FileMonitors FileMonitors;
+typedef struct DirectoryLoadState DirectoryLoadState;
+typedef struct DirectoryCountState DirectoryCountState;
+typedef struct DeepCountState DeepCountState;
+typedef struct GetInfoState GetInfoState;
+typedef struct NewFilesState NewFilesState;
+typedef struct MimeListState MimeListState;
+typedef struct ThumbnailState ThumbnailState;
+typedef struct MountState MountState;
+typedef struct FilesystemInfoState FilesystemInfoState;
+
+typedef enum {
+ REQUEST_DEEP_COUNT,
+ REQUEST_DIRECTORY_COUNT,
+ REQUEST_FILE_INFO,
+ REQUEST_FILE_LIST, /* always FALSE if file != NULL */
+ REQUEST_MIME_LIST,
+ REQUEST_EXTENSION_INFO,
+ REQUEST_THUMBNAIL,
+ REQUEST_MOUNT,
+ REQUEST_FILESYSTEM_INFO,
+ REQUEST_TYPE_LAST
+} RequestType;
+
+/* A request for information about one or more files. */
+typedef guint32 Request;
+typedef gint32 RequestCounter[REQUEST_TYPE_LAST];
+
+#define REQUEST_WANTS_TYPE(request, type) ((request) & (1<<(type)))
+#define REQUEST_SET_TYPE(request, type) (request) |= (1<<(type))
+
+struct NautilusDirectoryDetails
+{
+ /* The location. */
+ GFile *location;
+
+ /* The file objects. */
+ NautilusFile *as_file;
+ GList *file_list;
+ GHashTable *file_hash;
+
+ /* Queues of files needing some I/O done. */
+ NautilusFileQueue *high_priority_queue;
+ NautilusFileQueue *low_priority_queue;
+ NautilusFileQueue *extension_queue;
+
+ /* These lists are going to be pretty short. If we think they
+ * are going to get big, we can use hash tables instead.
+ */
+ GList *call_when_ready_list;
+ RequestCounter call_when_ready_counters;
+ GHashTable *monitor_table;
+ RequestCounter monitor_counters;
+ guint call_ready_idle_id;
+
+ NautilusMonitor *monitor;
+ gulong mime_db_monitor;
+
+ gboolean in_async_service_loop;
+ gboolean state_changed;
+
+ gboolean file_list_monitored;
+ gboolean directory_loaded;
+ gboolean directory_loaded_sent_notification;
+ DirectoryLoadState *directory_load_in_progress;
+
+ GList *pending_file_info; /* list of GnomeVFSFileInfo's that are pending */
+ int confirmed_file_count;
+ guint dequeue_pending_idle_id;
+
+ GList *new_files_in_progress; /* list of NewFilesState * */
+
+ /* List of GFile's that received CHANGE events while new files were being added in
+ * that same folder. We will process this CHANGE events after new_files_in_progress
+ * list is finished. See Bug 703179 and issue #1576 for a case when this happens.
+ */
+ GList *files_changed_while_adding;
+
+ DirectoryCountState *count_in_progress;
+
+ NautilusFile *deep_count_file;
+ DeepCountState *deep_count_in_progress;
+
+ MimeListState *mime_list_in_progress;
+
+ NautilusFile *get_info_file;
+ GetInfoState *get_info_in_progress;
+
+ NautilusFile *extension_info_file;
+ NautilusInfoProvider *extension_info_provider;
+ NautilusOperationHandle *extension_info_in_progress;
+ guint extension_info_idle;
+
+ ThumbnailState *thumbnail_state;
+
+ MountState *mount_state;
+
+ FilesystemInfoState *filesystem_info_state;
+
+ GList *file_operations_in_progress; /* list of FileOperation * */
+};
+
+NautilusDirectory *nautilus_directory_get_existing (GFile *location);
+
+/* async. interface */
+void nautilus_directory_async_state_changed (NautilusDirectory *directory);
+void nautilus_directory_call_when_ready_internal (NautilusDirectory *directory,
+ NautilusFile *file,
+ NautilusFileAttributes file_attributes,
+ gboolean wait_for_file_list,
+ NautilusDirectoryCallback directory_callback,
+ NautilusFileCallback file_callback,
+ gpointer callback_data);
+gboolean nautilus_directory_check_if_ready_internal (NautilusDirectory *directory,
+ NautilusFile *file,
+ NautilusFileAttributes file_attributes);
+void nautilus_directory_cancel_callback_internal (NautilusDirectory *directory,
+ NautilusFile *file,
+ NautilusDirectoryCallback directory_callback,
+ NautilusFileCallback file_callback,
+ gpointer callback_data);
+void nautilus_directory_monitor_add_internal (NautilusDirectory *directory,
+ NautilusFile *file,
+ gconstpointer client,
+ gboolean monitor_hidden_files,
+ NautilusFileAttributes attributes,
+ NautilusDirectoryCallback callback,
+ gpointer callback_data);
+void nautilus_directory_monitor_remove_internal (NautilusDirectory *directory,
+ NautilusFile *file,
+ gconstpointer client);
+void nautilus_directory_get_info_for_new_files (NautilusDirectory *directory,
+ GList *vfs_uris);
+NautilusFile * nautilus_directory_get_existing_corresponding_file (NautilusDirectory *directory);
+void nautilus_directory_invalidate_count_and_mime_list (NautilusDirectory *directory);
+gboolean nautilus_directory_is_file_list_monitored (NautilusDirectory *directory);
+gboolean nautilus_directory_is_anyone_monitoring_file_list (NautilusDirectory *directory);
+gboolean nautilus_directory_has_active_request_for_file (NautilusDirectory *directory,
+ NautilusFile *file);
+void nautilus_directory_remove_file_monitor_link (NautilusDirectory *directory,
+ GList *link);
+void nautilus_directory_schedule_dequeue_pending (NautilusDirectory *directory);
+void nautilus_directory_stop_monitoring_file_list (NautilusDirectory *directory);
+void nautilus_directory_cancel (NautilusDirectory *directory);
+void nautilus_async_destroying_file (NautilusFile *file);
+void nautilus_directory_force_reload_internal (NautilusDirectory *directory,
+ NautilusFileAttributes file_attributes);
+void nautilus_directory_cancel_loading_file_attributes (NautilusDirectory *directory,
+ NautilusFile *file,
+ NautilusFileAttributes file_attributes);
+
+/* Calls shared between directory, file, and async. code. */
+void nautilus_directory_emit_files_added (NautilusDirectory *directory,
+ GList *added_files);
+void nautilus_directory_emit_files_changed (NautilusDirectory *directory,
+ GList *changed_files);
+void nautilus_directory_emit_change_signals (NautilusDirectory *directory,
+ GList *changed_files);
+void emit_change_signals_for_all_files (NautilusDirectory *directory);
+void emit_change_signals_for_all_files_in_all_directories (void);
+void nautilus_directory_emit_done_loading (NautilusDirectory *directory);
+void nautilus_directory_emit_load_error (NautilusDirectory *directory,
+ GError *error);
+NautilusDirectory *nautilus_directory_get_internal (GFile *location,
+ gboolean create);
+char * nautilus_directory_get_name_for_self_as_new_file (NautilusDirectory *directory);
+Request nautilus_directory_set_up_request (NautilusFileAttributes file_attributes);
+
+/* Interface to the file list. */
+NautilusFile * nautilus_directory_find_file_by_name (NautilusDirectory *directory,
+ const char *filename);
+
+void nautilus_directory_add_file (NautilusDirectory *directory,
+ NautilusFile *file);
+void nautilus_directory_remove_file (NautilusDirectory *directory,
+ NautilusFile *file);
+FileMonitors * nautilus_directory_remove_file_monitors (NautilusDirectory *directory,
+ NautilusFile *file);
+void nautilus_directory_add_file_monitors (NautilusDirectory *directory,
+ NautilusFile *file,
+ FileMonitors *monitors);
+void nautilus_directory_add_file (NautilusDirectory *directory,
+ NautilusFile *file);
+GList * nautilus_directory_begin_file_name_change (NautilusDirectory *directory,
+ NautilusFile *file);
+void nautilus_directory_end_file_name_change (NautilusDirectory *directory,
+ NautilusFile *file,
+ GList *node);
+void nautilus_directory_moved (const char *from_uri,
+ const char *to_uri);
+/* Interface to the work queue. */
+
+void nautilus_directory_add_file_to_work_queue (NautilusDirectory *directory,
+ NautilusFile *file);
+void nautilus_directory_remove_file_from_work_queue (NautilusDirectory *directory,
+ NautilusFile *file);
+
+
+/* debugging functions */
+int nautilus_directory_number_outstanding (void);
diff --git a/src/nautilus-directory.c b/src/nautilus-directory.c
new file mode 100644
index 0000000..1a9947f
--- /dev/null
+++ b/src/nautilus-directory.c
@@ -0,0 +1,2085 @@
+/*
+ * nautilus-directory.c: Nautilus directory model.
+ *
+ * Copyright (C) 1999, 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/>.
+ *
+ * Author: Darin Adler <darin@bentspoon.com>
+ */
+
+#include "nautilus-directory-private.h"
+
+#include <eel/eel-glib-extensions.h>
+#include <eel/eel-string.h>
+#include <glib/gi18n.h>
+#include <gtk/gtk.h>
+
+#include "nautilus-directory-notify.h"
+#include "nautilus-directory-private.h"
+#include "nautilus-enums.h"
+#include "nautilus-file-private.h"
+#include "nautilus-file-queue.h"
+#include "nautilus-file-utilities.h"
+#include "nautilus-global-preferences.h"
+#include "nautilus-lib-self-check-functions.h"
+#include "nautilus-metadata.h"
+#include "nautilus-profile.h"
+#include "nautilus-search-directory-file.h"
+#include "nautilus-search-directory.h"
+#include "nautilus-starred-directory.h"
+#include "nautilus-vfs-directory.h"
+#include "nautilus-vfs-file.h"
+
+enum
+{
+ FILES_ADDED,
+ FILES_CHANGED,
+ DONE_LOADING,
+ LOAD_ERROR,
+ LAST_SIGNAL
+};
+
+enum
+{
+ PROP_LOCATION = 1,
+ NUM_PROPERTIES
+};
+
+static guint signals[LAST_SIGNAL];
+static GParamSpec *properties[NUM_PROPERTIES] = { NULL, };
+
+static GHashTable *directories;
+
+static NautilusDirectory *nautilus_directory_new (GFile *location);
+static void set_directory_location (NautilusDirectory *directory,
+ GFile *location);
+
+G_DEFINE_TYPE (NautilusDirectory, nautilus_directory, G_TYPE_OBJECT);
+
+static gboolean
+real_contains_file (NautilusDirectory *self,
+ NautilusFile *file)
+{
+ NautilusDirectory *directory;
+
+ directory = nautilus_file_get_directory (file);
+
+ return directory == self;
+}
+
+static gboolean
+real_are_all_files_seen (NautilusDirectory *directory)
+{
+ return directory->details->directory_loaded;
+}
+
+static gboolean
+real_is_not_empty (NautilusDirectory *directory)
+{
+ return directory->details->file_list != NULL;
+}
+
+static gboolean
+is_tentative (NautilusFile *file,
+ gpointer callback_data)
+{
+ g_assert (callback_data == NULL);
+
+ /* Avoid returning files with !is_added, because these
+ * will later be sent with the files_added signal, and a
+ * user doing get_file_list + files_added monitoring will
+ * then see the file twice */
+ return !file->details->got_file_info || !file->details->is_added;
+}
+
+static GList *
+real_get_file_list (NautilusDirectory *directory)
+{
+ GList *tentative_files, *non_tentative_files;
+
+ tentative_files = nautilus_file_list_filter (directory->details->file_list,
+ &non_tentative_files, is_tentative, NULL);
+ nautilus_file_list_free (tentative_files);
+
+ return non_tentative_files;
+}
+
+static gboolean
+real_is_editable (NautilusDirectory *directory)
+{
+ return TRUE;
+}
+
+static NautilusFile *
+real_new_file_from_filename (NautilusDirectory *directory,
+ const char *filename,
+ gboolean self_owned)
+{
+ NautilusFile *file;
+
+ g_assert (NAUTILUS_IS_DIRECTORY (directory));
+ g_assert (filename != NULL);
+ g_assert (filename[0] != '\0');
+
+ if (NAUTILUS_IS_SEARCH_DIRECTORY (directory))
+ {
+ if (self_owned)
+ {
+ file = NAUTILUS_FILE (g_object_new (NAUTILUS_TYPE_SEARCH_DIRECTORY_FILE, NULL));
+ }
+ else
+ {
+ /* This doesn't normally happen, unless the user somehow types in a uri
+ * that references a file like this. (See #349840) */
+ file = NAUTILUS_FILE (g_object_new (NAUTILUS_TYPE_VFS_FILE, NULL));
+ }
+ }
+ else
+ {
+ file = NAUTILUS_FILE (g_object_new (NAUTILUS_TYPE_VFS_FILE, NULL));
+ }
+ nautilus_file_set_directory (file, directory);
+
+ return file;
+}
+
+static gboolean
+real_handles_location (GFile *location)
+{
+ /* This class is the fallback on handling any location */
+ return TRUE;
+}
+
+static void
+nautilus_directory_finalize (GObject *object)
+{
+ NautilusDirectory *directory;
+
+ directory = NAUTILUS_DIRECTORY (object);
+
+ g_hash_table_remove (directories, directory->details->location);
+
+ nautilus_directory_cancel (directory);
+ g_assert (directory->details->count_in_progress == NULL);
+
+ if (g_hash_table_size (directory->details->monitor_table) != 0)
+ {
+ GHashTableIter iter;
+ gpointer value;
+
+ g_warning ("destroying a NautilusDirectory while it's being monitored");
+
+ g_hash_table_iter_init (&iter, directory->details->monitor_table);
+ while (g_hash_table_iter_next (&iter, NULL, &value))
+ {
+ GList *list = value;
+ g_list_free_full (list, g_free);
+ }
+ g_hash_table_remove_all (directory->details->monitor_table);
+ }
+ g_hash_table_destroy (directory->details->monitor_table);
+
+ if (directory->details->monitor != NULL)
+ {
+ nautilus_monitor_cancel (directory->details->monitor);
+ }
+
+ if (directory->details->dequeue_pending_idle_id != 0)
+ {
+ g_source_remove (directory->details->dequeue_pending_idle_id);
+ }
+
+ if (directory->details->call_ready_idle_id != 0)
+ {
+ g_source_remove (directory->details->call_ready_idle_id);
+ }
+
+ if (directory->details->location)
+ {
+ g_object_unref (directory->details->location);
+ }
+
+ g_assert (directory->details->file_list == NULL);
+ g_hash_table_destroy (directory->details->file_hash);
+
+ nautilus_file_queue_destroy (directory->details->high_priority_queue);
+ nautilus_file_queue_destroy (directory->details->low_priority_queue);
+ nautilus_file_queue_destroy (directory->details->extension_queue);
+ g_clear_list (&directory->details->files_changed_while_adding, g_object_unref);
+ g_assert (directory->details->directory_load_in_progress == NULL);
+ g_assert (directory->details->count_in_progress == NULL);
+ g_assert (directory->details->dequeue_pending_idle_id == 0);
+ g_list_free_full (directory->details->pending_file_info, g_object_unref);
+
+ G_OBJECT_CLASS (nautilus_directory_parent_class)->finalize (object);
+}
+
+static void
+nautilus_directory_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ NautilusDirectory *directory = NAUTILUS_DIRECTORY (object);
+
+ switch (property_id)
+ {
+ case PROP_LOCATION:
+ {
+ set_directory_location (directory, g_value_get_object (value));
+ }
+ break;
+
+ default:
+ {
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ }
+ break;
+ }
+}
+
+static void
+nautilus_directory_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ NautilusDirectory *directory = NAUTILUS_DIRECTORY (object);
+
+ switch (property_id)
+ {
+ case PROP_LOCATION:
+ {
+ g_value_set_object (value, directory->details->location);
+ }
+ break;
+
+ default:
+ {
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ }
+ break;
+ }
+}
+
+static void
+nautilus_directory_class_init (NautilusDirectoryClass *klass)
+{
+ GObjectClass *object_class;
+
+ object_class = G_OBJECT_CLASS (klass);
+
+ klass->contains_file = real_contains_file;
+ klass->are_all_files_seen = real_are_all_files_seen;
+ klass->is_not_empty = real_is_not_empty;
+ klass->get_file_list = real_get_file_list;
+ klass->is_editable = real_is_editable;
+ klass->new_file_from_filename = real_new_file_from_filename;
+ klass->handles_location = real_handles_location;
+
+ object_class->finalize = nautilus_directory_finalize;
+ object_class->set_property = nautilus_directory_set_property;
+ object_class->get_property = nautilus_directory_get_property;
+
+ signals[FILES_ADDED] =
+ g_signal_new ("files-added",
+ G_TYPE_FROM_CLASS (object_class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (NautilusDirectoryClass, files_added),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__POINTER,
+ G_TYPE_NONE, 1, G_TYPE_POINTER);
+ signals[FILES_CHANGED] =
+ g_signal_new ("files-changed",
+ G_TYPE_FROM_CLASS (object_class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (NautilusDirectoryClass, files_changed),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__POINTER,
+ G_TYPE_NONE, 1, G_TYPE_POINTER);
+ signals[DONE_LOADING] =
+ g_signal_new ("done-loading",
+ G_TYPE_FROM_CLASS (object_class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (NautilusDirectoryClass, done_loading),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+ signals[LOAD_ERROR] =
+ g_signal_new ("load-error",
+ G_TYPE_FROM_CLASS (object_class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (NautilusDirectoryClass, load_error),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__POINTER,
+ G_TYPE_NONE, 1, G_TYPE_POINTER);
+
+ properties[PROP_LOCATION] =
+ g_param_spec_object ("location",
+ "The location",
+ "The location of this directory",
+ G_TYPE_FILE,
+ G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE);
+
+ g_type_class_add_private (klass, sizeof (NautilusDirectoryDetails));
+ g_object_class_install_properties (object_class, NUM_PROPERTIES, properties);
+}
+
+static void
+nautilus_directory_init (NautilusDirectory *directory)
+{
+ directory->details = G_TYPE_INSTANCE_GET_PRIVATE ((directory), NAUTILUS_TYPE_DIRECTORY, NautilusDirectoryDetails);
+ directory->details->file_hash = g_hash_table_new_full (g_str_hash, g_str_equal,
+ g_free, NULL);
+ directory->details->high_priority_queue = nautilus_file_queue_new ();
+ directory->details->low_priority_queue = nautilus_file_queue_new ();
+ directory->details->extension_queue = nautilus_file_queue_new ();
+ directory->details->monitor_table = g_hash_table_new (NULL, NULL);
+}
+
+NautilusDirectory *
+nautilus_directory_ref (NautilusDirectory *directory)
+{
+ if (directory == NULL)
+ {
+ return directory;
+ }
+
+ g_return_val_if_fail (NAUTILUS_IS_DIRECTORY (directory), NULL);
+
+ g_object_ref (directory);
+ return directory;
+}
+
+void
+nautilus_directory_unref (NautilusDirectory *directory)
+{
+ if (directory == NULL)
+ {
+ return;
+ }
+
+ g_return_if_fail (NAUTILUS_IS_DIRECTORY (directory));
+
+ g_object_unref (directory);
+}
+
+static void
+collect_all_directories (gpointer key,
+ gpointer value,
+ gpointer callback_data)
+{
+ NautilusDirectory *directory;
+ GList **dirs;
+
+ directory = NAUTILUS_DIRECTORY (value);
+ dirs = callback_data;
+
+ *dirs = g_list_prepend (*dirs, nautilus_directory_ref (directory));
+}
+
+static void
+filtering_changed_callback (gpointer callback_data)
+{
+ g_autolist (NautilusDirectory) dirs = NULL;
+
+ g_assert (callback_data == NULL);
+
+ dirs = NULL;
+ g_hash_table_foreach (directories, collect_all_directories, &dirs);
+
+ /* Preference about which items to show has changed, so we
+ * can't trust any of our precomputed directory counts.
+ */
+ for (GList *l = dirs; l != NULL; l = l->next)
+ {
+ NautilusDirectory *directory;
+
+ directory = NAUTILUS_DIRECTORY (l->data);
+
+ nautilus_directory_invalidate_count_and_mime_list (directory);
+ }
+}
+
+void
+emit_change_signals_for_all_files (NautilusDirectory *directory)
+{
+ GList *files;
+
+ files = g_list_copy (directory->details->file_list);
+ if (directory->details->as_file != NULL)
+ {
+ files = g_list_prepend (files, directory->details->as_file);
+ }
+
+ nautilus_file_list_ref (files);
+ nautilus_directory_emit_change_signals (directory, files);
+
+ nautilus_file_list_free (files);
+}
+
+void
+emit_change_signals_for_all_files_in_all_directories (void)
+{
+ GList *dirs, *l;
+ NautilusDirectory *directory;
+
+ dirs = NULL;
+ g_hash_table_foreach (directories,
+ collect_all_directories,
+ &dirs);
+
+ for (l = dirs; l != NULL; l = l->next)
+ {
+ directory = NAUTILUS_DIRECTORY (l->data);
+ emit_change_signals_for_all_files (directory);
+ nautilus_directory_unref (directory);
+ }
+
+ g_list_free (dirs);
+}
+
+static void
+async_state_changed_one (gpointer key,
+ gpointer value,
+ gpointer user_data)
+{
+ NautilusDirectory *directory;
+
+ g_assert (key != NULL);
+ g_assert (NAUTILUS_IS_DIRECTORY (value));
+ g_assert (user_data == NULL);
+
+ directory = NAUTILUS_DIRECTORY (value);
+
+ nautilus_directory_async_state_changed (directory);
+ emit_change_signals_for_all_files (directory);
+}
+
+static void
+async_data_preference_changed_callback (gpointer callback_data)
+{
+ g_assert (callback_data == NULL);
+
+ /* Preference involving fetched async data has changed, so
+ * we have to kick off refetching all async data, and tell
+ * each file that it (might have) changed.
+ */
+ g_hash_table_foreach (directories, async_state_changed_one, NULL);
+}
+
+static void
+add_preferences_callbacks (void)
+{
+ nautilus_global_preferences_init ();
+
+ g_signal_connect_swapped (gtk_filechooser_preferences,
+ "changed::" NAUTILUS_PREFERENCES_SHOW_HIDDEN_FILES,
+ G_CALLBACK (filtering_changed_callback),
+ NULL);
+ g_signal_connect_swapped (nautilus_preferences,
+ "changed::" NAUTILUS_PREFERENCES_SHOW_DIRECTORY_ITEM_COUNTS,
+ G_CALLBACK (async_data_preference_changed_callback),
+ NULL);
+}
+
+/**
+ * nautilus_directory_get_by_uri:
+ * @uri: URI of directory to get.
+ *
+ * Get a directory given a uri.
+ * Creates the appropriate subclass given the uri mappings.
+ * Returns a referenced object, not a floating one. Unref when finished.
+ * If two windows are viewing the same uri, the directory object is shared.
+ */
+NautilusDirectory *
+nautilus_directory_get_internal (GFile *location,
+ gboolean create)
+{
+ NautilusDirectory *directory;
+
+ /* Create the hash table first time through. */
+ if (directories == NULL)
+ {
+ directories = g_hash_table_new (g_file_hash, (GCompareFunc) g_file_equal);
+ add_preferences_callbacks ();
+ }
+
+ /* If the object is already in the hash table, look it up. */
+
+ directory = g_hash_table_lookup (directories,
+ location);
+ if (directory != NULL)
+ {
+ nautilus_directory_ref (directory);
+ }
+ else if (create)
+ {
+ /* Create a new directory object instead. */
+ directory = nautilus_directory_new (location);
+ if (directory == NULL)
+ {
+ return NULL;
+ }
+
+ /* Put it in the hash table. */
+ g_hash_table_insert (directories,
+ directory->details->location,
+ directory);
+ }
+
+ return directory;
+}
+
+NautilusDirectory *
+nautilus_directory_get (GFile *location)
+{
+ if (location == NULL)
+ {
+ return NULL;
+ }
+
+ return nautilus_directory_get_internal (location, TRUE);
+}
+
+NautilusDirectory *
+nautilus_directory_get_existing (GFile *location)
+{
+ if (location == NULL)
+ {
+ return NULL;
+ }
+
+ return nautilus_directory_get_internal (location, FALSE);
+}
+
+
+NautilusDirectory *
+nautilus_directory_get_by_uri (const char *uri)
+{
+ NautilusDirectory *directory;
+ GFile *location;
+
+ if (uri == NULL)
+ {
+ return NULL;
+ }
+
+ location = g_file_new_for_uri (uri);
+
+ directory = nautilus_directory_get_internal (location, TRUE);
+ g_object_unref (location);
+ return directory;
+}
+
+NautilusDirectory *
+nautilus_directory_get_for_file (NautilusFile *file)
+{
+ char *uri;
+ NautilusDirectory *directory;
+
+ g_return_val_if_fail (NAUTILUS_IS_FILE (file), NULL);
+
+ uri = nautilus_file_get_uri (file);
+ directory = nautilus_directory_get_by_uri (uri);
+ g_free (uri);
+ return directory;
+}
+
+/* Returns a reffed NautilusFile object for this directory.
+ */
+NautilusFile *
+nautilus_directory_get_corresponding_file (NautilusDirectory *directory)
+{
+ NautilusFile *file;
+ char *uri;
+
+ file = nautilus_directory_get_existing_corresponding_file (directory);
+ if (file == NULL)
+ {
+ uri = nautilus_directory_get_uri (directory);
+ file = nautilus_file_get_by_uri (uri);
+ g_free (uri);
+ }
+
+ return file;
+}
+
+/* Returns a reffed NautilusFile object for this directory, but only if the
+ * NautilusFile object has already been created.
+ */
+NautilusFile *
+nautilus_directory_get_existing_corresponding_file (NautilusDirectory *directory)
+{
+ NautilusFile *file;
+ char *uri;
+
+ file = directory->details->as_file;
+ if (file != NULL)
+ {
+ nautilus_file_ref (file);
+ return file;
+ }
+
+ uri = nautilus_directory_get_uri (directory);
+ file = nautilus_file_get_existing_by_uri (uri);
+ g_free (uri);
+ return file;
+}
+
+/* nautilus_directory_get_name_for_self_as_new_file:
+ *
+ * Get a name to display for the file representing this
+ * directory. This is called only when there's no VFS
+ * directory for this NautilusDirectory.
+ */
+char *
+nautilus_directory_get_name_for_self_as_new_file (NautilusDirectory *directory)
+{
+ GFile *file;
+ char *directory_uri;
+ char *scheme;
+ char *name;
+ char *hostname = NULL;
+
+ directory_uri = nautilus_directory_get_uri (directory);
+ file = g_file_new_for_uri (directory_uri);
+ scheme = g_file_get_uri_scheme (file);
+ g_object_unref (file);
+
+ nautilus_uri_parse (directory_uri, &hostname, NULL, NULL);
+ if (hostname == NULL || (strlen (hostname) == 0))
+ {
+ name = g_strdup (directory_uri);
+ }
+ else if (scheme == NULL)
+ {
+ name = g_strdup (hostname);
+ }
+ else
+ {
+ /* Translators: this is of the format "hostname (uri-scheme)" */
+ name = g_strdup_printf (_("%s (%s)"), hostname, scheme);
+ }
+
+ g_free (directory_uri);
+ g_free (scheme);
+ g_free (hostname);
+
+ return name;
+}
+
+char *
+nautilus_directory_get_uri (NautilusDirectory *directory)
+{
+ g_return_val_if_fail (NAUTILUS_IS_DIRECTORY (directory), NULL);
+
+ return g_file_get_uri (directory->details->location);
+}
+
+GFile *
+nautilus_directory_get_location (NautilusDirectory *directory)
+{
+ g_return_val_if_fail (NAUTILUS_IS_DIRECTORY (directory), NULL);
+
+ return g_object_ref (directory->details->location);
+}
+
+NautilusFile *
+nautilus_directory_new_file_from_filename (NautilusDirectory *directory,
+ const char *filename,
+ gboolean self_owned)
+{
+ return NAUTILUS_DIRECTORY_CLASS (G_OBJECT_GET_CLASS (directory))->new_file_from_filename (directory,
+ filename,
+ self_owned);
+}
+
+static GList *
+nautilus_directory_provider_get_all (void)
+{
+ GIOExtensionPoint *extension_point;
+ GList *extensions;
+
+ extension_point = g_io_extension_point_lookup (NAUTILUS_DIRECTORY_PROVIDER_EXTENSION_POINT_NAME);
+ if (extension_point == NULL)
+ {
+ g_warning ("Directory provider extension point not registered. Did you call nautilus_ensure_extension_points()?");
+ }
+ extensions = g_io_extension_point_get_extensions (extension_point);
+
+ return extensions;
+}
+
+static NautilusDirectory *
+nautilus_directory_new (GFile *location)
+{
+ GList *extensions;
+ GList *l;
+ GIOExtension *gio_extension;
+ GType handling_provider_type;
+ gboolean handled = FALSE;
+ NautilusDirectoryClass *current_provider_class;
+ NautilusDirectory *handling_instance;
+
+ extensions = nautilus_directory_provider_get_all ();
+
+ for (l = extensions; l != NULL; l = l->next)
+ {
+ gio_extension = l->data;
+ current_provider_class = NAUTILUS_DIRECTORY_CLASS (g_io_extension_ref_class (gio_extension));
+ if (current_provider_class->handles_location (location))
+ {
+ handling_provider_type = g_io_extension_get_type (gio_extension);
+ handled = TRUE;
+ break;
+ }
+ }
+
+ if (!handled)
+ {
+ /* This class is the fallback for any location */
+ handling_provider_type = NAUTILUS_TYPE_VFS_DIRECTORY;
+ }
+
+ handling_instance = g_object_new (handling_provider_type,
+ "location", location,
+ NULL);
+
+
+ return handling_instance;
+}
+
+/**
+ * nautilus_directory_is_local_or_fuse:
+ *
+ * @directory: a #NautilusDirectory
+ *
+ * Checks whether this directory contains files with local paths. Usually, this
+ * means the local path can be obtained by calling g_file_get_path(). As an
+ * exception, the local URI for files in recent:// can only be obtained from the
+ * G_FILE_ATTRIBUTE_STANDARD_TARGET_URI attribute.
+ *
+ * Returns: %TRUE if a local path is known to be obtainable for all files in
+ * this directory. Otherwise, %FALSE.
+ */
+gboolean
+nautilus_directory_is_local_or_fuse (NautilusDirectory *directory)
+{
+ g_return_val_if_fail (NAUTILUS_IS_DIRECTORY (directory), FALSE);
+ g_return_val_if_fail (directory->details->location, FALSE);
+
+
+ if (nautilus_directory_is_in_recent (directory)
+ || g_file_is_native (directory->details->location))
+ {
+ /* Native files have a local path by definition. The files in recent:/
+ * have a local URI stored in the standard::target-uri attribute. */
+ return TRUE;
+ }
+ else
+ {
+ g_autofree char *path = NULL;
+
+ /* Non-native files may have local paths in FUSE mounts. The only way to
+ * know if that's the case is to test if GIO reports a path.
+ */
+ path = g_file_get_path (directory->details->location);
+
+ return (path != NULL);
+ }
+}
+
+gboolean
+nautilus_directory_is_in_trash (NautilusDirectory *directory)
+{
+ g_assert (NAUTILUS_IS_DIRECTORY (directory));
+
+ if (directory->details->location == NULL)
+ {
+ return FALSE;
+ }
+
+ return g_file_has_uri_scheme (directory->details->location, "trash");
+}
+
+gboolean
+nautilus_directory_is_in_recent (NautilusDirectory *directory)
+{
+ g_assert (NAUTILUS_IS_DIRECTORY (directory));
+
+ if (directory->details->location == NULL)
+ {
+ return FALSE;
+ }
+
+ return g_file_has_uri_scheme (directory->details->location, "recent");
+}
+
+gboolean
+nautilus_directory_is_in_starred (NautilusDirectory *directory)
+{
+ g_assert (NAUTILUS_IS_DIRECTORY (directory));
+
+ if (directory->details->location == NULL)
+ {
+ return FALSE;
+ }
+
+ return g_file_has_uri_scheme (directory->details->location, "starred");
+}
+
+gboolean
+nautilus_directory_is_in_admin (NautilusDirectory *directory)
+{
+ g_assert (NAUTILUS_IS_DIRECTORY (directory));
+
+ if (directory->details->location == NULL)
+ {
+ return FALSE;
+ }
+
+ return g_file_has_uri_scheme (directory->details->location, "admin");
+}
+
+gboolean
+nautilus_directory_are_all_files_seen (NautilusDirectory *directory)
+{
+ g_return_val_if_fail (NAUTILUS_IS_DIRECTORY (directory), FALSE);
+
+ return NAUTILUS_DIRECTORY_CLASS (G_OBJECT_GET_CLASS (directory))->are_all_files_seen (directory);
+}
+
+static void
+add_to_hash_table (NautilusDirectory *directory,
+ NautilusFile *file,
+ GList *node)
+{
+ gchar *name;
+
+ name = nautilus_file_get_name (file);
+
+ g_assert (name != NULL);
+ g_assert (node != NULL);
+ g_assert (g_hash_table_lookup (directory->details->file_hash,
+ name) == NULL);
+ g_hash_table_insert (directory->details->file_hash, name, node);
+}
+
+static GList *
+extract_from_hash_table (NautilusDirectory *directory,
+ NautilusFile *file)
+{
+ g_autofree gchar *name = NULL;
+ GList *node;
+
+ name = nautilus_file_get_name (file);
+ if (name == NULL)
+ {
+ return NULL;
+ }
+
+ /* Find the list node in the hash table. */
+ node = g_hash_table_lookup (directory->details->file_hash, name);
+ g_hash_table_remove (directory->details->file_hash, name);
+
+ return node;
+}
+
+void
+nautilus_directory_add_file (NautilusDirectory *directory,
+ NautilusFile *file)
+{
+ GList *node;
+ gboolean add_to_work_queue;
+
+ g_assert (NAUTILUS_IS_DIRECTORY (directory));
+ g_assert (NAUTILUS_IS_FILE (file));
+
+ /* Add to list. */
+ node = g_list_prepend (directory->details->file_list, file);
+ directory->details->file_list = node;
+
+ /* Add to hash table. */
+ add_to_hash_table (directory, file, node);
+
+ directory->details->confirmed_file_count++;
+
+ add_to_work_queue = FALSE;
+ if (nautilus_directory_is_file_list_monitored (directory))
+ {
+ /* Ref if we are monitoring, since monitoring owns the file list. */
+ nautilus_file_ref (file);
+ add_to_work_queue = TRUE;
+ }
+ else if (nautilus_directory_has_active_request_for_file (directory, file))
+ {
+ /* We're waiting for the file in a call_when_ready. Make sure
+ * we add the file to the work queue so that said waiter won't
+ * wait forever for e.g. all files in the directory to be done */
+ add_to_work_queue = TRUE;
+ }
+
+ if (add_to_work_queue)
+ {
+ nautilus_directory_add_file_to_work_queue (directory, file);
+ }
+}
+
+void
+nautilus_directory_remove_file (NautilusDirectory *directory,
+ NautilusFile *file)
+{
+ GList *node;
+
+ g_assert (NAUTILUS_IS_DIRECTORY (directory));
+ g_assert (NAUTILUS_IS_FILE (file));
+
+ /* Find the list node in the hash table. */
+ node = extract_from_hash_table (directory, file);
+ g_assert (node != NULL);
+ g_assert (node->data == file);
+
+ /* Remove the item from the list. */
+ directory->details->file_list = g_list_remove_link
+ (directory->details->file_list, node);
+ g_list_free_1 (node);
+
+ nautilus_directory_remove_file_from_work_queue (directory, file);
+
+ if (!file->details->unconfirmed)
+ {
+ directory->details->confirmed_file_count--;
+ }
+
+ /* Unref if we are monitoring. */
+ if (nautilus_directory_is_file_list_monitored (directory))
+ {
+ nautilus_file_unref (file);
+ }
+}
+
+GList *
+nautilus_directory_begin_file_name_change (NautilusDirectory *directory,
+ NautilusFile *file)
+{
+ /* Find the list node in the hash table. */
+ return extract_from_hash_table (directory, file);
+}
+
+void
+nautilus_directory_end_file_name_change (NautilusDirectory *directory,
+ NautilusFile *file,
+ GList *node)
+{
+ /* Add the list node to the hash table. */
+ if (node != NULL)
+ {
+ add_to_hash_table (directory, file, node);
+ }
+}
+
+NautilusFile *
+nautilus_directory_find_file_by_name (NautilusDirectory *directory,
+ const char *name)
+{
+ GList *node;
+
+ g_return_val_if_fail (NAUTILUS_IS_DIRECTORY (directory), NULL);
+ g_return_val_if_fail (name != NULL, NULL);
+
+ node = g_hash_table_lookup (directory->details->file_hash,
+ name);
+ return node == NULL ? NULL : NAUTILUS_FILE (node->data);
+}
+
+void
+nautilus_directory_emit_files_added (NautilusDirectory *directory,
+ GList *added_files)
+{
+ nautilus_profile_start (NULL);
+ if (added_files != NULL)
+ {
+ g_signal_emit (directory,
+ signals[FILES_ADDED], 0,
+ added_files);
+ }
+ nautilus_profile_end (NULL);
+}
+
+void
+nautilus_directory_emit_files_changed (NautilusDirectory *directory,
+ GList *changed_files)
+{
+ nautilus_profile_start (NULL);
+ if (changed_files != NULL)
+ {
+ g_signal_emit (directory,
+ signals[FILES_CHANGED], 0,
+ changed_files);
+ }
+ nautilus_profile_end (NULL);
+}
+
+void
+nautilus_directory_emit_change_signals (NautilusDirectory *directory,
+ GList *changed_files)
+{
+ GList *p;
+
+ nautilus_profile_start (NULL);
+ for (p = changed_files; p != NULL; p = p->next)
+ {
+ nautilus_file_emit_changed (p->data);
+ }
+ nautilus_directory_emit_files_changed (directory, changed_files);
+ nautilus_profile_end (NULL);
+}
+
+void
+nautilus_directory_emit_done_loading (NautilusDirectory *directory)
+{
+ g_signal_emit (directory,
+ signals[DONE_LOADING], 0);
+}
+
+void
+nautilus_directory_emit_load_error (NautilusDirectory *directory,
+ GError *error)
+{
+ g_signal_emit (directory,
+ signals[LOAD_ERROR], 0,
+ error);
+}
+
+/* Return a directory object for this one's parent. */
+static NautilusDirectory *
+get_parent_directory (GFile *location)
+{
+ NautilusDirectory *directory;
+ GFile *parent;
+
+ parent = g_file_get_parent (location);
+ if (parent)
+ {
+ directory = nautilus_directory_get_internal (parent, TRUE);
+ g_object_unref (parent);
+ return directory;
+ }
+ return NULL;
+}
+
+/* If a directory object exists for this one's parent, then
+ * return it, otherwise return NULL.
+ */
+static NautilusDirectory *
+get_parent_directory_if_exists (GFile *location)
+{
+ NautilusDirectory *directory;
+ GFile *parent;
+
+ parent = g_file_get_parent (location);
+ if (parent)
+ {
+ directory = nautilus_directory_get_internal (parent, FALSE);
+ g_object_unref (parent);
+ return directory;
+ }
+ return NULL;
+}
+
+static void
+hash_table_list_prepend (GHashTable *table,
+ gconstpointer key,
+ gpointer data)
+{
+ GList *list;
+
+ list = g_hash_table_lookup (table, key);
+ list = g_list_prepend (list, data);
+ g_hash_table_insert (table, (gpointer) key, list);
+}
+
+static void
+call_files_added_free_list (gpointer key,
+ gpointer value,
+ gpointer user_data)
+{
+ g_assert (NAUTILUS_IS_DIRECTORY (key));
+ g_assert (value != NULL);
+ g_assert (user_data == NULL);
+
+ g_signal_emit (key,
+ signals[FILES_ADDED], 0,
+ value);
+ g_list_free (value);
+}
+
+static void
+call_files_changed_common (NautilusDirectory *self,
+ GList *file_list)
+{
+ GList *node;
+ NautilusFile *file;
+
+ for (node = file_list; node != NULL; node = node->next)
+ {
+ NautilusDirectory *directory;
+
+ file = node->data;
+ directory = nautilus_file_get_directory (file);
+
+ if (directory == self)
+ {
+ nautilus_directory_add_file_to_work_queue (self, file);
+ }
+ }
+ nautilus_directory_async_state_changed (self);
+ nautilus_directory_emit_change_signals (self, file_list);
+}
+
+static void
+call_files_changed_free_list (gpointer key,
+ gpointer value,
+ gpointer user_data)
+{
+ g_assert (value != NULL);
+ g_assert (user_data == NULL);
+
+ call_files_changed_common (NAUTILUS_DIRECTORY (key), value);
+ g_list_free (value);
+}
+
+static void
+call_files_changed_unref_free_list (gpointer key,
+ gpointer value,
+ gpointer user_data)
+{
+ g_assert (value != NULL);
+ g_assert (user_data == NULL);
+
+ call_files_changed_common (NAUTILUS_DIRECTORY (key), value);
+ nautilus_file_list_free (value);
+}
+
+static void
+call_get_file_info_free_list (gpointer key,
+ gpointer value,
+ gpointer user_data)
+{
+ NautilusDirectory *directory;
+ GList *files;
+
+ g_assert (NAUTILUS_IS_DIRECTORY (key));
+ g_assert (value != NULL);
+ g_assert (user_data == NULL);
+
+ directory = key;
+ files = value;
+
+ nautilus_directory_get_info_for_new_files (directory, files);
+ g_list_foreach (files, (GFunc) g_object_unref, NULL);
+ g_list_free (files);
+}
+
+static void
+invalidate_count_and_unref (gpointer key,
+ gpointer value,
+ gpointer user_data)
+{
+ g_assert (NAUTILUS_IS_DIRECTORY (key));
+ g_assert (value == key);
+ g_assert (user_data == NULL);
+
+ nautilus_directory_invalidate_count_and_mime_list (key);
+ nautilus_directory_unref (key);
+}
+
+static void
+collect_parent_directories (GHashTable *hash_table,
+ NautilusDirectory *directory)
+{
+ g_assert (hash_table != NULL);
+ g_assert (NAUTILUS_IS_DIRECTORY (directory));
+
+ if (g_hash_table_lookup (hash_table, directory) == NULL)
+ {
+ nautilus_directory_ref (directory);
+ g_hash_table_insert (hash_table, directory, directory);
+ }
+}
+
+void
+nautilus_directory_notify_files_added (GList *files)
+{
+ GHashTable *added_lists;
+ GList *p;
+ NautilusDirectory *directory;
+ GHashTable *parent_directories;
+ NautilusFile *file;
+ GFile *location, *parent;
+
+ nautilus_profile_start (NULL);
+
+ /* Make a list of added files in each directory. */
+ added_lists = g_hash_table_new (NULL, NULL);
+
+ /* Make a list of parent directories that will need their counts updated. */
+ parent_directories = g_hash_table_new (NULL, NULL);
+
+ for (p = files; p != NULL; p = p->next)
+ {
+ location = p->data;
+
+ /* See if the directory is already known. */
+ directory = get_parent_directory_if_exists (location);
+ if (directory == NULL)
+ {
+ /* In case the directory is not being
+ * monitored, but the corresponding file is,
+ * we must invalidate it's item count.
+ */
+
+
+ file = NULL;
+ parent = g_file_get_parent (location);
+ if (parent)
+ {
+ file = nautilus_file_get_existing (parent);
+ g_object_unref (parent);
+ }
+
+ if (file != NULL)
+ {
+ nautilus_file_invalidate_count_and_mime_list (file);
+ nautilus_file_unref (file);
+ }
+
+ continue;
+ }
+
+ collect_parent_directories (parent_directories, directory);
+
+ /* If no one is monitoring files in the directory, nothing to do. */
+ if (!nautilus_directory_is_file_list_monitored (directory))
+ {
+ nautilus_directory_unref (directory);
+ continue;
+ }
+
+ file = nautilus_file_get_existing (location);
+ /* We check is_added here, because the file could have been added
+ * to the directory by a nautilus_file_get() but not gotten
+ * files_added emitted
+ */
+ if (file && file->details->is_added)
+ {
+ /* A file already exists, it was probably renamed.
+ * If it was renamed this could be ignored, but
+ * queue a change just in case */
+ nautilus_file_changed (file);
+ }
+ else
+ {
+ hash_table_list_prepend (added_lists,
+ directory,
+ g_object_ref (location));
+ }
+ nautilus_file_unref (file);
+ nautilus_directory_unref (directory);
+ }
+
+ /* Now get file info for the new files. This creates NautilusFile
+ * objects for the new files, and sends out a files_added signal.
+ */
+ g_hash_table_foreach (added_lists, call_get_file_info_free_list, NULL);
+ g_hash_table_destroy (added_lists);
+
+ /* Invalidate count for each parent directory. */
+ g_hash_table_foreach (parent_directories, invalidate_count_and_unref, NULL);
+ g_hash_table_destroy (parent_directories);
+
+ nautilus_profile_end (NULL);
+}
+
+void
+nautilus_directory_notify_files_changed (GList *files)
+{
+ GHashTable *changed_lists;
+ GList *node;
+ GFile *location;
+ g_autoptr (GFile) parent = NULL;
+ g_autoptr (NautilusDirectory) dir = NULL;
+ NautilusFile *file;
+
+ /* Make a list of changed files in each directory. */
+ changed_lists = g_hash_table_new (NULL, NULL);
+
+ /* Go through all the notifications. */
+ for (node = files; node != NULL; node = node->next)
+ {
+ location = node->data;
+
+ /* Find the file. */
+ file = nautilus_file_get_existing (location);
+ if (file != NULL)
+ {
+ NautilusDirectory *directory;
+
+ directory = nautilus_file_get_directory (file);
+
+ /* Tell it to re-get info now, and later emit
+ * a changed signal.
+ */
+ file->details->file_info_is_up_to_date = FALSE;
+ nautilus_file_invalidate_extension_info_internal (file);
+
+ hash_table_list_prepend (changed_lists, directory, file);
+ }
+ else
+ {
+ parent = g_file_get_parent (location);
+ dir = nautilus_directory_get_existing (parent);
+ if (dir != NULL && dir->details->new_files_in_progress != NULL &&
+ files != dir->details->files_changed_while_adding)
+ {
+ dir->details->files_changed_while_adding =
+ g_list_prepend (dir->details->files_changed_while_adding,
+ g_object_ref (location));
+ }
+ }
+ }
+
+ /* Now send out the changed signals. */
+ g_hash_table_foreach (changed_lists, call_files_changed_unref_free_list, NULL);
+ g_hash_table_destroy (changed_lists);
+}
+
+void
+nautilus_directory_notify_files_removed (GList *files)
+{
+ GHashTable *changed_lists;
+ GList *p;
+ GHashTable *parent_directories;
+ NautilusFile *file;
+ GFile *location;
+
+ /* Make a list of changed files in each directory. */
+ changed_lists = g_hash_table_new (NULL, NULL);
+
+ /* Make a list of parent directories that will need their counts updated. */
+ parent_directories = g_hash_table_new (NULL, NULL);
+
+ /* Go through all the notifications. */
+ for (p = files; p != NULL; p = p->next)
+ {
+ NautilusDirectory *directory;
+
+ location = p->data;
+
+ /* Update file count for parent directory if anyone might care. */
+ directory = get_parent_directory_if_exists (location);
+ if (directory != NULL)
+ {
+ collect_parent_directories (parent_directories, directory);
+ nautilus_directory_unref (directory);
+ }
+
+ /* Find the file. */
+ file = nautilus_file_get_existing (location);
+ if (file != NULL && !nautilus_file_rename_in_progress (file))
+ {
+ directory = nautilus_file_get_directory (file);
+
+ /* Mark it gone and prepare to send the changed signal. */
+ nautilus_file_mark_gone (file);
+ hash_table_list_prepend (changed_lists,
+ directory, nautilus_file_ref (file));
+ }
+ nautilus_file_unref (file);
+ }
+
+ /* Now send out the changed signals. */
+ g_hash_table_foreach (changed_lists, call_files_changed_unref_free_list, NULL);
+ g_hash_table_destroy (changed_lists);
+
+ /* Invalidate count for each parent directory. */
+ g_hash_table_foreach (parent_directories, invalidate_count_and_unref, NULL);
+ g_hash_table_destroy (parent_directories);
+}
+
+static void
+set_directory_location (NautilusDirectory *directory,
+ GFile *location)
+{
+ if (directory->details->location)
+ {
+ g_object_unref (directory->details->location);
+ }
+ directory->details->location = g_object_ref (location);
+
+ g_object_notify_by_pspec (G_OBJECT (directory), properties[PROP_LOCATION]);
+}
+
+static void
+change_directory_location (NautilusDirectory *directory,
+ GFile *new_location)
+{
+ /* I believe it's impossible for a self-owned file/directory
+ * to be moved. But if that did somehow happen, this function
+ * wouldn't do enough to handle it.
+ */
+ g_assert (directory->details->as_file == NULL);
+
+ g_hash_table_remove (directories,
+ directory->details->location);
+
+ set_directory_location (directory, new_location);
+
+ g_hash_table_insert (directories,
+ directory->details->location,
+ directory);
+}
+
+typedef struct
+{
+ GFile *container;
+ GList *directories;
+} CollectData;
+
+static void
+collect_directories_by_container (gpointer key,
+ gpointer value,
+ gpointer callback_data)
+{
+ NautilusDirectory *directory;
+ CollectData *collect_data;
+ GFile *location;
+
+ location = (GFile *) key;
+ directory = NAUTILUS_DIRECTORY (value);
+ collect_data = (CollectData *) callback_data;
+
+ if (g_file_has_prefix (location, collect_data->container) ||
+ g_file_equal (collect_data->container, location))
+ {
+ nautilus_directory_ref (directory);
+ collect_data->directories =
+ g_list_prepend (collect_data->directories,
+ directory);
+ }
+}
+
+static GList *
+nautilus_directory_moved_internal (GFile *old_location,
+ GFile *new_location)
+{
+ CollectData collection;
+ NautilusDirectory *directory;
+ GList *node, *affected_files;
+ GFile *new_directory_location;
+ char *relative_path;
+
+ collection.container = old_location;
+ collection.directories = NULL;
+
+ g_hash_table_foreach (directories,
+ collect_directories_by_container,
+ &collection);
+
+ affected_files = NULL;
+
+ for (node = collection.directories; node != NULL; node = node->next)
+ {
+ directory = NAUTILUS_DIRECTORY (node->data);
+ new_directory_location = NULL;
+
+ if (g_file_equal (directory->details->location, old_location))
+ {
+ new_directory_location = g_object_ref (new_location);
+ }
+ else
+ {
+ relative_path = g_file_get_relative_path (old_location,
+ directory->details->location);
+ if (relative_path != NULL)
+ {
+ new_directory_location = g_file_resolve_relative_path (new_location, relative_path);
+ g_free (relative_path);
+ }
+ }
+
+ if (new_directory_location)
+ {
+ change_directory_location (directory, new_directory_location);
+ g_object_unref (new_directory_location);
+
+ /* Collect affected files. */
+ if (directory->details->as_file != NULL)
+ {
+ affected_files = g_list_prepend
+ (affected_files,
+ nautilus_file_ref (directory->details->as_file));
+ }
+ affected_files = g_list_concat
+ (affected_files,
+ nautilus_file_list_copy (directory->details->file_list));
+ }
+
+ nautilus_directory_unref (directory);
+ }
+
+ g_list_free (collection.directories);
+
+ return affected_files;
+}
+
+void
+nautilus_directory_moved (const char *old_uri,
+ const char *new_uri)
+{
+ GList *list, *node;
+ GHashTable *hash;
+ NautilusFile *file;
+ GFile *old_location;
+ GFile *new_location;
+
+ hash = g_hash_table_new (NULL, NULL);
+
+ old_location = g_file_new_for_uri (old_uri);
+ new_location = g_file_new_for_uri (new_uri);
+
+ list = nautilus_directory_moved_internal (old_location, new_location);
+ for (node = list; node != NULL; node = node->next)
+ {
+ NautilusDirectory *directory;
+
+ file = NAUTILUS_FILE (node->data);
+ directory = nautilus_file_get_directory (file);
+
+ hash_table_list_prepend (hash, directory, nautilus_file_ref (file));
+ }
+ nautilus_file_list_free (list);
+
+ g_object_unref (old_location);
+ g_object_unref (new_location);
+
+ g_hash_table_foreach (hash, call_files_changed_unref_free_list, NULL);
+ g_hash_table_destroy (hash);
+}
+
+void
+nautilus_directory_notify_files_moved (GList *file_pairs)
+{
+ GList *p, *affected_files, *node;
+ GFilePair *pair;
+ NautilusFile *file;
+ NautilusDirectory *old_directory, *new_directory;
+ GHashTable *parent_directories;
+ GList *new_files_list, *unref_list;
+ GHashTable *added_lists, *changed_lists;
+ char *name;
+ NautilusFileAttributes cancel_attributes;
+ GFile *to_location, *from_location;
+
+ /* Make a list of added and changed files in each directory. */
+ new_files_list = NULL;
+ added_lists = g_hash_table_new (NULL, NULL);
+ changed_lists = g_hash_table_new (NULL, NULL);
+ unref_list = NULL;
+
+ /* Make a list of parent directories that will need their counts updated. */
+ parent_directories = g_hash_table_new (NULL, NULL);
+
+ cancel_attributes = nautilus_file_get_all_attributes ();
+
+ for (p = file_pairs; p != NULL; p = p->next)
+ {
+ pair = p->data;
+ from_location = pair->from;
+ to_location = pair->to;
+
+ /* Handle overwriting a file. */
+ file = nautilus_file_get_existing (to_location);
+ if (file != NULL)
+ {
+ NautilusDirectory *directory;
+
+ directory = nautilus_file_get_directory (file);
+
+ /* Mark it gone and prepare to send the changed signal. */
+ nautilus_file_mark_gone (file);
+ hash_table_list_prepend (changed_lists, directory, file);
+ collect_parent_directories (parent_directories, directory);
+ }
+
+ /* Update any directory objects that are affected. */
+ affected_files = nautilus_directory_moved_internal (from_location,
+ to_location);
+ for (node = affected_files; node != NULL; node = node->next)
+ {
+ NautilusDirectory *directory;
+
+ file = NAUTILUS_FILE (node->data);
+ directory = nautilus_file_get_directory (file);
+ hash_table_list_prepend (changed_lists, directory, file);
+ }
+ unref_list = g_list_concat (unref_list, affected_files);
+
+ /* Move an existing file. */
+ file = nautilus_file_get_existing (from_location);
+ if (file == NULL)
+ {
+ /* Handle this as if it was a new file. */
+ new_files_list = g_list_prepend (new_files_list,
+ to_location);
+ }
+ else
+ {
+ NautilusDirectory *directory;
+
+ directory = nautilus_file_get_directory (file);
+
+ /* Handle notification in the old directory. */
+ old_directory = directory;
+ collect_parent_directories (parent_directories, old_directory);
+
+ /* Cancel loading of attributes in the old directory */
+ nautilus_directory_cancel_loading_file_attributes
+ (old_directory, file, cancel_attributes);
+
+ /* Locate the new directory. */
+ new_directory = get_parent_directory (to_location);
+ collect_parent_directories (parent_directories, new_directory);
+ /* We can unref now -- new_directory is in the
+ * parent directories list so it will be
+ * around until the end of this function
+ * anyway.
+ */
+ nautilus_directory_unref (new_directory);
+
+ /* Update the file's name and directory. */
+ name = g_file_get_basename (to_location);
+ nautilus_file_update_name_and_directory
+ (file, name, new_directory);
+ g_free (name);
+
+ /* Update file attributes */
+ nautilus_file_invalidate_attributes (file, NAUTILUS_FILE_ATTRIBUTE_INFO);
+
+ hash_table_list_prepend (changed_lists,
+ old_directory,
+ file);
+ if (old_directory != new_directory)
+ {
+ hash_table_list_prepend (added_lists,
+ new_directory,
+ file);
+ }
+
+ /* Unref each file once to balance out nautilus_file_get_by_uri. */
+ unref_list = g_list_prepend (unref_list, file);
+ }
+ }
+
+ /* Now send out the changed and added signals for existing file objects. */
+ g_hash_table_foreach (changed_lists, call_files_changed_free_list, NULL);
+ g_hash_table_destroy (changed_lists);
+ g_hash_table_foreach (added_lists, call_files_added_free_list, NULL);
+ g_hash_table_destroy (added_lists);
+
+ /* Let the file objects go. */
+ nautilus_file_list_free (unref_list);
+
+ /* Invalidate count for each parent directory. */
+ g_hash_table_foreach (parent_directories, invalidate_count_and_unref, NULL);
+ g_hash_table_destroy (parent_directories);
+
+ /* Separate handling for brand new file objects. */
+ nautilus_directory_notify_files_added (new_files_list);
+ g_list_free (new_files_list);
+}
+
+gboolean
+nautilus_directory_contains_file (NautilusDirectory *directory,
+ NautilusFile *file)
+{
+ g_return_val_if_fail (NAUTILUS_IS_DIRECTORY (directory), FALSE);
+ g_return_val_if_fail (NAUTILUS_IS_FILE (file), FALSE);
+
+ if (nautilus_file_is_gone (file))
+ {
+ return FALSE;
+ }
+
+ return NAUTILUS_DIRECTORY_CLASS (G_OBJECT_GET_CLASS (directory))->contains_file (directory, file);
+}
+
+NautilusFile *
+nautilus_directory_get_file_by_name (NautilusDirectory *directory,
+ const gchar *name)
+{
+ GList *files;
+ GList *l;
+ NautilusFile *result = NULL;
+
+ files = nautilus_directory_get_file_list (directory);
+
+ for (l = files; l != NULL; l = l->next)
+ {
+ if (nautilus_file_compare_display_name (l->data, name) == 0)
+ {
+ result = nautilus_file_ref (l->data);
+ break;
+ }
+ }
+
+ nautilus_file_list_free (files);
+
+ return result;
+}
+
+void
+nautilus_directory_call_when_ready (NautilusDirectory *directory,
+ NautilusFileAttributes file_attributes,
+ gboolean wait_for_all_files,
+ NautilusDirectoryCallback callback,
+ gpointer callback_data)
+{
+ g_return_if_fail (NAUTILUS_IS_DIRECTORY (directory));
+ g_return_if_fail (callback != NULL);
+
+ NAUTILUS_DIRECTORY_CLASS (G_OBJECT_GET_CLASS (directory))->call_when_ready
+ (directory, file_attributes, wait_for_all_files,
+ callback, callback_data);
+}
+
+void
+nautilus_directory_cancel_callback (NautilusDirectory *directory,
+ NautilusDirectoryCallback callback,
+ gpointer callback_data)
+{
+ g_return_if_fail (NAUTILUS_IS_DIRECTORY (directory));
+ g_return_if_fail (callback != NULL);
+
+ NAUTILUS_DIRECTORY_CLASS (G_OBJECT_GET_CLASS (directory))->cancel_callback
+ (directory, callback, callback_data);
+}
+
+void
+nautilus_directory_file_monitor_add (NautilusDirectory *directory,
+ gconstpointer client,
+ gboolean monitor_hidden_files,
+ NautilusFileAttributes file_attributes,
+ NautilusDirectoryCallback callback,
+ gpointer callback_data)
+{
+ g_return_if_fail (NAUTILUS_IS_DIRECTORY (directory));
+ g_return_if_fail (client != NULL);
+
+ NAUTILUS_DIRECTORY_CLASS (G_OBJECT_GET_CLASS (directory))->file_monitor_add
+ (directory, client,
+ monitor_hidden_files,
+ file_attributes,
+ callback, callback_data);
+}
+
+void
+nautilus_directory_file_monitor_remove (NautilusDirectory *directory,
+ gconstpointer client)
+{
+ g_return_if_fail (NAUTILUS_IS_DIRECTORY (directory));
+ g_return_if_fail (client != NULL);
+
+ NAUTILUS_DIRECTORY_CLASS (G_OBJECT_GET_CLASS (directory))->file_monitor_remove
+ (directory, client);
+}
+
+void
+nautilus_directory_force_reload (NautilusDirectory *directory)
+{
+ g_return_if_fail (NAUTILUS_IS_DIRECTORY (directory));
+
+ NAUTILUS_DIRECTORY_CLASS (G_OBJECT_GET_CLASS (directory))->force_reload (directory);
+}
+
+gboolean
+nautilus_directory_is_not_empty (NautilusDirectory *directory)
+{
+ g_return_val_if_fail (NAUTILUS_IS_DIRECTORY (directory), FALSE);
+
+ return NAUTILUS_DIRECTORY_CLASS (G_OBJECT_GET_CLASS (directory))->is_not_empty (directory);
+}
+
+GList *
+nautilus_directory_get_file_list (NautilusDirectory *directory)
+{
+ return NAUTILUS_DIRECTORY_CLASS (G_OBJECT_GET_CLASS (directory))->get_file_list (directory);
+}
+
+gboolean
+nautilus_directory_is_editable (NautilusDirectory *directory)
+{
+ return NAUTILUS_DIRECTORY_CLASS (G_OBJECT_GET_CLASS (directory))->is_editable (directory);
+}
+
+GList *
+nautilus_directory_match_pattern (NautilusDirectory *directory,
+ const char *pattern)
+{
+ GList *files, *l, *ret;
+ GPatternSpec *spec;
+
+
+ ret = NULL;
+ spec = g_pattern_spec_new (pattern);
+
+ files = nautilus_directory_get_file_list (directory);
+ for (l = files; l; l = l->next)
+ {
+ NautilusFile *file;
+ char *name;
+
+ file = NAUTILUS_FILE (l->data);
+ name = nautilus_file_get_display_name (file);
+
+ if (g_pattern_match_string (spec, name))
+ {
+ ret = g_list_prepend (ret, nautilus_file_ref (file));
+ }
+
+ g_free (name);
+ }
+
+ g_pattern_spec_free (spec);
+ nautilus_file_list_free (files);
+
+ return ret;
+}
+
+/**
+ * nautilus_directory_list_ref
+ *
+ * Ref all the directories in a list.
+ * @list: GList of directories.
+ **/
+GList *
+nautilus_directory_list_ref (GList *list)
+{
+ g_list_foreach (list, (GFunc) nautilus_directory_ref, NULL);
+ return list;
+}
+
+/**
+ * nautilus_directory_list_unref
+ *
+ * Unref all the directories in a list.
+ * @list: GList of directories.
+ **/
+void
+nautilus_directory_list_unref (GList *list)
+{
+ g_list_foreach (list, (GFunc) nautilus_directory_unref, NULL);
+}
+
+/**
+ * nautilus_directory_list_free
+ *
+ * Free a list of directories after unrefing them.
+ * @list: GList of directories.
+ **/
+void
+nautilus_directory_list_free (GList *list)
+{
+ nautilus_directory_list_unref (list);
+ g_list_free (list);
+}
+
+/**
+ * nautilus_directory_list_copy
+ *
+ * Copy the list of directories, making a new ref of each,
+ * @list: GList of directories.
+ **/
+GList *
+nautilus_directory_list_copy (GList *list)
+{
+ return g_list_copy (nautilus_directory_list_ref (list));
+}
+
+static int
+compare_by_uri (NautilusDirectory *a,
+ NautilusDirectory *b)
+{
+ char *uri_a, *uri_b;
+ int res;
+
+ uri_a = g_file_get_uri (a->details->location);
+ uri_b = g_file_get_uri (b->details->location);
+
+ res = strcmp (uri_a, uri_b);
+
+ g_free (uri_a);
+ g_free (uri_b);
+
+ return res;
+}
+
+static int
+compare_by_uri_cover (gconstpointer a,
+ gconstpointer b)
+{
+ return compare_by_uri (NAUTILUS_DIRECTORY (a), NAUTILUS_DIRECTORY (b));
+}
+
+/**
+ * nautilus_directory_list_sort_by_uri
+ *
+ * Sort the list of directories by directory uri.
+ * @list: GList of directories.
+ **/
+GList *
+nautilus_directory_list_sort_by_uri (GList *list)
+{
+ return g_list_sort (list, compare_by_uri_cover);
+}
+
+#if !defined (NAUTILUS_OMIT_SELF_CHECK)
+
+#include <eel/eel-debug.h>
+
+static int data_dummy;
+static gboolean got_files_flag;
+
+static void
+got_files_callback (NautilusDirectory *directory,
+ GList *files,
+ gpointer callback_data)
+{
+ g_assert (NAUTILUS_IS_DIRECTORY (directory));
+ g_assert (g_list_length (files) > 10);
+ g_assert (callback_data == &data_dummy);
+
+ got_files_flag = TRUE;
+}
+
+/* Return the number of extant NautilusDirectories */
+int
+nautilus_directory_number_outstanding (void)
+{
+ return directories ? g_hash_table_size (directories) : 0;
+}
+
+void
+nautilus_directory_dump (NautilusDirectory *directory)
+{
+ g_autofree gchar *uri = NULL;
+
+ uri = g_file_get_uri (directory->details->location);
+ g_print ("uri: %s\n", uri);
+ g_print ("ref count: %d\n", G_OBJECT (directory)->ref_count);
+}
+
+void
+nautilus_self_check_directory (void)
+{
+ NautilusDirectory *directory;
+ NautilusFile *file;
+
+ directory = nautilus_directory_get_by_uri ("file:///etc");
+ file = nautilus_file_get_by_uri ("file:///etc/passwd");
+
+ EEL_CHECK_INTEGER_RESULT (g_hash_table_size (directories), 1);
+
+ nautilus_directory_file_monitor_add
+ (directory, &data_dummy,
+ TRUE, 0, NULL, NULL);
+
+ /* FIXME: these need to be updated to the new metadata infrastructure
+ * as make check doesn't pass.
+ * nautilus_file_set_metadata (file, "test", "default", "value");
+ * EEL_CHECK_STRING_RESULT (nautilus_file_get_metadata (file, "test", "default"), "value");
+ *
+ * nautilus_file_set_boolean_metadata (file, "test_boolean", TRUE, TRUE);
+ * EEL_CHECK_BOOLEAN_RESULT (nautilus_file_get_boolean_metadata (file, "test_boolean", TRUE), TRUE);
+ * nautilus_file_set_boolean_metadata (file, "test_boolean", TRUE, FALSE);
+ * EEL_CHECK_BOOLEAN_RESULT (nautilus_file_get_boolean_metadata (file, "test_boolean", TRUE), FALSE);
+ * EEL_CHECK_BOOLEAN_RESULT (nautilus_file_get_boolean_metadata (NULL, "test_boolean", TRUE), TRUE);
+ *
+ * nautilus_file_set_integer_metadata (file, "test_integer", 0, 17);
+ * EEL_CHECK_INTEGER_RESULT (nautilus_file_get_integer_metadata (file, "test_integer", 0), 17);
+ * nautilus_file_set_integer_metadata (file, "test_integer", 0, -1);
+ * EEL_CHECK_INTEGER_RESULT (nautilus_file_get_integer_metadata (file, "test_integer", 0), -1);
+ * nautilus_file_set_integer_metadata (file, "test_integer", 42, 42);
+ * EEL_CHECK_INTEGER_RESULT (nautilus_file_get_integer_metadata (file, "test_integer", 42), 42);
+ * EEL_CHECK_INTEGER_RESULT (nautilus_file_get_integer_metadata (NULL, "test_integer", 42), 42);
+ * EEL_CHECK_INTEGER_RESULT (nautilus_file_get_integer_metadata (file, "nonexistent_key", 42), 42);
+ */
+
+ EEL_CHECK_BOOLEAN_RESULT (nautilus_directory_get_by_uri ("file:///etc") == directory, TRUE);
+ nautilus_directory_unref (directory);
+
+ EEL_CHECK_BOOLEAN_RESULT (nautilus_directory_get_by_uri ("file:///etc/") == directory, TRUE);
+ nautilus_directory_unref (directory);
+
+ EEL_CHECK_BOOLEAN_RESULT (nautilus_directory_get_by_uri ("file:///etc////") == directory, TRUE);
+ nautilus_directory_unref (directory);
+
+ nautilus_file_unref (file);
+
+ nautilus_directory_file_monitor_remove (directory, &data_dummy);
+
+ nautilus_directory_unref (directory);
+
+ for (guint i = 0; g_hash_table_size (directories) != 0 && i < 100000; i++)
+ {
+ g_main_context_iteration (NULL, TRUE);
+ }
+
+ EEL_CHECK_INTEGER_RESULT (g_hash_table_size (directories), 0);
+
+ directory = nautilus_directory_get_by_uri ("file:///etc");
+
+ got_files_flag = FALSE;
+
+ nautilus_directory_call_when_ready (directory,
+ NAUTILUS_FILE_ATTRIBUTE_INFO |
+ NAUTILUS_FILE_ATTRIBUTE_DEEP_COUNTS,
+ TRUE,
+ got_files_callback, &data_dummy);
+
+ for (guint i = 0; !got_files_flag && i < 100000; i++)
+ {
+ g_main_context_iteration (NULL, TRUE);
+ }
+
+ EEL_CHECK_BOOLEAN_RESULT (got_files_flag, TRUE);
+
+ EEL_CHECK_BOOLEAN_RESULT (directory->details->file_list == NULL, TRUE);
+
+ EEL_CHECK_INTEGER_RESULT (g_hash_table_size (directories), 1);
+
+ file = nautilus_file_get_by_uri ("file:///etc/passwd");
+
+ /* EEL_CHECK_STRING_RESULT (nautilus_file_get_metadata (file, "test", "default"), "value"); */
+
+ nautilus_file_unref (file);
+
+ nautilus_directory_unref (directory);
+
+ EEL_CHECK_INTEGER_RESULT (g_hash_table_size (directories), 0);
+}
+
+#endif /* !NAUTILUS_OMIT_SELF_CHECK */
diff --git a/src/nautilus-directory.h b/src/nautilus-directory.h
new file mode 100644
index 0000000..70317a3
--- /dev/null
+++ b/src/nautilus-directory.h
@@ -0,0 +1,252 @@
+/*
+ nautilus-directory.h: Nautilus directory model.
+
+ Copyright (C) 1999, 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/>.
+
+ Author: Darin Adler <darin@bentspoon.com>
+*/
+
+#pragma once
+
+#include <gtk/gtk.h>
+#include <gio/gio.h>
+
+#include "nautilus-enums.h"
+
+/* NautilusDirectory is a class that manages the model for a directory,
+ real or virtual, for Nautilus, mainly the file-manager component. The directory is
+ responsible for managing both real data and cached metadata. On top of
+ the file system independence provided by gio, the directory
+ object also provides:
+
+ 1) A synchronization framework, which notifies via signals as the
+ set of known files changes.
+ 2) An abstract interface for getting attributes and performing
+ operations on files.
+*/
+
+#define NAUTILUS_DIRECTORY_PROVIDER_EXTENSION_POINT_NAME "nautilus-directory-provider"
+
+#define NAUTILUS_TYPE_DIRECTORY nautilus_directory_get_type()
+#define NAUTILUS_DIRECTORY(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST ((obj), NAUTILUS_TYPE_DIRECTORY, NautilusDirectory))
+#define NAUTILUS_DIRECTORY_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST ((klass), NAUTILUS_TYPE_DIRECTORY, NautilusDirectoryClass))
+#define NAUTILUS_IS_DIRECTORY(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE ((obj), NAUTILUS_TYPE_DIRECTORY))
+#define NAUTILUS_IS_DIRECTORY_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE ((klass), NAUTILUS_TYPE_DIRECTORY))
+#define NAUTILUS_DIRECTORY_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), NAUTILUS_TYPE_DIRECTORY, NautilusDirectoryClass))
+
+/* NautilusFile is defined both here and in nautilus-file.h. */
+#ifndef NAUTILUS_FILE_DEFINED
+#define NAUTILUS_FILE_DEFINED
+typedef struct NautilusFile NautilusFile;
+#endif
+
+typedef struct _NautilusDirectory NautilusDirectory;
+typedef struct NautilusDirectoryDetails NautilusDirectoryDetails;
+
+struct _NautilusDirectory
+{
+ GObject object;
+ NautilusDirectoryDetails *details;
+};
+
+typedef void (*NautilusDirectoryCallback) (NautilusDirectory *directory,
+ GList *files,
+ gpointer callback_data);
+
+typedef struct
+{
+ GObjectClass parent_class;
+
+ /*** Notification signals for clients to connect to. ***/
+
+ /* The files_added signal is emitted as the directory model
+ * discovers new files.
+ */
+ void (* files_added) (NautilusDirectory *directory,
+ GList *added_files);
+
+ /* The files_changed signal is emitted as changes occur to
+ * existing files that are noticed by the synchronization framework,
+ * including when an old file has been deleted. When an old file
+ * has been deleted, this is the last chance to forget about these
+ * file objects, which are about to be unref'd. Use a call to
+ * nautilus_file_is_gone () to test for this case.
+ */
+ void (* files_changed) (NautilusDirectory *directory,
+ GList *changed_files);
+
+ /* The done_loading signal is emitted when a directory load
+ * request completes. This is needed because, at least in the
+ * case where the directory is empty, the caller will receive
+ * no kind of notification at all when a directory load
+ * initiated by `nautilus_directory_file_monitor_add' completes.
+ */
+ void (* done_loading) (NautilusDirectory *directory);
+
+ void (* load_error) (NautilusDirectory *directory,
+ GError *error);
+
+ /*** Virtual functions for subclasses to override. ***/
+ gboolean (* contains_file) (NautilusDirectory *directory,
+ NautilusFile *file);
+ void (* call_when_ready) (NautilusDirectory *directory,
+ NautilusFileAttributes file_attributes,
+ gboolean wait_for_file_list,
+ NautilusDirectoryCallback callback,
+ gpointer callback_data);
+ void (* cancel_callback) (NautilusDirectory *directory,
+ NautilusDirectoryCallback callback,
+ gpointer callback_data);
+ void (* file_monitor_add) (NautilusDirectory *directory,
+ gconstpointer client,
+ gboolean monitor_hidden_files,
+ NautilusFileAttributes monitor_attributes,
+ NautilusDirectoryCallback initial_files_callback,
+ gpointer callback_data);
+ void (* file_monitor_remove) (NautilusDirectory *directory,
+ gconstpointer client);
+ void (* force_reload) (NautilusDirectory *directory);
+ gboolean (* are_all_files_seen) (NautilusDirectory *directory);
+ gboolean (* is_not_empty) (NautilusDirectory *directory);
+
+ /* get_file_list is a function pointer that subclasses may override to
+ * customize collecting the list of files in a directory.
+ * For example, the NautilusDesktopDirectory overrides this so that it can
+ * merge together the list of files in the $HOME/Desktop directory with
+ * the list of standard icons (Home, Trash) on the desktop.
+ */
+ GList * (* get_file_list) (NautilusDirectory *directory);
+
+ /* Should return FALSE if the directory is read-only and doesn't
+ * allow setting of metadata.
+ * An example of this is the search directory.
+ */
+ gboolean (* is_editable) (NautilusDirectory *directory);
+
+ /* Subclasses can use this to create custom files when asked by the user
+ * or the nautilus cache. */
+ NautilusFile * (* new_file_from_filename) (NautilusDirectory *directory,
+ const char *filename,
+ gboolean self_owned);
+ /* Subclasses can say if they handle the location provided or should the
+ * nautilus file class handle it.
+ */
+ gboolean (* handles_location) (GFile *location);
+} NautilusDirectoryClass;
+
+/* Basic GObject requirements. */
+GType nautilus_directory_get_type (void);
+
+/* Get a directory given a uri.
+ * Creates the appropriate subclass given the uri mappings.
+ * Returns a referenced object, not a floating one. Unref when finished.
+ * If two windows are viewing the same uri, the directory object is shared.
+ */
+NautilusDirectory *nautilus_directory_get (GFile *location);
+NautilusDirectory *nautilus_directory_get_by_uri (const char *uri);
+NautilusDirectory *nautilus_directory_get_for_file (NautilusFile *file);
+
+/* Covers for g_object_ref and g_object_unref that provide two conveniences:
+ * 1) Using these is type safe.
+ * 2) You are allowed to call these with NULL,
+ */
+NautilusDirectory *nautilus_directory_ref (NautilusDirectory *directory);
+void nautilus_directory_unref (NautilusDirectory *directory);
+
+G_DEFINE_AUTOPTR_CLEANUP_FUNC (NautilusDirectory, nautilus_directory_unref)
+
+/* Access to a URI. */
+char * nautilus_directory_get_uri (NautilusDirectory *directory);
+GFile * nautilus_directory_get_location (NautilusDirectory *directory);
+
+/* Is this file still alive and in this directory? */
+gboolean nautilus_directory_contains_file (NautilusDirectory *directory,
+ NautilusFile *file);
+
+NautilusFile* nautilus_directory_get_file_by_name (NautilusDirectory *directory,
+ const gchar *name);
+/* Get (and ref) a NautilusFile object for this directory. */
+NautilusFile * nautilus_directory_get_corresponding_file (NautilusDirectory *directory);
+
+/* Waiting for data that's read asynchronously.
+ * The file attribute and metadata keys are for files in the directory.
+ */
+void nautilus_directory_call_when_ready (NautilusDirectory *directory,
+ NautilusFileAttributes file_attributes,
+ gboolean wait_for_all_files,
+ NautilusDirectoryCallback callback,
+ gpointer callback_data);
+void nautilus_directory_cancel_callback (NautilusDirectory *directory,
+ NautilusDirectoryCallback callback,
+ gpointer callback_data);
+
+
+/* Monitor the files in a directory. */
+void nautilus_directory_file_monitor_add (NautilusDirectory *directory,
+ gconstpointer client,
+ gboolean monitor_hidden_files,
+ NautilusFileAttributes attributes,
+ NautilusDirectoryCallback initial_files_callback,
+ gpointer callback_data);
+void nautilus_directory_file_monitor_remove (NautilusDirectory *directory,
+ gconstpointer client);
+void nautilus_directory_force_reload (NautilusDirectory *directory);
+
+/* Get a list of all files currently known in the directory. */
+GList * nautilus_directory_get_file_list (NautilusDirectory *directory);
+
+GList * nautilus_directory_match_pattern (NautilusDirectory *directory,
+ const char *glob);
+
+
+/* Return true if the directory has information about all the files.
+ * This will be false until the directory has been read at least once.
+ */
+gboolean nautilus_directory_are_all_files_seen (NautilusDirectory *directory);
+
+gboolean nautilus_directory_is_local_or_fuse (NautilusDirectory *directory);
+
+gboolean nautilus_directory_is_in_trash (NautilusDirectory *directory);
+gboolean nautilus_directory_is_in_recent (NautilusDirectory *directory);
+gboolean nautilus_directory_is_in_starred (NautilusDirectory *directory);
+gboolean nautilus_directory_is_in_admin (NautilusDirectory *directory);
+
+/* Return false if directory contains anything besides a Nautilus metafile.
+ * Only valid if directory is monitored. Used by the Trash monitor.
+ */
+gboolean nautilus_directory_is_not_empty (NautilusDirectory *directory);
+
+/* Convenience functions for dealing with a list of NautilusDirectory objects that each have a ref.
+ * These are just convenient names for functions that work on lists of GtkObject *.
+ */
+GList * nautilus_directory_list_ref (GList *directory_list);
+void nautilus_directory_list_unref (GList *directory_list);
+void nautilus_directory_list_free (GList *directory_list);
+GList * nautilus_directory_list_copy (GList *directory_list);
+GList * nautilus_directory_list_sort_by_uri (GList *directory_list);
+
+gboolean nautilus_directory_is_editable (NautilusDirectory *directory);
+
+void nautilus_directory_dump (NautilusDirectory *directory);
+
+NautilusFile * nautilus_directory_new_file_from_filename (NautilusDirectory *directory,
+ const char *filename,
+ gboolean self_owned);
diff --git a/src/nautilus-dnd.c b/src/nautilus-dnd.c
new file mode 100644
index 0000000..9663e01
--- /dev/null
+++ b/src/nautilus-dnd.c
@@ -0,0 +1,317 @@
+/* nautilus-dnd.h - Common Drag & drop handling code
+ *
+ * Authors: Pavel Cisler <pavel@eazel.com>,
+ * Ettore Perazzoli <ettore@gnu.org>
+ * Copyright (C) 2000, 2001 Eazel, Inc.
+ * Copyright (C) 2022 The GNOME project contributors
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#include "nautilus-directory.h"
+#include "nautilus-dnd.h"
+#include "nautilus-file-utilities.h"
+#include "nautilus-tag-manager.h"
+
+static gboolean
+check_same_fs (NautilusFile *file1,
+ NautilusFile *file2)
+{
+ char *id1, *id2;
+ gboolean result;
+
+ result = FALSE;
+
+ if (file1 != NULL && file2 != NULL)
+ {
+ id1 = nautilus_file_get_filesystem_id (file1);
+ id2 = nautilus_file_get_filesystem_id (file2);
+
+ if (id1 != NULL && id2 != NULL)
+ {
+ result = (strcmp (id1, id2) == 0);
+ }
+
+ g_free (id1);
+ g_free (id2);
+ }
+
+ return result;
+}
+
+static gboolean
+source_is_deletable (GFile *file)
+{
+ NautilusFile *naut_file;
+ gboolean ret;
+
+ /* if there's no a cached NautilusFile, it returns NULL */
+ naut_file = nautilus_file_get (file);
+ if (naut_file == NULL)
+ {
+ return FALSE;
+ }
+
+ ret = nautilus_file_can_delete (naut_file);
+ nautilus_file_unref (naut_file);
+
+ return ret;
+}
+
+#if 0 && NAUTILUS_DND_NEEDS_GTK4_REIMPLEMENTATION
+static void
+append_drop_action_menu_item (GtkWidget *menu,
+ const char *text,
+ GdkDragAction action,
+ gboolean sensitive,
+ DropActionMenuData *damd)
+{
+ GtkWidget *menu_item;
+
+ menu_item = gtk_button_new_with_mnemonic (text);
+ gtk_widget_set_sensitive (menu_item, sensitive);
+ gtk_box_append (GTK_BOX (menu), menu_item);
+
+ gtk_style_context_add_class (gtk_widget_get_style_context (menu_item), "flat");
+
+ g_object_set_data (G_OBJECT (menu_item),
+ "action",
+ GINT_TO_POINTER (action));
+
+ g_signal_connect (menu_item, "clicked",
+ G_CALLBACK (drop_action_activated_callback),
+ damd);
+
+ gtk_widget_show (menu_item);
+}
+#endif
+/* Pops up a menu of actions to perform on dropped files */
+GdkDragAction
+nautilus_drag_drop_action_ask (GtkWidget *widget,
+ GdkDragAction actions)
+{
+#if 0
+ GtkWidget *popover;
+ GtkWidget *menu;
+ GtkWidget *menu_item;
+ DropActionMenuData damd;
+
+ /* Create the menu and set the sensitivity of the items based on the
+ * allowed actions.
+ */
+ popover = gtk_popover_new (widget);
+ gtk_popover_set_position (GTK_POPOVER (popover), GTK_POS_TOP);
+
+ menu = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0);
+ gtk_widget_set_margin_top (menu, 6);
+ gtk_widget_set_margin_bottom (menu, 6);
+ gtk_widget_set_margin_start (menu, 6);
+ gtk_widget_set_margin_end (menu, 6);
+ gtk_popover_set_child (GTK_POPOVER (popover), menu);
+ gtk_widget_show (menu);
+
+ append_drop_action_menu_item (menu, _("_Move Here"),
+ GDK_ACTION_MOVE,
+ (actions & GDK_ACTION_MOVE) != 0,
+ &damd);
+
+ append_drop_action_menu_item (menu, _("_Copy Here"),
+ GDK_ACTION_COPY,
+ (actions & GDK_ACTION_COPY) != 0,
+ &damd);
+
+ append_drop_action_menu_item (menu, _("_Link Here"),
+ GDK_ACTION_LINK,
+ (actions & GDK_ACTION_LINK) != 0,
+ &damd);
+
+ menu_item = gtk_separator_new (GTK_ORIENTATION_VERTICAL);
+ gtk_box_append (GTK_BOX (menu), menu_item);
+ gtk_widget_show (menu_item);
+
+ append_drop_action_menu_item (menu, _("Cancel"), 0, TRUE, &damd);
+
+ damd.chosen = 0;
+ damd.loop = g_main_loop_new (NULL, FALSE);
+
+ g_signal_connect (popover, "closed",
+ G_CALLBACK (menu_deactivate_callback),
+ &damd);
+
+ gtk_grab_add (popover);
+
+ /* We don't have pointer coords here. Just pick the center of the widget. */
+ gtk_popover_set_pointing_to (GTK_POPOVER (popover),
+ &(GdkRectangle){ .x = 0.5 * gtk_widget_get_allocated_width (widget),
+ .y = 0.5 * gtk_widget_get_allocated_height (widget),
+ .width = 0, .height = 0 });
+
+ gtk_popover_popup (GTK_POPOVER (popover));
+
+ g_main_loop_run (damd.loop);
+
+ gtk_grab_remove (popover);
+
+ g_main_loop_unref (damd.loop);
+
+ g_object_ref_sink (popover);
+ g_object_unref (popover);
+
+ return damd.chosen;
+#endif
+ return 0;
+}
+
+GdkDragAction
+nautilus_dnd_get_preferred_action (NautilusFile *target_file,
+ GFile *dropped)
+{
+ g_autoptr (NautilusDirectory) directory = NULL;
+ g_autoptr (GFile) target_location = NULL;
+ g_autoptr (NautilusFile) dropped_file = NULL;
+ gboolean same_fs;
+ gboolean source_deletable;
+
+ g_return_val_if_fail (NAUTILUS_IS_FILE (target_file), 0);
+ g_return_val_if_fail (dropped == NULL || G_IS_FILE (dropped), 0);
+
+ target_location = nautilus_file_get_location (target_file);
+ if (g_file_equal (target_location, dropped))
+ {
+ return 0;
+ }
+
+ /* First check target imperatives */
+ directory = nautilus_directory_get_for_file (target_file);
+
+ if (nautilus_is_file_roller_installed () &&
+ nautilus_file_is_archive (target_file))
+ {
+ return GDK_ACTION_COPY;
+ }
+ else if (nautilus_file_is_starred_location (target_file))
+ {
+ if (nautilus_tag_manager_can_star_contents (nautilus_tag_manager_get (), dropped))
+ {
+ return GDK_ACTION_COPY;
+ }
+ else
+ {
+ return 0;
+ }
+ }
+ else if (!nautilus_file_is_directory (target_file) ||
+ !nautilus_file_can_write (target_file) ||
+ !nautilus_directory_is_editable (directory))
+ {
+ /* No other file type other than archives and directories currently
+ * accepts drops */
+ return 0;
+ }
+ else if (nautilus_file_is_in_trash (target_file))
+ {
+ return GDK_ACTION_MOVE;
+ }
+
+ if (g_file_has_uri_scheme (dropped, "trash"))
+ {
+ return GDK_ACTION_MOVE;
+ }
+
+ dropped_file = nautilus_file_get (dropped);
+ same_fs = check_same_fs (target_file, dropped_file);
+ source_deletable = source_is_deletable (dropped);
+ if (same_fs && source_deletable)
+ {
+ return GDK_ACTION_MOVE;
+ }
+
+ return GDK_ACTION_COPY;
+}
+
+#define MAX_DRAWN_DRAG_ICONS 10
+
+GdkPaintable *
+get_paintable_for_drag_selection (GList *selection,
+ int scale)
+{
+ g_autoqueue (GdkPaintable) icons = g_queue_new ();
+ g_autoptr (GtkSnapshot) snapshot = gtk_snapshot_new ();
+ NautilusFileIconFlags flags;
+ GdkPaintable *icon;
+ guint n_icons;
+ guint icon_size = NAUTILUS_DRAG_SURFACE_ICON_SIZE;
+ float dx;
+ float dy;
+ /* A wide shadow for the pile of icons gives a sense of floating. */
+ GskShadow stack_shadow = {.color = {0, 0, 0, .alpha = 0.15}, .dx = 0, .dy = 2, .radius = 10 };
+ /* A slight shadow swhich makes each icon in the stack look separate. */
+ GskShadow icon_shadow = {.color = {0, 0, 0, .alpha = 0.30}, .dx = 0, .dy = 1, .radius = 1 };
+
+ g_return_val_if_fail (NAUTILUS_IS_FILE (selection->data), NULL);
+
+ /* The selection list is reversed compared to what the user sees. Get the
+ * first items by starting from the end of the list. */
+ flags = NAUTILUS_FILE_ICON_FLAGS_USE_THUMBNAILS;
+ for (GList *l = g_list_last (selection);
+ l != NULL && g_queue_get_length (icons) <= MAX_DRAWN_DRAG_ICONS;
+ l = l->prev)
+ {
+ icon = nautilus_file_get_icon_paintable (l->data, icon_size, scale, flags);
+ g_queue_push_tail (icons, icon);
+ }
+
+ /* When there are 2 or 3 identical icons, we need to space them more,
+ * otherwise it would be hard to tell there is more than one icon at all.
+ * The more icons we have, the easier it is to notice multiple icons are
+ * stacked, and the more compact we want to be.
+ *
+ * 1 icon 2 icons 3 icons 4+ icons
+ * .--------. .--------. .--------. .--------.
+ * | | | | | | | |
+ * | | | | | | | |
+ * | | | | | | | |
+ * | | | | | | | |
+ * '--------' |--------| |--------| |--------|
+ * | | | | |--------|
+ * | | |--------| |--------|
+ * '--------' | | |--------|
+ * '--------' '--------'
+ */
+ n_icons = g_queue_get_length (icons);
+ dx = (n_icons % 2 == 1) ? 6 : -6;
+ dy = (n_icons == 2) ? 10 : (n_icons == 3) ? 6 : (n_icons >= 4) ? 4 : 0;
+
+ /* We want the first icon on top of every other. So we need to start drawing
+ * the stack from the bottom, that is, from the last icon. This requires us
+ * to jump to the last position and then move upwards one step at a time.
+ * Also, add 10px horizontal offset, for shadow, to workaround this GTK bug:
+ * https://gitlab.gnome.org/GNOME/gtk/-/issues/2341
+ */
+ gtk_snapshot_translate (snapshot, &GRAPHENE_POINT_INIT (10 + (dx / 2), dy * n_icons));
+ gtk_snapshot_push_shadow (snapshot, &stack_shadow, 1);
+ for (GList *l = g_queue_peek_tail_link (icons); l != NULL; l = l->prev)
+ {
+ double w = gdk_paintable_get_intrinsic_width (l->data);
+ double h = gdk_paintable_get_intrinsic_height (l->data);
+ /* Offsets needed to center thumbnails. Floored to keep images sharp. */
+ float x = floor ((icon_size - w) / 2);
+ float y = floor ((icon_size - h) / 2);
+
+ gtk_snapshot_translate (snapshot, &GRAPHENE_POINT_INIT (-dx, -dy));
+
+ /* Alternate horizontal offset direction to give a rough pile look. */
+ dx = -dx;
+
+ gtk_snapshot_translate (snapshot, &GRAPHENE_POINT_INIT (x, y));
+ gtk_snapshot_push_shadow (snapshot, &icon_shadow, 1);
+
+ gdk_paintable_snapshot (l->data, snapshot, w, h);
+
+ gtk_snapshot_pop (snapshot); /* End of icon shadow */
+ gtk_snapshot_translate (snapshot, &GRAPHENE_POINT_INIT (-x, -y));
+ }
+ gtk_snapshot_pop (snapshot); /* End of stack shadow */
+
+ return gtk_snapshot_to_paintable (snapshot, NULL);
+}
diff --git a/src/nautilus-dnd.h b/src/nautilus-dnd.h
new file mode 100644
index 0000000..1df5725
--- /dev/null
+++ b/src/nautilus-dnd.h
@@ -0,0 +1,26 @@
+/* nautilus-dnd.h - Common Drag & drop handling code
+ *
+ * Authors: Pavel Cisler <pavel@eazel.com>,
+ * Ettore Perazzoli <ettore@gnu.org>
+ * Copyright (C) 2000 Eazel, Inc.
+ * Copyright (C) 2022 The GNOME project contributors
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#pragma once
+
+#include <gtk/gtk.h>
+#include "nautilus-file.h"
+
+#define HOVER_TIMEOUT 500
+
+#define NAUTILUS_DRAG_SURFACE_ICON_SIZE 64
+
+GdkDragAction nautilus_drag_drop_action_ask (GtkWidget *widget,
+ GdkDragAction possible_actions);
+
+GdkDragAction nautilus_dnd_get_preferred_action (NautilusFile *target_file,
+ GFile *dropped);
+GdkPaintable * get_paintable_for_drag_selection (GList *selection,
+ int scale);
diff --git a/src/nautilus-enum-types.c.template b/src/nautilus-enum-types.c.template
new file mode 100644
index 0000000..9d8ac83
--- /dev/null
+++ b/src/nautilus-enum-types.c.template
@@ -0,0 +1,37 @@
+/*** BEGIN file-header ***/
+#include "nautilus-enum-types.h"
+
+/*** END file-header ***/
+
+/*** BEGIN file-production ***/
+/* Enumerations from "@filename@" */
+#include "@filename@"
+
+/*** END file-production ***/
+
+/*** BEGIN value-header ***/
+GType
+@enum_name@_get_type (void)
+{
+ static GType type_once = 0;
+
+ if (g_once_init_enter (&type_once))
+ {
+ static const G@Type@Value values[] = {
+/*** END value-header ***/
+/*** BEGIN value-production ***/
+ { @VALUENAME@, "@VALUENAME@", "@valuenick@" },
+/*** END value-production ***/
+/*** BEGIN value-tail ***/
+ { 0, NULL, NULL }
+ };
+
+ GType type = g_@type@_register_static (g_intern_static_string ("@EnumName@"), values);
+
+ g_once_init_leave (&type_once, type);
+ }
+
+ return type_once;
+}
+
+/*** END value-tail ***/
diff --git a/src/nautilus-enum-types.h.template b/src/nautilus-enum-types.h.template
new file mode 100644
index 0000000..399bbca
--- /dev/null
+++ b/src/nautilus-enum-types.h.template
@@ -0,0 +1,25 @@
+/*** BEGIN file-header ***/
+#ifndef NAUTILUS_ENUM_TYPES_H
+#define NAUTILUS_ENUM_TYPES_H
+
+#include <glib-object.h>
+
+G_BEGIN_DECLS
+/*** END file-header ***/
+
+/*** BEGIN file-production ***/
+/* Enumerations from "@filename@" */
+
+/*** END file-production ***/
+
+/*** BEGIN enumeration-production ***/
+#define @ENUMPREFIX@_TYPE_@ENUMSHORT@ (@enum_name@_get_type ())
+GType @enum_name@_get_type (void);
+
+/*** END enumeration-production ***/
+
+/*** BEGIN file-tail ***/
+G_END_DECLS
+
+#endif /* NAUTILUS_ENUM_TYPES_H */
+/*** END file-tail ***/
diff --git a/src/nautilus-enums.h b/src/nautilus-enums.h
new file mode 100644
index 0000000..022e622
--- /dev/null
+++ b/src/nautilus-enums.h
@@ -0,0 +1,81 @@
+/* Copyright (C) 2018 Ernestas Kulik <ernestask@gnome.org>
+ *
+ * This file is part of Nautilus.
+ *
+ * Nautilus is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Nautilus is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Nautilus. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+/* This is the little brother of nautilus-types.h and only contains enumerations.
+ *
+ * Now that you’ve familiarized yourself with it, the reason for its existence
+ * is similar, and the split is purely for convenience reasons. Include this
+ * when you only need a certain enumeration and not the whole header that might
+ * have had it originally. Otherwise, include both!
+ */
+
+#pragma once
+
+/* Keep sorted alphabetically. */
+
+typedef enum
+{
+ NAUTILUS_GRID_ICON_SIZE_SMALL = 48,
+ NAUTILUS_GRID_ICON_SIZE_SMALL_PLUS = 64,
+ NAUTILUS_GRID_ICON_SIZE_MEDIUM = 96,
+ NAUTILUS_GRID_ICON_SIZE_LARGE = 168,
+ NAUTILUS_GRID_ICON_SIZE_EXTRA_LARGE = 256,
+} NautilusGridIconSize;
+
+typedef enum
+{
+ NAUTILUS_GRID_ZOOM_LEVEL_SMALL,
+ NAUTILUS_GRID_ZOOM_LEVEL_SMALL_PLUS,
+ NAUTILUS_GRID_ZOOM_LEVEL_MEDIUM,
+ NAUTILUS_GRID_ZOOM_LEVEL_LARGE,
+ NAUTILUS_GRID_ZOOM_LEVEL_EXTRA_LARGE,
+} NautilusGridZoomLevel;
+
+typedef enum
+{
+ NAUTILUS_LIST_ICON_SIZE_SMALL = 16,
+ NAUTILUS_LIST_ICON_SIZE_MEDIUM = 32,
+ NAUTILUS_LIST_ICON_SIZE_LARGE = 64,
+} NautilusListIconSize;
+
+typedef enum
+{
+ NAUTILUS_LIST_ZOOM_LEVEL_SMALL,
+ NAUTILUS_LIST_ZOOM_LEVEL_MEDIUM,
+ NAUTILUS_LIST_ZOOM_LEVEL_LARGE,
+} NautilusListZoomLevel;
+
+typedef enum
+{
+ NAUTILUS_FILE_ATTRIBUTE_INFO = 1 << 0, /* All standard info */
+ NAUTILUS_FILE_ATTRIBUTE_DEEP_COUNTS = 1 << 1,
+ NAUTILUS_FILE_ATTRIBUTE_DIRECTORY_ITEM_COUNT = 1 << 2,
+ NAUTILUS_FILE_ATTRIBUTE_DIRECTORY_ITEM_MIME_TYPES = 1 << 3,
+ NAUTILUS_FILE_ATTRIBUTE_EXTENSION_INFO = 1 << 4,
+ NAUTILUS_FILE_ATTRIBUTE_THUMBNAIL = 1 << 5,
+ NAUTILUS_FILE_ATTRIBUTE_MOUNT = 1 << 6,
+ NAUTILUS_FILE_ATTRIBUTE_FILESYSTEM_INFO = 1 << 7,
+} NautilusFileAttributes;
+
+typedef enum
+{
+ NAUTILUS_OPEN_FLAG_NORMAL = 1 << 0,
+ NAUTILUS_OPEN_FLAG_NEW_WINDOW = 1 << 1,
+ NAUTILUS_OPEN_FLAG_NEW_TAB = 1 << 2,
+ NAUTILUS_OPEN_FLAG_DONT_MAKE_ACTIVE = 1 << 3,
+} NautilusOpenFlags;
diff --git a/src/nautilus-error-reporting.c b/src/nautilus-error-reporting.c
new file mode 100644
index 0000000..655730a
--- /dev/null
+++ b/src/nautilus-error-reporting.c
@@ -0,0 +1,446 @@
+/* nautilus-error-reporting.h - implementation of file manager functions that report
+ * errors to the user.
+ *
+ * Copyright (C) 2000 Eazel, Inc.
+ *
+ * The Gnome Library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * The Gnome Library 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
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with the Gnome Library; see the file COPYING.LIB. If not,
+ * see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: John Sullivan <sullivan@eazel.com>
+ */
+
+#include <config.h>
+
+#include "nautilus-error-reporting.h"
+
+#include <string.h>
+#include <glib/gi18n.h>
+#include "nautilus-file.h"
+#include <eel/eel-string.h>
+#include <eel/eel-stock-dialogs.h>
+
+#include "nautilus-ui-utilities.h"
+
+#define DEBUG_FLAG NAUTILUS_DEBUG_DIRECTORY_VIEW
+#include "nautilus-debug.h"
+
+#define NEW_NAME_TAG "Nautilus: new name"
+
+static void finish_rename (NautilusFile *file,
+ gboolean stop_timer,
+ GError *error);
+
+static char *
+get_truncated_name_for_file (NautilusFile *file)
+{
+ g_autofree char *file_name = NULL;
+
+ g_assert (NAUTILUS_IS_FILE (file));
+
+ file_name = nautilus_file_get_display_name (file);
+
+ return eel_str_middle_truncate (file_name, MAXIMUM_DISPLAYED_FILE_NAME_LENGTH);
+}
+
+void
+nautilus_report_error_loading_directory (NautilusFile *file,
+ GError *error,
+ GtkWindow *parent_window)
+{
+ g_autofree char *truncated_name = NULL;
+ g_autofree char *message = NULL;
+
+ if (error == NULL ||
+ error->message == NULL)
+ {
+ return;
+ }
+
+ if (error->domain == G_IO_ERROR &&
+ error->code == G_IO_ERROR_NOT_MOUNTED)
+ {
+ /* This case is retried automatically */
+ return;
+ }
+
+ truncated_name = get_truncated_name_for_file (file);
+
+ if (error->domain == G_IO_ERROR)
+ {
+ switch (error->code)
+ {
+ case G_IO_ERROR_PERMISSION_DENIED:
+ {
+ message = g_strdup_printf (_("You do not have the permissions necessary to view the contents of “%s”."),
+ truncated_name);
+ }
+ break;
+
+ case G_IO_ERROR_NOT_FOUND:
+ {
+ message = g_strdup_printf (_("“%s” could not be found. Perhaps it has recently been deleted."),
+ truncated_name);
+ }
+ break;
+
+ default:
+ {
+ g_autofree char *truncated_error_message = NULL;
+
+ truncated_error_message = eel_str_middle_truncate (error->message,
+ MAXIMUM_DISPLAYED_ERROR_MESSAGE_LENGTH);
+
+ message = g_strdup_printf (_("Sorry, could not display all the contents of “%s”: %s"), truncated_name,
+ truncated_error_message);
+ }
+ break;
+ }
+ }
+ else
+ {
+ message = g_strdup (error->message);
+ }
+
+ show_dialog (_("This location could not be displayed."),
+ message,
+ parent_window,
+ GTK_MESSAGE_ERROR);
+}
+
+void
+nautilus_report_error_setting_group (NautilusFile *file,
+ GError *error,
+ GtkWindow *parent_window)
+{
+ g_autofree char *truncated_name = NULL;
+ g_autofree char *message = NULL;
+
+ if (error == NULL)
+ {
+ return;
+ }
+
+ truncated_name = get_truncated_name_for_file (file);
+
+ if (error->domain == G_IO_ERROR)
+ {
+ switch (error->code)
+ {
+ case G_IO_ERROR_PERMISSION_DENIED:
+ {
+ message = g_strdup_printf (_("You do not have the permissions necessary to change the group of “%s”."),
+ truncated_name);
+ }
+ break;
+
+ default:
+ {
+ }
+ break;
+ }
+ }
+
+ if (message == NULL)
+ {
+ g_autofree char *truncated_error_message = NULL;
+
+ truncated_error_message = eel_str_middle_truncate (error->message,
+ MAXIMUM_DISPLAYED_ERROR_MESSAGE_LENGTH);
+
+ /* We should invent decent error messages for every case we actually experience. */
+ g_warning ("Hit unhandled case %s:%d in nautilus_report_error_setting_group",
+ g_quark_to_string (error->domain), error->code);
+ /* fall through */
+ message = g_strdup_printf (_("Sorry, could not change the group of “%s”: %s"), truncated_name,
+ truncated_error_message);
+ }
+
+
+ show_dialog (_("The group could not be changed."), message, parent_window, GTK_MESSAGE_ERROR);
+}
+
+void
+nautilus_report_error_setting_owner (NautilusFile *file,
+ GError *error,
+ GtkWindow *parent_window)
+{
+ g_autofree char *truncated_name = NULL;
+ g_autofree char *truncated_error_message = NULL;
+ g_autofree char *message = NULL;
+
+ if (error == NULL)
+ {
+ return;
+ }
+
+ truncated_name = get_truncated_name_for_file (file);
+
+ truncated_error_message = eel_str_middle_truncate (error->message,
+ MAXIMUM_DISPLAYED_ERROR_MESSAGE_LENGTH);
+ message = g_strdup_printf (_("Sorry, could not change the owner of “%s”: %s"),
+ truncated_name, truncated_error_message);
+
+ show_dialog (_("The owner could not be changed."), message, parent_window, GTK_MESSAGE_ERROR);
+}
+
+void
+nautilus_report_error_setting_permissions (NautilusFile *file,
+ GError *error,
+ GtkWindow *parent_window)
+{
+ g_autofree char *truncated_name = NULL;
+ g_autofree char *truncated_error_message = NULL;
+ g_autofree char *message = NULL;
+
+ if (error == NULL)
+ {
+ return;
+ }
+
+ truncated_name = get_truncated_name_for_file (file);
+
+ truncated_error_message = eel_str_middle_truncate (error->message,
+ MAXIMUM_DISPLAYED_ERROR_MESSAGE_LENGTH);
+ message = g_strdup_printf (_("Sorry, could not change the permissions of “%s”: %s"),
+ truncated_name, truncated_error_message);
+
+ show_dialog (_("The permissions could not be changed."),
+ message,
+ parent_window,
+ GTK_MESSAGE_ERROR);
+}
+
+typedef struct _NautilusRenameData
+{
+ char *name;
+ NautilusFileOperationCallback callback;
+ gpointer callback_data;
+} NautilusRenameData;
+
+void
+nautilus_report_error_renaming_file (NautilusFile *file,
+ const char *new_name,
+ GError *error,
+ GtkWindow *parent_window)
+{
+ g_autofree char *truncated_old_name = NULL;
+ g_autofree char *truncated_new_name = NULL;
+ g_autofree char *message = NULL;
+
+ /* Truncate names for display since very long file names with no spaces
+ * in them won't get wrapped, and can create insanely wide dialog boxes.
+ */
+ truncated_old_name = get_truncated_name_for_file (file);
+ truncated_new_name = eel_str_middle_truncate (new_name, MAXIMUM_DISPLAYED_FILE_NAME_LENGTH);
+
+ if (error->domain == G_IO_ERROR)
+ {
+ switch (error->code)
+ {
+ case G_IO_ERROR_EXISTS:
+ {
+ message = g_strdup_printf (_("The name “%s” is already used in this location. "
+ "Please use a different name."),
+ truncated_new_name);
+ }
+ break;
+
+ case G_IO_ERROR_NOT_FOUND:
+ {
+ message = g_strdup_printf (_("There is no “%s” in this location. "
+ "Perhaps it was just moved or deleted?"),
+ truncated_old_name);
+ }
+ break;
+
+ case G_IO_ERROR_PERMISSION_DENIED:
+ {
+ message = g_strdup_printf (_("You do not have the permissions necessary to rename “%s”."),
+ truncated_old_name);
+ }
+ break;
+
+ case G_IO_ERROR_INVALID_FILENAME:
+ {
+ if (strchr (new_name, '/') != NULL)
+ {
+ message = g_strdup_printf (_("The name “%s” is not valid because it contains the character “/”. "
+ "Please use a different name."),
+ truncated_new_name);
+ }
+ else
+ {
+ message = g_strdup_printf (_("The name “%s” is not valid. "
+ "Please use a different name."),
+ truncated_new_name);
+ }
+ }
+ break;
+
+ case G_IO_ERROR_FILENAME_TOO_LONG:
+ {
+ message = g_strdup_printf (_("The name “%s” is too long. "
+ "Please use a different name."),
+ truncated_new_name);
+ }
+ break;
+
+ default:
+ {
+ }
+ break;
+ }
+ }
+
+ if (message == NULL)
+ {
+ g_autofree char *truncated_error_message = NULL;
+
+ truncated_error_message = eel_str_middle_truncate (error->message,
+ MAXIMUM_DISPLAYED_ERROR_MESSAGE_LENGTH);
+
+ /* We should invent decent error messages for every case we actually experience. */
+ g_warning ("Hit unhandled case %s:%d in nautilus_report_error_renaming_file",
+ g_quark_to_string (error->domain), error->code);
+ /* fall through */
+ message = g_strdup_printf (_("Sorry, could not rename “%s” to “%s”: %s"),
+ truncated_old_name, truncated_new_name,
+ truncated_error_message);
+ }
+
+ show_dialog (_("The item could not be renamed."), message, parent_window, GTK_MESSAGE_ERROR);
+}
+
+static void
+nautilus_rename_data_free (NautilusRenameData *data)
+{
+ g_free (data->name);
+ g_free (data);
+}
+
+static void
+rename_callback (NautilusFile *file,
+ GFile *result_location,
+ GError *error,
+ gpointer callback_data)
+{
+ NautilusRenameData *data;
+ gboolean cancelled = FALSE;
+
+ g_assert (NAUTILUS_IS_FILE (file));
+ g_assert (callback_data == NULL);
+
+ data = g_object_get_data (G_OBJECT (file), NEW_NAME_TAG);
+ g_assert (data != NULL);
+
+ if (error)
+ {
+ if (!(error->domain == G_IO_ERROR && error->code == G_IO_ERROR_CANCELLED))
+ {
+ GtkApplication *app = GTK_APPLICATION (g_application_get_default ());
+ GtkWindow *window = gtk_application_get_active_window (app);
+
+ /* If rename failed, notify the user. */
+ nautilus_report_error_renaming_file (file, data->name, error, window);
+ }
+ else
+ {
+ cancelled = TRUE;
+ }
+ }
+
+ finish_rename (file, !cancelled, error);
+}
+
+static void
+cancel_rename_callback (gpointer callback_data)
+{
+ nautilus_file_cancel (NAUTILUS_FILE (callback_data), rename_callback, NULL);
+}
+
+static void
+finish_rename (NautilusFile *file,
+ gboolean stop_timer,
+ GError *error)
+{
+ NautilusRenameData *data;
+
+ data = g_object_get_data (G_OBJECT (file), NEW_NAME_TAG);
+ if (data == NULL)
+ {
+ return;
+ }
+
+ /* Cancel both the rename and the timed wait. */
+ nautilus_file_cancel (file, rename_callback, NULL);
+ if (stop_timer)
+ {
+ eel_timed_wait_stop (cancel_rename_callback, file);
+ }
+
+ if (data->callback != NULL)
+ {
+ data->callback (file, NULL, error, data->callback_data);
+ }
+
+ /* Let go of file name. */
+ g_object_set_data (G_OBJECT (file), NEW_NAME_TAG, NULL);
+}
+
+void
+nautilus_rename_file (NautilusFile *file,
+ const char *new_name,
+ NautilusFileOperationCallback callback,
+ gpointer callback_data)
+{
+ g_autoptr (GError) error = NULL;
+ NautilusRenameData *data;
+ g_autofree char *truncated_old_name = NULL;
+ g_autofree char *truncated_new_name = NULL;
+ g_autofree char *wait_message = NULL;
+ g_autofree char *uri = NULL;
+
+ g_return_if_fail (NAUTILUS_IS_FILE (file));
+ g_return_if_fail (new_name != NULL);
+
+ /* Stop any earlier rename that's already in progress. */
+ error = g_error_new (G_IO_ERROR, G_IO_ERROR_CANCELLED, "Cancelled");
+ finish_rename (file, TRUE, error);
+
+ data = g_new0 (NautilusRenameData, 1);
+ data->name = g_strdup (new_name);
+ data->callback = callback;
+ data->callback_data = callback_data;
+
+ /* Attach the new name to the file. */
+ g_object_set_data_full (G_OBJECT (file),
+ NEW_NAME_TAG,
+ data, (GDestroyNotify) nautilus_rename_data_free);
+
+ /* Start the timed wait to cancel the rename. */
+ truncated_old_name = get_truncated_name_for_file (file);
+ truncated_new_name = eel_str_middle_truncate (new_name, MAXIMUM_DISPLAYED_FILE_NAME_LENGTH);
+ wait_message = g_strdup_printf (_("Renaming “%s” to “%s”."),
+ truncated_old_name,
+ truncated_new_name);
+ eel_timed_wait_start (cancel_rename_callback, file, wait_message,
+ NULL); /* FIXME bugzilla.gnome.org 42395: Parent this? */
+
+ uri = nautilus_file_get_uri (file);
+ DEBUG ("Renaming file %s to %s", uri, new_name);
+
+ /* Start the rename. */
+ nautilus_file_rename (file, new_name,
+ rename_callback, NULL);
+}
diff --git a/src/nautilus-error-reporting.h b/src/nautilus-error-reporting.h
new file mode 100644
index 0000000..1d9a229
--- /dev/null
+++ b/src/nautilus-error-reporting.h
@@ -0,0 +1,53 @@
+
+/* fm-error-reporting.h - interface for file manager functions that report
+ errors to the user.
+
+ Copyright (C) 2000 Eazel, Inc.
+
+ The Gnome Library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Library General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version.
+
+ The Gnome Library 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
+ Library General Public License for more details.
+
+ You should have received a copy of the GNU Library General Public
+ License along with the Gnome Library; see the file COPYING.LIB. If not,
+ see <http://www.gnu.org/licenses/>.
+
+ Authors: John Sullivan <sullivan@eazel.com>
+*/
+
+#pragma once
+
+#include <gtk/gtk.h>
+#include "nautilus-file.h"
+
+#define MAXIMUM_DISPLAYED_FILE_NAME_LENGTH 50
+#define MAXIMUM_DISPLAYED_ERROR_MESSAGE_LENGTH 350
+
+void nautilus_report_error_loading_directory (NautilusFile *file,
+ GError *error,
+ GtkWindow *parent_window);
+void nautilus_report_error_renaming_file (NautilusFile *file,
+ const char *new_name,
+ GError *error,
+ GtkWindow *parent_window);
+void nautilus_report_error_setting_permissions (NautilusFile *file,
+ GError *error,
+ GtkWindow *parent_window);
+void nautilus_report_error_setting_owner (NautilusFile *file,
+ GError *error,
+ GtkWindow *parent_window);
+void nautilus_report_error_setting_group (NautilusFile *file,
+ GError *error,
+ GtkWindow *parent_window);
+
+/* FIXME bugzilla.gnome.org 42394: Should this file be renamed or should this function be moved? */
+void nautilus_rename_file (NautilusFile *file,
+ const char *new_name,
+ NautilusFileOperationCallback callback,
+ gpointer callback_data); \ No newline at end of file
diff --git a/src/nautilus-file-changes-queue.c b/src/nautilus-file-changes-queue.c
new file mode 100644
index 0000000..5473271
--- /dev/null
+++ b/src/nautilus-file-changes-queue.c
@@ -0,0 +1,344 @@
+/*
+ * Copyright (C) 1999, 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/>.
+ *
+ * Author: Pavel Cisler <pavel@eazel.com>
+ */
+
+#include <config.h>
+#include "nautilus-file-changes-queue.h"
+
+#include "nautilus-directory-notify.h"
+#include "nautilus-tag-manager.h"
+
+typedef enum
+{
+ CHANGE_FILE_INITIAL,
+ CHANGE_FILE_ADDED,
+ CHANGE_FILE_CHANGED,
+ CHANGE_FILE_REMOVED,
+ CHANGE_FILE_MOVED,
+} NautilusFileChangeKind;
+
+typedef struct
+{
+ NautilusFileChangeKind kind;
+ GFile *from;
+ GFile *to;
+ int screen;
+} NautilusFileChange;
+
+typedef struct
+{
+ GList *head;
+ GList *tail;
+ GMutex mutex;
+} NautilusFileChangesQueue;
+
+static NautilusFileChangesQueue *
+nautilus_file_changes_queue_new (void)
+{
+ NautilusFileChangesQueue *result;
+
+ result = g_new0 (NautilusFileChangesQueue, 1);
+ g_mutex_init (&result->mutex);
+
+ return result;
+}
+
+static NautilusFileChangesQueue *
+nautilus_file_changes_queue_get (void)
+{
+ static NautilusFileChangesQueue *file_changes_queue;
+
+ if (file_changes_queue == NULL)
+ {
+ file_changes_queue = nautilus_file_changes_queue_new ();
+ }
+
+ return file_changes_queue;
+}
+
+static void
+nautilus_file_changes_queue_add_common (NautilusFileChangesQueue *queue,
+ NautilusFileChange *new_item)
+{
+ /* enqueue the new queue item while locking down the list */
+ g_mutex_lock (&queue->mutex);
+
+ queue->head = g_list_prepend (queue->head, new_item);
+ if (queue->tail == NULL)
+ {
+ queue->tail = queue->head;
+ }
+
+ g_mutex_unlock (&queue->mutex);
+}
+
+void
+nautilus_file_changes_queue_file_added (GFile *location)
+{
+ NautilusFileChange *new_item;
+ NautilusFileChangesQueue *queue;
+
+ queue = nautilus_file_changes_queue_get ();
+
+ new_item = g_new0 (NautilusFileChange, 1);
+ new_item->kind = CHANGE_FILE_ADDED;
+ new_item->from = g_object_ref (location);
+ nautilus_file_changes_queue_add_common (queue, new_item);
+}
+
+void
+nautilus_file_changes_queue_file_changed (GFile *location)
+{
+ NautilusFileChange *new_item;
+ NautilusFileChangesQueue *queue;
+
+ queue = nautilus_file_changes_queue_get ();
+
+ new_item = g_new0 (NautilusFileChange, 1);
+ new_item->kind = CHANGE_FILE_CHANGED;
+ new_item->from = g_object_ref (location);
+ nautilus_file_changes_queue_add_common (queue, new_item);
+}
+
+void
+nautilus_file_changes_queue_file_removed (GFile *location)
+{
+ NautilusFileChange *new_item;
+ NautilusFileChangesQueue *queue;
+
+ queue = nautilus_file_changes_queue_get ();
+
+ new_item = g_new0 (NautilusFileChange, 1);
+ new_item->kind = CHANGE_FILE_REMOVED;
+ new_item->from = g_object_ref (location);
+ nautilus_file_changes_queue_add_common (queue, new_item);
+}
+
+void
+nautilus_file_changes_queue_file_moved (GFile *from,
+ GFile *to)
+{
+ NautilusFileChange *new_item;
+ NautilusFileChangesQueue *queue;
+
+ queue = nautilus_file_changes_queue_get ();
+
+ new_item = g_new (NautilusFileChange, 1);
+ new_item->kind = CHANGE_FILE_MOVED;
+ new_item->from = g_object_ref (from);
+ new_item->to = g_object_ref (to);
+ nautilus_file_changes_queue_add_common (queue, new_item);
+}
+
+static NautilusFileChange *
+nautilus_file_changes_queue_get_change (NautilusFileChangesQueue *queue)
+{
+ GList *new_tail;
+ NautilusFileChange *result;
+
+ g_assert (queue != NULL);
+
+ /* dequeue the tail item while locking down the list */
+ g_mutex_lock (&queue->mutex);
+
+ if (queue->tail == NULL)
+ {
+ result = NULL;
+ }
+ else
+ {
+ new_tail = queue->tail->prev;
+ result = queue->tail->data;
+ queue->head = g_list_remove_link (queue->head,
+ queue->tail);
+ g_list_free_1 (queue->tail);
+ queue->tail = new_tail;
+ }
+
+ g_mutex_unlock (&queue->mutex);
+
+ return result;
+}
+
+enum
+{
+ CONSUME_CHANGES_MAX_CHUNK = 20
+};
+
+static void
+pairs_list_free (GList *pairs)
+{
+ GList *p;
+ GFilePair *pair;
+
+ /* deep delete the list of pairs */
+
+ for (p = pairs; p != NULL; p = p->next)
+ {
+ /* delete the strings in each pair */
+ pair = p->data;
+ g_object_unref (pair->from);
+ g_object_unref (pair->to);
+ }
+
+ /* delete the list and the now empty pair structs */
+ g_list_free_full (pairs, g_free);
+}
+
+/* go through changes in the change queue, send ones with the same kind
+ * in a list to the different nautilus_directory_notify calls
+ */
+void
+nautilus_file_changes_consume_changes (gboolean consume_all)
+{
+ NautilusFileChange *change;
+ GList *additions, *changes, *deletions, *moves;
+ GFilePair *pair;
+ guint chunk_count;
+ NautilusFileChangesQueue *queue;
+ gboolean flush_needed;
+
+
+ additions = NULL;
+ changes = NULL;
+ deletions = NULL;
+ moves = NULL;
+
+ queue = nautilus_file_changes_queue_get ();
+
+ /* Consume changes from the queue, stuffing them into one of three lists,
+ * keep doing it while the changes are of the same kind, then send them off.
+ * This is to ensure that the changes get sent off in the same order that they
+ * arrived.
+ */
+ for (chunk_count = 0;; chunk_count++)
+ {
+ change = nautilus_file_changes_queue_get_change (queue);
+
+ /* figure out if we need to flush the pending changes that we collected sofar */
+
+ if (change == NULL)
+ {
+ flush_needed = TRUE;
+ /* no changes left, flush everything */
+ }
+ else
+ {
+ flush_needed = additions != NULL
+ && change->kind != CHANGE_FILE_ADDED;
+
+ flush_needed |= changes != NULL
+ && change->kind != CHANGE_FILE_CHANGED;
+
+ flush_needed |= moves != NULL
+ && change->kind != CHANGE_FILE_MOVED;
+
+ flush_needed |= deletions != NULL
+ && change->kind != CHANGE_FILE_REMOVED;
+
+ flush_needed |= !consume_all && chunk_count >= CONSUME_CHANGES_MAX_CHUNK;
+ /* we have reached the chunk maximum */
+ }
+
+ if (flush_needed)
+ {
+ /* Send changes we collected off.
+ * At one time we may only have one of the lists
+ * contain changes.
+ */
+
+ if (deletions != NULL)
+ {
+ deletions = g_list_reverse (deletions);
+ nautilus_directory_notify_files_removed (deletions);
+ g_list_free_full (deletions, g_object_unref);
+ deletions = NULL;
+ }
+ if (moves != NULL)
+ {
+ moves = g_list_reverse (moves);
+ nautilus_directory_notify_files_moved (moves);
+ pairs_list_free (moves);
+ moves = NULL;
+ }
+ if (additions != NULL)
+ {
+ additions = g_list_reverse (additions);
+ nautilus_directory_notify_files_added (additions);
+ g_list_free_full (additions, g_object_unref);
+ additions = NULL;
+ }
+ if (changes != NULL)
+ {
+ changes = g_list_reverse (changes);
+ nautilus_directory_notify_files_changed (changes);
+ g_list_free_full (changes, g_object_unref);
+ changes = NULL;
+ }
+ }
+
+ if (change == NULL)
+ {
+ /* we are done */
+ return;
+ }
+
+ /* add the new change to the list */
+ switch (change->kind)
+ {
+ case CHANGE_FILE_ADDED:
+ {
+ additions = g_list_prepend (additions, change->from);
+ }
+ break;
+
+ case CHANGE_FILE_CHANGED:
+ {
+ changes = g_list_prepend (changes, change->from);
+ }
+ break;
+
+ case CHANGE_FILE_REMOVED:
+ {
+ deletions = g_list_prepend (deletions, change->from);
+ }
+ break;
+
+ case CHANGE_FILE_MOVED:
+ {
+ nautilus_tag_manager_update_moved_uris (nautilus_tag_manager_get (),
+ change->from,
+ change->to);
+
+ pair = g_new (GFilePair, 1);
+ pair->from = change->from;
+ pair->to = change->to;
+ moves = g_list_prepend (moves, pair);
+ }
+ break;
+
+ default:
+ {
+ g_assert_not_reached ();
+ }
+ break;
+ }
+
+ g_free (change);
+ }
+}
diff --git a/src/nautilus-file-changes-queue.h b/src/nautilus-file-changes-queue.h
new file mode 100644
index 0000000..8f49bc9
--- /dev/null
+++ b/src/nautilus-file-changes-queue.h
@@ -0,0 +1,31 @@
+/*
+ Copyright (C) 1999, 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/>.
+
+ Author: Pavel Cisler <pavel@eazel.com>
+*/
+
+#pragma once
+
+#include <gdk/gdk.h>
+#include <gio/gio.h>
+
+void nautilus_file_changes_queue_file_added (GFile *location);
+void nautilus_file_changes_queue_file_changed (GFile *location);
+void nautilus_file_changes_queue_file_removed (GFile *location);
+void nautilus_file_changes_queue_file_moved (GFile *from,
+ GFile *to);
+
+void nautilus_file_changes_consume_changes (gboolean consume_all);
diff --git a/src/nautilus-file-conflict-dialog.c b/src/nautilus-file-conflict-dialog.c
new file mode 100644
index 0000000..f7c3cc7
--- /dev/null
+++ b/src/nautilus-file-conflict-dialog.c
@@ -0,0 +1,307 @@
+/* nautilus-file-conflict-dialog: dialog that handles file conflicts
+ * during transfer operations.
+ *
+ * Copyright (C) 2008-2010 Cosimo Cecchi
+ *
+ * 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: Cosimo Cecchi <cosimoc@gnome.org>
+ */
+
+#include <config.h>
+#include "nautilus-file-conflict-dialog.h"
+
+#include <string.h>
+#include <glib-object.h>
+#include <gio/gio.h>
+#include <glib/gi18n.h>
+#include <pango/pango.h>
+#include <eel/eel-vfs-extensions.h>
+
+#include "nautilus-file.h"
+#include "nautilus-icon-info.h"
+#include "nautilus-operations-ui-manager.h"
+
+struct _NautilusFileConflictDialog
+{
+ GtkDialog parent_instance;
+
+ gchar *conflict_name;
+ gchar *suggested_name;
+
+ /* UI objects */
+ GtkWidget *primary_label;
+ GtkWidget *secondary_label;
+ GtkWidget *dest_label;
+ GtkWidget *src_label;
+ GtkWidget *expander;
+ GtkWidget *entry;
+ GtkWidget *checkbox;
+ GtkWidget *cancel_button;
+ GtkWidget *skip_button;
+ GtkWidget *rename_button;
+ GtkWidget *replace_button;
+ GtkWidget *dest_icon;
+ GtkWidget *src_icon;
+};
+
+G_DEFINE_TYPE (NautilusFileConflictDialog, nautilus_file_conflict_dialog, GTK_TYPE_DIALOG);
+
+void
+nautilus_file_conflict_dialog_set_text (NautilusFileConflictDialog *fcd,
+ gchar *primary_text,
+ gchar *secondary_text)
+{
+ gtk_label_set_text (GTK_LABEL (fcd->primary_label), primary_text);
+ gtk_label_set_text (GTK_LABEL (fcd->secondary_label), secondary_text);
+}
+
+void
+nautilus_file_conflict_dialog_set_images (NautilusFileConflictDialog *fcd,
+ GdkPaintable *destination_paintable,
+ GdkPaintable *source_paintable)
+{
+ gtk_picture_set_paintable (GTK_PICTURE (fcd->dest_icon), destination_paintable);
+ gtk_picture_set_paintable (GTK_PICTURE (fcd->src_icon), source_paintable);
+}
+
+void
+nautilus_file_conflict_dialog_set_file_labels (NautilusFileConflictDialog *fcd,
+ gchar *destination_label,
+ gchar *source_label)
+{
+ gtk_label_set_markup (GTK_LABEL (fcd->dest_label), destination_label);
+ gtk_label_set_markup (GTK_LABEL (fcd->src_label), source_label);
+}
+
+void
+nautilus_file_conflict_dialog_set_conflict_name (NautilusFileConflictDialog *fcd,
+ gchar *conflict_name)
+{
+ fcd->conflict_name = g_strdup (conflict_name);
+}
+
+void
+nautilus_file_conflict_dialog_set_suggested_name (NautilusFileConflictDialog *fcd,
+ gchar *suggested_name)
+{
+ fcd->suggested_name = g_strdup (suggested_name);
+ gtk_editable_set_text (GTK_EDITABLE (fcd->entry), suggested_name);
+}
+
+void
+nautilus_file_conflict_dialog_set_replace_button_label (NautilusFileConflictDialog *fcd,
+ gchar *label)
+{
+ gtk_button_set_label (GTK_BUTTON (fcd->replace_button), label);
+}
+
+void
+nautilus_file_conflict_dialog_disable_skip (NautilusFileConflictDialog *fcd)
+{
+ gtk_widget_hide (fcd->skip_button);
+}
+
+void
+nautilus_file_conflict_dialog_disable_replace (NautilusFileConflictDialog *fcd)
+{
+ gtk_widget_set_sensitive (fcd->replace_button, FALSE);
+}
+
+void
+nautilus_file_conflict_dialog_disable_apply_to_all (NautilusFileConflictDialog *fcd)
+{
+ gtk_widget_hide (fcd->checkbox);
+}
+
+static void
+entry_text_changed_cb (GtkEditable *entry,
+ NautilusFileConflictDialog *dialog)
+{
+ if (g_strcmp0 (gtk_editable_get_text (GTK_EDITABLE (entry)), "") != 0 &&
+ g_strcmp0 (gtk_editable_get_text (GTK_EDITABLE (entry)), dialog->conflict_name) != 0)
+ {
+ gtk_widget_set_sensitive (dialog->rename_button, TRUE);
+ }
+ else
+ {
+ gtk_widget_set_sensitive (dialog->rename_button, FALSE);
+ }
+}
+
+static int
+get_character_position_after_basename (const gchar *filename)
+{
+ const gchar *extension;
+
+ extension = eel_filename_get_extension_offset (filename);
+
+ if (extension == NULL)
+ {
+ /* If the filename has got no extension, we want the position of the
+ * the terminating null. */
+ return (int) g_utf8_strlen (filename, -1);
+ }
+
+ return g_utf8_pointer_to_offset (filename, extension);
+}
+
+static void
+on_expanded_notify (GtkExpander *w,
+ GParamSpec *pspec,
+ NautilusFileConflictDialog *dialog)
+{
+ if (gtk_expander_get_expanded (w))
+ {
+ gtk_widget_hide (dialog->replace_button);
+ gtk_widget_show (dialog->rename_button);
+ gtk_dialog_set_default_response (GTK_DIALOG (dialog), CONFLICT_RESPONSE_RENAME);
+
+ gtk_widget_set_sensitive (dialog->checkbox, FALSE);
+
+ gtk_widget_grab_focus (dialog->entry);
+ if (g_strcmp0 (gtk_editable_get_text (GTK_EDITABLE (dialog->entry)), dialog->suggested_name) == 0)
+ {
+ /* The suggested name is in the form "original (1).txt", if the
+ * the conflicting name was "original.txt". The user may want to
+ * replace the "(1)" bits with with something more meaningful, so
+ * select this region for convenience. */
+
+ int start_pos;
+ int end_pos;
+
+ start_pos = get_character_position_after_basename (dialog->conflict_name);
+ end_pos = get_character_position_after_basename (dialog->suggested_name);
+
+ gtk_editable_select_region (GTK_EDITABLE (dialog->entry), start_pos, end_pos);
+ }
+ }
+ else
+ {
+ gtk_widget_hide (dialog->rename_button);
+ gtk_widget_show (dialog->replace_button);
+ gtk_dialog_set_default_response (GTK_DIALOG (dialog), CONFLICT_RESPONSE_REPLACE);
+
+ gtk_widget_set_sensitive (dialog->checkbox, TRUE);
+ }
+}
+
+static void
+checkbox_toggled_cb (GtkCheckButton *t,
+ NautilusFileConflictDialog *dialog)
+{
+ gtk_widget_set_sensitive (dialog->expander, !gtk_check_button_get_active (t));
+}
+
+static void
+reset_button_clicked_cb (GtkButton *w,
+ NautilusFileConflictDialog *dialog)
+{
+ int start_pos, end_pos;
+
+ gtk_editable_set_text (GTK_EDITABLE (dialog->entry), dialog->conflict_name);
+ gtk_widget_grab_focus (dialog->entry);
+ eel_filename_get_rename_region (dialog->conflict_name, &start_pos, &end_pos);
+ gtk_editable_select_region (GTK_EDITABLE (dialog->entry), start_pos, end_pos);
+}
+
+static void
+nautilus_file_conflict_dialog_init (NautilusFileConflictDialog *fcd)
+{
+ gtk_widget_init_template (GTK_WIDGET (fcd));
+}
+
+static void
+do_finalize (GObject *self)
+{
+ NautilusFileConflictDialog *dialog = NAUTILUS_FILE_CONFLICT_DIALOG (self);
+
+ g_free (dialog->conflict_name);
+ g_free (dialog->suggested_name);
+
+ G_OBJECT_CLASS (nautilus_file_conflict_dialog_parent_class)->finalize (self);
+}
+
+static void
+nautilus_file_conflict_dialog_class_init (NautilusFileConflictDialogClass *klass)
+{
+ GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+
+ gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/nautilus/ui/nautilus-file-conflict-dialog.ui");
+ gtk_widget_class_bind_template_child (widget_class, NautilusFileConflictDialog, primary_label);
+ gtk_widget_class_bind_template_child (widget_class, NautilusFileConflictDialog, secondary_label);
+ gtk_widget_class_bind_template_child (widget_class, NautilusFileConflictDialog, dest_label);
+ gtk_widget_class_bind_template_child (widget_class, NautilusFileConflictDialog, src_label);
+ gtk_widget_class_bind_template_child (widget_class, NautilusFileConflictDialog, expander);
+ gtk_widget_class_bind_template_child (widget_class, NautilusFileConflictDialog, entry);
+ gtk_widget_class_bind_template_child (widget_class, NautilusFileConflictDialog, checkbox);
+ gtk_widget_class_bind_template_child (widget_class, NautilusFileConflictDialog, cancel_button);
+ gtk_widget_class_bind_template_child (widget_class, NautilusFileConflictDialog, rename_button);
+ gtk_widget_class_bind_template_child (widget_class, NautilusFileConflictDialog, replace_button);
+ gtk_widget_class_bind_template_child (widget_class, NautilusFileConflictDialog, skip_button);
+ gtk_widget_class_bind_template_child (widget_class, NautilusFileConflictDialog, dest_icon);
+ gtk_widget_class_bind_template_child (widget_class, NautilusFileConflictDialog, src_icon);
+ gtk_widget_class_bind_template_callback (widget_class, entry_text_changed_cb);
+ gtk_widget_class_bind_template_callback (widget_class, on_expanded_notify);
+ gtk_widget_class_bind_template_callback (widget_class, checkbox_toggled_cb);
+ gtk_widget_class_bind_template_callback (widget_class, reset_button_clicked_cb);
+
+ G_OBJECT_CLASS (klass)->finalize = do_finalize;
+}
+
+static gboolean
+activate_buttons (NautilusFileConflictDialog *fcd)
+{
+ gtk_widget_set_sensitive (GTK_WIDGET (fcd->cancel_button), TRUE);
+ gtk_widget_set_sensitive (GTK_WIDGET (fcd->skip_button), TRUE);
+ gtk_widget_set_sensitive (GTK_WIDGET (fcd->rename_button), TRUE);
+ gtk_widget_set_sensitive (GTK_WIDGET (fcd->replace_button), TRUE);
+ gtk_widget_set_sensitive (GTK_WIDGET (fcd->expander), TRUE);
+ return G_SOURCE_REMOVE;
+}
+
+void
+nautilus_file_conflict_dialog_delay_buttons_activation (NautilusFileConflictDialog *fcd)
+{
+ gtk_widget_set_sensitive (GTK_WIDGET (fcd->cancel_button), FALSE);
+ gtk_widget_set_sensitive (GTK_WIDGET (fcd->skip_button), FALSE);
+ gtk_widget_set_sensitive (GTK_WIDGET (fcd->rename_button), FALSE);
+ gtk_widget_set_sensitive (GTK_WIDGET (fcd->replace_button), FALSE);
+ gtk_widget_set_sensitive (GTK_WIDGET (fcd->expander), FALSE);
+
+ g_timeout_add_seconds (BUTTON_ACTIVATION_DELAY_IN_SECONDS,
+ G_SOURCE_FUNC (activate_buttons),
+ fcd);
+}
+
+char *
+nautilus_file_conflict_dialog_get_new_name (NautilusFileConflictDialog *dialog)
+{
+ return g_strdup (gtk_editable_get_text (GTK_EDITABLE (dialog->entry)));
+}
+
+gboolean
+nautilus_file_conflict_dialog_get_apply_to_all (NautilusFileConflictDialog *dialog)
+{
+ return gtk_check_button_get_active (GTK_CHECK_BUTTON (dialog->checkbox));
+}
+
+NautilusFileConflictDialog *
+nautilus_file_conflict_dialog_new (GtkWindow *parent)
+{
+ return NAUTILUS_FILE_CONFLICT_DIALOG (g_object_new (NAUTILUS_TYPE_FILE_CONFLICT_DIALOG,
+ "transient-for", parent,
+ "use-header-bar", TRUE,
+ NULL));
+}
diff --git a/src/nautilus-file-conflict-dialog.h b/src/nautilus-file-conflict-dialog.h
new file mode 100644
index 0000000..70f81d0
--- /dev/null
+++ b/src/nautilus-file-conflict-dialog.h
@@ -0,0 +1,62 @@
+
+/* nautilus-file-conflict-dialog: dialog that handles file conflicts
+ during transfer operations.
+
+ Copyright (C) 2008, Cosimo Cecchi
+
+ 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: Cosimo Cecchi <cosimoc@gnome.org>
+*/
+
+#pragma once
+
+#include <glib-object.h>
+#include <gio/gio.h>
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+#define NAUTILUS_TYPE_FILE_CONFLICT_DIALOG (nautilus_file_conflict_dialog_get_type ())
+
+G_DECLARE_FINAL_TYPE (NautilusFileConflictDialog, nautilus_file_conflict_dialog, NAUTILUS, FILE_CONFLICT_DIALOG, GtkDialog)
+
+NautilusFileConflictDialog* nautilus_file_conflict_dialog_new (GtkWindow *parent);
+
+void nautilus_file_conflict_dialog_set_text (NautilusFileConflictDialog *fcd,
+ gchar *primary_text,
+ gchar *secondary_text);
+void nautilus_file_conflict_dialog_set_images (NautilusFileConflictDialog *fcd,
+ GdkPaintable *source_paintable,
+ GdkPaintable *destination_paintable);
+void nautilus_file_conflict_dialog_set_file_labels (NautilusFileConflictDialog *fcd,
+ gchar *destination_label,
+ gchar *source_label);
+void nautilus_file_conflict_dialog_set_conflict_name (NautilusFileConflictDialog *fcd,
+ gchar *conflict_name);
+void nautilus_file_conflict_dialog_set_suggested_name (NautilusFileConflictDialog *fcd,
+ gchar *suggested_name);
+void nautilus_file_conflict_dialog_set_replace_button_label (NautilusFileConflictDialog *fcd,
+ gchar *label);
+
+void nautilus_file_conflict_dialog_disable_skip (NautilusFileConflictDialog *fcd);
+void nautilus_file_conflict_dialog_disable_replace (NautilusFileConflictDialog *fcd);
+void nautilus_file_conflict_dialog_disable_apply_to_all (NautilusFileConflictDialog *fcd);
+
+void nautilus_file_conflict_dialog_delay_buttons_activation (NautilusFileConflictDialog *fdc);
+
+char* nautilus_file_conflict_dialog_get_new_name (NautilusFileConflictDialog *dialog);
+gboolean nautilus_file_conflict_dialog_get_apply_to_all (NautilusFileConflictDialog *dialog);
+
+G_END_DECLS
diff --git a/src/nautilus-file-name-widget-controller.c b/src/nautilus-file-name-widget-controller.c
new file mode 100644
index 0000000..db048b7
--- /dev/null
+++ b/src/nautilus-file-name-widget-controller.c
@@ -0,0 +1,522 @@
+/* nautilus-file-name-widget-controller.c
+ *
+ * Copyright (C) 2016 the Nautilus developers
+ *
+ * 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/>.
+ *
+ */
+
+#include <glib/gi18n.h>
+
+#include "nautilus-file-name-widget-controller.h"
+#include "nautilus-file-utilities.h"
+
+#define FILE_NAME_DUPLICATED_LABEL_TIMEOUT 500
+
+typedef struct
+{
+ GtkWidget *error_revealer;
+ GtkWidget *error_label;
+ GtkWidget *name_entry;
+ GtkWidget *activate_button;
+ NautilusDirectory *containing_directory;
+
+ gboolean duplicated_is_folder;
+ gint duplicated_label_timeout_id;
+} NautilusFileNameWidgetControllerPrivate;
+
+enum
+{
+ NAME_ACCEPTED,
+ CANCELLED,
+ LAST_SIGNAL
+};
+
+enum
+{
+ PROP_ERROR_REVEALER = 1,
+ PROP_ERROR_LABEL,
+ PROP_NAME_ENTRY,
+ PROP_ACTION_BUTTON,
+ PROP_CONTAINING_DIRECTORY,
+ NUM_PROPERTIES
+};
+
+static guint signals[LAST_SIGNAL];
+
+G_DEFINE_ABSTRACT_TYPE_WITH_PRIVATE (NautilusFileNameWidgetController, nautilus_file_name_widget_controller, G_TYPE_OBJECT)
+
+gchar *
+nautilus_file_name_widget_controller_get_new_name (NautilusFileNameWidgetController *self)
+{
+ return NAUTILUS_FILE_NAME_WIDGET_CONTROLLER_GET_CLASS (self)->get_new_name (self);
+}
+
+void
+nautilus_file_name_widget_controller_set_containing_directory (NautilusFileNameWidgetController *self,
+ NautilusDirectory *directory)
+{
+ g_assert (NAUTILUS_IS_DIRECTORY (directory));
+
+ g_object_set (self, "containing-directory", directory, NULL);
+}
+
+gboolean
+nautilus_file_name_widget_controller_is_name_too_long (NautilusFileNameWidgetController *self,
+ gchar *name)
+{
+ NautilusFileNameWidgetControllerPrivate *priv;
+ size_t name_length;
+ g_autoptr (GFile) location = NULL;
+ glong max_name_length;
+
+ priv = nautilus_file_name_widget_controller_get_instance_private (self);
+ name_length = strlen (name);
+ location = nautilus_directory_get_location (priv->containing_directory);
+ max_name_length = nautilus_get_max_child_name_length_for_location (location);
+
+ if (max_name_length == -1)
+ {
+ /* We don't know, so let's give it a chance */
+ return FALSE;
+ }
+ else
+ {
+ return name_length > max_name_length + 1;
+ }
+}
+
+static gboolean
+nautilus_file_name_widget_controller_name_is_valid (NautilusFileNameWidgetController *self,
+ gchar *name,
+ gchar **error_message)
+{
+ return NAUTILUS_FILE_NAME_WIDGET_CONTROLLER_GET_CLASS (self)->name_is_valid (self,
+ name,
+ error_message);
+}
+
+static gboolean
+nautilus_file_name_widget_controller_ignore_existing_file (NautilusFileNameWidgetController *self,
+ NautilusFile *existing_file)
+{
+ return NAUTILUS_FILE_NAME_WIDGET_CONTROLLER_GET_CLASS (self)->ignore_existing_file (self,
+ existing_file);
+}
+
+static gchar *
+real_get_new_name (NautilusFileNameWidgetController *self)
+{
+ NautilusFileNameWidgetControllerPrivate *priv;
+
+ priv = nautilus_file_name_widget_controller_get_instance_private (self);
+
+ return g_strstrip (g_strdup (gtk_editable_get_text (GTK_EDITABLE (priv->name_entry))));
+}
+
+static gboolean
+real_name_is_valid (NautilusFileNameWidgetController *self,
+ gchar *name,
+ gchar **error_message)
+{
+ gboolean is_valid;
+
+ is_valid = TRUE;
+ if (strlen (name) == 0)
+ {
+ is_valid = FALSE;
+ }
+ else if (strstr (name, "/") != NULL)
+ {
+ is_valid = FALSE;
+ *error_message = _("File names cannot contain “/”.");
+ }
+ else if (strcmp (name, ".") == 0)
+ {
+ is_valid = FALSE;
+ *error_message = _("A file cannot be called “.”.");
+ }
+ else if (strcmp (name, "..") == 0)
+ {
+ is_valid = FALSE;
+ *error_message = _("A file cannot be called “..”.");
+ }
+ else if (nautilus_file_name_widget_controller_is_name_too_long (self, name))
+ {
+ is_valid = FALSE;
+ *error_message = _("File name is too long.");
+ }
+
+ if (is_valid && g_str_has_prefix (name, "."))
+ {
+ /* We must warn about the side effect */
+ *error_message = _("Files with “.” at the beginning of their name are hidden.");
+ }
+
+ return is_valid;
+}
+
+static gboolean
+real_ignore_existing_file (NautilusFileNameWidgetController *self,
+ NautilusFile *existing_file)
+{
+ return FALSE;
+}
+
+static gboolean
+duplicated_file_label_show (NautilusFileNameWidgetController *self)
+{
+ NautilusFileNameWidgetControllerPrivate *priv;
+
+ priv = nautilus_file_name_widget_controller_get_instance_private (self);
+ if (priv->duplicated_is_folder)
+ {
+ gtk_label_set_label (GTK_LABEL (priv->error_label),
+ _("A folder with that name already exists."));
+ }
+ else
+ {
+ gtk_label_set_label (GTK_LABEL (priv->error_label),
+ _("A file with that name already exists."));
+ }
+
+ gtk_revealer_set_reveal_child (GTK_REVEALER (priv->error_revealer),
+ TRUE);
+
+ priv->duplicated_label_timeout_id = 0;
+
+ return G_SOURCE_REMOVE;
+}
+
+static void
+file_name_widget_controller_process_new_name (NautilusFileNameWidgetController *controller,
+ gboolean *duplicated_name,
+ gboolean *valid_name)
+{
+ NautilusFileNameWidgetControllerPrivate *priv;
+ g_autofree gchar *name = NULL;
+ gchar *error_message = NULL;
+ NautilusFile *existing_file;
+ priv = nautilus_file_name_widget_controller_get_instance_private (controller);
+
+ g_return_if_fail (NAUTILUS_IS_DIRECTORY (priv->containing_directory));
+
+ name = nautilus_file_name_widget_controller_get_new_name (controller);
+ *valid_name = nautilus_file_name_widget_controller_name_is_valid (controller,
+ name,
+ &error_message);
+
+ gtk_label_set_label (GTK_LABEL (priv->error_label), error_message);
+ gtk_revealer_set_reveal_child (GTK_REVEALER (priv->error_revealer),
+ error_message != NULL);
+
+ existing_file = nautilus_directory_get_file_by_name (priv->containing_directory, name);
+ *duplicated_name = existing_file != NULL &&
+ !nautilus_file_name_widget_controller_ignore_existing_file (controller,
+ existing_file);
+
+ gtk_widget_set_sensitive (priv->activate_button, *valid_name && !*duplicated_name);
+
+ if (priv->duplicated_label_timeout_id != 0)
+ {
+ g_source_remove (priv->duplicated_label_timeout_id);
+ priv->duplicated_label_timeout_id = 0;
+ }
+
+ if (*duplicated_name)
+ {
+ priv->duplicated_is_folder = nautilus_file_is_directory (existing_file);
+ }
+
+ if (existing_file != NULL)
+ {
+ nautilus_file_unref (existing_file);
+ }
+}
+
+static void
+file_name_widget_controller_on_changed_directory_info_ready (NautilusDirectory *directory,
+ GList *files,
+ gpointer user_data)
+{
+ NautilusFileNameWidgetController *controller;
+ NautilusFileNameWidgetControllerPrivate *priv;
+ gboolean duplicated_name;
+ gboolean valid_name;
+
+ controller = NAUTILUS_FILE_NAME_WIDGET_CONTROLLER (user_data);
+ priv = nautilus_file_name_widget_controller_get_instance_private (controller);
+
+ file_name_widget_controller_process_new_name (controller,
+ &duplicated_name,
+ &valid_name);
+
+ /* Report duplicated file only if not other message shown (for instance,
+ * folders like "." or ".." will always exists, but we consider it as an
+ * error, not as a duplicated file or if the name is the same as the file
+ * we are renaming also don't report as a duplicated */
+ if (duplicated_name && valid_name)
+ {
+ priv->duplicated_label_timeout_id = g_timeout_add (FILE_NAME_DUPLICATED_LABEL_TIMEOUT,
+ (GSourceFunc) duplicated_file_label_show,
+ controller);
+ }
+}
+
+static void
+file_name_widget_controller_on_changed (gpointer user_data)
+{
+ NautilusFileNameWidgetController *controller;
+ NautilusFileNameWidgetControllerPrivate *priv;
+
+ controller = NAUTILUS_FILE_NAME_WIDGET_CONTROLLER (user_data);
+ priv = nautilus_file_name_widget_controller_get_instance_private (controller);
+
+ nautilus_directory_call_when_ready (priv->containing_directory,
+ NAUTILUS_FILE_ATTRIBUTE_INFO,
+ TRUE,
+ file_name_widget_controller_on_changed_directory_info_ready,
+ controller);
+}
+
+static void
+file_name_widget_controller_on_activate_directory_info_ready (NautilusDirectory *directory,
+ GList *files,
+ gpointer user_data)
+{
+ NautilusFileNameWidgetController *controller;
+ gboolean duplicated_name;
+ gboolean valid_name;
+
+ controller = NAUTILUS_FILE_NAME_WIDGET_CONTROLLER (user_data);
+
+ file_name_widget_controller_process_new_name (controller,
+ &duplicated_name,
+ &valid_name);
+
+ if (valid_name && !duplicated_name)
+ {
+ g_signal_emit (controller, signals[NAME_ACCEPTED], 0);
+ }
+ else
+ {
+ /* Report duplicated file only if not other message shown (for instance,
+ * folders like "." or ".." will always exists, but we consider it as an
+ * error, not as a duplicated file) */
+ if (duplicated_name && valid_name)
+ {
+ /* Show it inmediatily since the user tried to trigger the action */
+ duplicated_file_label_show (controller);
+ }
+ }
+}
+
+static void
+file_name_widget_controller_on_activate (gpointer user_data)
+{
+ NautilusFileNameWidgetController *controller;
+ NautilusFileNameWidgetControllerPrivate *priv;
+
+ controller = NAUTILUS_FILE_NAME_WIDGET_CONTROLLER (user_data);
+ priv = nautilus_file_name_widget_controller_get_instance_private (controller);
+
+ nautilus_directory_call_when_ready (priv->containing_directory,
+ NAUTILUS_FILE_ATTRIBUTE_INFO,
+ TRUE,
+ file_name_widget_controller_on_activate_directory_info_ready,
+ controller);
+}
+
+static void
+nautilus_file_name_widget_controller_init (NautilusFileNameWidgetController *self)
+{
+ NautilusFileNameWidgetControllerPrivate *priv;
+
+ priv = nautilus_file_name_widget_controller_get_instance_private (self);
+
+ priv->containing_directory = NULL;
+}
+
+static void
+nautilus_file_name_widget_controller_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ NautilusFileNameWidgetController *controller;
+ NautilusFileNameWidgetControllerPrivate *priv;
+
+ controller = NAUTILUS_FILE_NAME_WIDGET_CONTROLLER (object);
+ priv = nautilus_file_name_widget_controller_get_instance_private (controller);
+
+ switch (prop_id)
+ {
+ case PROP_ERROR_REVEALER:
+ {
+ priv->error_revealer = GTK_WIDGET (g_value_get_object (value));
+ }
+ break;
+
+ case PROP_ERROR_LABEL:
+ {
+ priv->error_label = GTK_WIDGET (g_value_get_object (value));
+ }
+ break;
+
+ case PROP_NAME_ENTRY:
+ {
+ priv->name_entry = GTK_WIDGET (g_value_get_object (value));
+
+ g_signal_connect_swapped (G_OBJECT (priv->name_entry),
+ "activate",
+ (GCallback) file_name_widget_controller_on_activate,
+ controller);
+ g_signal_connect_swapped (G_OBJECT (priv->name_entry),
+ "changed",
+ (GCallback) file_name_widget_controller_on_changed,
+ controller);
+ }
+ break;
+
+ case PROP_ACTION_BUTTON:
+ {
+ priv->activate_button = GTK_WIDGET (g_value_get_object (value));
+
+ g_signal_connect_swapped (G_OBJECT (priv->activate_button),
+ "clicked",
+ (GCallback) file_name_widget_controller_on_activate,
+ controller);
+ }
+ break;
+
+ case PROP_CONTAINING_DIRECTORY:
+ {
+ g_clear_object (&priv->containing_directory);
+
+ priv->containing_directory = NAUTILUS_DIRECTORY (g_value_dup_object (value));
+ }
+ break;
+
+ default:
+ {
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+ break;
+ }
+}
+
+static void
+nautilus_file_name_widget_controller_finalize (GObject *object)
+{
+ NautilusFileNameWidgetController *self;
+ NautilusFileNameWidgetControllerPrivate *priv;
+
+ self = NAUTILUS_FILE_NAME_WIDGET_CONTROLLER (object);
+ priv = nautilus_file_name_widget_controller_get_instance_private (self);
+
+ if (priv->containing_directory != NULL)
+ {
+ nautilus_directory_cancel_callback (priv->containing_directory,
+ file_name_widget_controller_on_changed_directory_info_ready,
+ self);
+ nautilus_directory_cancel_callback (priv->containing_directory,
+ file_name_widget_controller_on_activate_directory_info_ready,
+ self);
+ g_clear_object (&priv->containing_directory);
+ }
+
+ if (priv->duplicated_label_timeout_id > 0)
+ {
+ g_source_remove (priv->duplicated_label_timeout_id);
+ priv->duplicated_label_timeout_id = 0;
+ }
+
+ G_OBJECT_CLASS (nautilus_file_name_widget_controller_parent_class)->finalize (object);
+}
+
+static void
+nautilus_file_name_widget_controller_class_init (NautilusFileNameWidgetControllerClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->set_property = nautilus_file_name_widget_controller_set_property;
+ object_class->finalize = nautilus_file_name_widget_controller_finalize;
+
+ klass->get_new_name = real_get_new_name;
+ klass->name_is_valid = real_name_is_valid;
+ klass->ignore_existing_file = real_ignore_existing_file;
+
+ signals[NAME_ACCEPTED] =
+ g_signal_new ("name-accepted",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_FIRST,
+ G_STRUCT_OFFSET (NautilusFileNameWidgetControllerClass, name_accepted),
+ NULL, NULL,
+ g_cclosure_marshal_generic,
+ G_TYPE_NONE, 0);
+ signals[CANCELLED] =
+ g_signal_new ("cancelled",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST,
+ 0,
+ NULL, NULL,
+ g_cclosure_marshal_generic,
+ G_TYPE_NONE, 0);
+
+ g_object_class_install_property (
+ object_class,
+ PROP_ERROR_REVEALER,
+ g_param_spec_object ("error-revealer",
+ "Error Revealer",
+ "The error label revealer",
+ GTK_TYPE_WIDGET,
+ G_PARAM_WRITABLE |
+ G_PARAM_CONSTRUCT_ONLY));
+
+ g_object_class_install_property (
+ object_class,
+ PROP_ERROR_LABEL,
+ g_param_spec_object ("error-label",
+ "Error Label",
+ "The label used for displaying errors",
+ GTK_TYPE_WIDGET,
+ G_PARAM_WRITABLE |
+ G_PARAM_CONSTRUCT_ONLY));
+ g_object_class_install_property (
+ object_class,
+ PROP_NAME_ENTRY,
+ g_param_spec_object ("name-entry",
+ "Name Entry",
+ "The entry for the file name",
+ GTK_TYPE_WIDGET,
+ G_PARAM_WRITABLE |
+ G_PARAM_CONSTRUCT_ONLY));
+ g_object_class_install_property (
+ object_class,
+ PROP_ACTION_BUTTON,
+ g_param_spec_object ("activate-button",
+ "Activate Button",
+ "The activate button of the widget",
+ GTK_TYPE_WIDGET,
+ G_PARAM_WRITABLE |
+ G_PARAM_CONSTRUCT_ONLY));
+ g_object_class_install_property (
+ object_class,
+ PROP_CONTAINING_DIRECTORY,
+ g_param_spec_object ("containing-directory",
+ "Containing Directory",
+ "The directory used to check for duplicate names",
+ NAUTILUS_TYPE_DIRECTORY,
+ G_PARAM_WRITABLE));
+}
diff --git a/src/nautilus-file-name-widget-controller.h b/src/nautilus-file-name-widget-controller.h
new file mode 100644
index 0000000..a492e2c
--- /dev/null
+++ b/src/nautilus-file-name-widget-controller.h
@@ -0,0 +1,52 @@
+/* nautilus-file-name-widget-controller.h
+ *
+ * Copyright (C) 2016 the Nautilus developers
+ *
+ * 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/>.
+ *
+ */
+
+#pragma once
+
+#include <glib.h>
+#include <gtk/gtk.h>
+
+#include "nautilus-file.h"
+#include "nautilus-directory.h"
+
+#define NAUTILUS_TYPE_FILE_NAME_WIDGET_CONTROLLER nautilus_file_name_widget_controller_get_type ()
+G_DECLARE_DERIVABLE_TYPE (NautilusFileNameWidgetController, nautilus_file_name_widget_controller, NAUTILUS, FILE_NAME_WIDGET_CONTROLLER, GObject)
+
+struct _NautilusFileNameWidgetControllerClass
+{
+ GObjectClass parent_class;
+
+ gchar * (*get_new_name) (NautilusFileNameWidgetController *controller);
+
+ gboolean (*name_is_valid) (NautilusFileNameWidgetController *controller,
+ gchar *name,
+ gchar **error_message);
+
+ gboolean (*ignore_existing_file) (NautilusFileNameWidgetController *controller,
+ NautilusFile *existing_file);
+
+ void (*name_accepted) (NautilusFileNameWidgetController *controller);
+};
+
+gchar * nautilus_file_name_widget_controller_get_new_name (NautilusFileNameWidgetController *controller);
+
+void nautilus_file_name_widget_controller_set_containing_directory (NautilusFileNameWidgetController *controller,
+ NautilusDirectory *directory);
+gboolean nautilus_file_name_widget_controller_is_name_too_long (NautilusFileNameWidgetController *self,
+ gchar *name);
diff --git a/src/nautilus-file-operations-dbus-data.c b/src/nautilus-file-operations-dbus-data.c
new file mode 100644
index 0000000..666253f
--- /dev/null
+++ b/src/nautilus-file-operations-dbus-data.c
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2020 Alberts Muktupāvels
+ *
+ * Nautilus is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * Nautilus is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+#include "nautilus-file-operations-dbus-data.h"
+
+struct _NautilusFileOperationsDBusData
+{
+ gatomicrefcount ref_count;
+
+ char *parent_handle;
+
+ guint32 timestamp;
+};
+
+NautilusFileOperationsDBusData *
+nautilus_file_operations_dbus_data_new (GVariant *platform_data)
+{
+ NautilusFileOperationsDBusData *self;
+ GVariantDict dict;
+
+ self = g_new0 (NautilusFileOperationsDBusData, 1);
+ g_atomic_ref_count_init (&self->ref_count);
+
+ g_variant_dict_init (&dict, platform_data);
+
+ g_variant_dict_lookup (&dict, "parent-handle", "s", &self->parent_handle);
+ g_variant_dict_lookup (&dict, "timestamp", "u", &self->timestamp);
+
+ return self;
+}
+
+NautilusFileOperationsDBusData *
+nautilus_file_operations_dbus_data_ref (NautilusFileOperationsDBusData *self)
+{
+ g_atomic_ref_count_inc (&self->ref_count);
+
+ return self;
+}
+
+void
+nautilus_file_operations_dbus_data_unref (NautilusFileOperationsDBusData *self)
+{
+ if (g_atomic_ref_count_dec (&self->ref_count))
+ {
+ g_free (self->parent_handle);
+ g_free (self);
+ }
+}
+
+const char *
+nautilus_file_operations_dbus_data_get_parent_handle (NautilusFileOperationsDBusData *self)
+{
+ return self->parent_handle;
+}
+
+guint32
+nautilus_file_operations_dbus_data_get_timestamp (NautilusFileOperationsDBusData *self)
+{
+ return self->timestamp;
+}
diff --git a/src/nautilus-file-operations-dbus-data.h b/src/nautilus-file-operations-dbus-data.h
new file mode 100644
index 0000000..ca719fc
--- /dev/null
+++ b/src/nautilus-file-operations-dbus-data.h
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2020 Alberts Muktupāvels
+ *
+ * Nautilus is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * Nautilus is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include <glib.h>
+
+typedef struct _NautilusFileOperationsDBusData NautilusFileOperationsDBusData;
+
+NautilusFileOperationsDBusData *nautilus_file_operations_dbus_data_new (GVariant *platform_data);
+
+NautilusFileOperationsDBusData *nautilus_file_operations_dbus_data_ref (NautilusFileOperationsDBusData *self);
+
+void nautilus_file_operations_dbus_data_unref (NautilusFileOperationsDBusData *self);
+
+const char *nautilus_file_operations_dbus_data_get_parent_handle (NautilusFileOperationsDBusData *self);
+
+guint32 nautilus_file_operations_dbus_data_get_timestamp (NautilusFileOperationsDBusData *self);
+
+G_DEFINE_AUTOPTR_CLEANUP_FUNC (NautilusFileOperationsDBusData, nautilus_file_operations_dbus_data_unref)
diff --git a/src/nautilus-file-operations.c b/src/nautilus-file-operations.c
new file mode 100644
index 0000000..9a8829e
--- /dev/null
+++ b/src/nautilus-file-operations.c
@@ -0,0 +1,9183 @@
+/* nautilus-file-operations.c - Nautilus file operations.
+ *
+ * Copyright (C) 1999, 2000 Free Software Foundation
+ * Copyright (C) 2000, 2001 Eazel, Inc.
+ * Copyright (C) 2007 Red Hat, 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: Alexander Larsson <alexl@redhat.com>
+ * Ettore Perazzoli <ettore@gnu.org>
+ * Pavel Cisler <pavel@eazel.com>
+ */
+
+#include <config.h>
+#include <string.h>
+#include <stdio.h>
+#include <stdarg.h>
+#include <locale.h>
+#include <math.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <stdlib.h>
+
+#include "nautilus-file-operations.h"
+
+#include "nautilus-file-changes-queue.h"
+#include "nautilus-lib-self-check-functions.h"
+
+#include "nautilus-progress-info.h"
+
+#include <eel/eel-glib-extensions.h>
+#include <eel/eel-vfs-extensions.h>
+#include <eel/eel-string.h>
+
+#include <glib/gi18n.h>
+#include <glib/gstdio.h>
+#include <gdk/gdk.h>
+#include <gtk/gtk.h>
+#include <gio/gio.h>
+#include <glib.h>
+#include <libadwaita-1/adwaita.h>
+
+#include "nautilus-error-reporting.h"
+#include "nautilus-operations-ui-manager.h"
+#include "nautilus-file-changes-queue.h"
+#include "nautilus-file-private.h"
+#include "nautilus-tag-manager.h"
+#include "nautilus-trash-monitor.h"
+#include "nautilus-file-utilities.h"
+#include "nautilus-file-undo-operations.h"
+#include "nautilus-file-undo-manager.h"
+#include "nautilus-ui-utilities.h"
+
+#ifdef GDK_WINDOWING_X11
+#include <gdk/x11/gdkx.h>
+#endif
+
+#ifdef GDK_WINDOWING_WAYLAND
+#include <gdk/wayland/gdkwayland.h>
+#endif
+
+typedef struct
+{
+ GTimer *time;
+ GtkWindow *parent_window;
+ NautilusFileOperationsDBusData *dbus_data;
+ guint inhibit_cookie;
+ NautilusProgressInfo *progress;
+ GCancellable *cancellable;
+ GHashTable *skip_files;
+ GHashTable *skip_readdir_error;
+ NautilusFileUndoInfo *undo_info;
+ gboolean skip_all_error;
+ gboolean skip_all_conflict;
+ gboolean merge_all;
+ gboolean replace_all;
+ gboolean delete_all;
+} CommonJob;
+
+typedef struct
+{
+ CommonJob common;
+ gboolean is_move;
+ GList *files;
+ GFile *destination;
+ GFile *fake_display_source;
+ GHashTable *debuting_files;
+ gchar *target_name;
+ NautilusCopyCallback done_callback;
+ gpointer done_callback_data;
+} CopyMoveJob;
+
+typedef struct
+{
+ CommonJob common;
+ GList *files;
+ gboolean try_trash;
+ gboolean user_cancel;
+ NautilusDeleteCallback done_callback;
+ gpointer done_callback_data;
+} DeleteJob;
+
+typedef struct
+{
+ CommonJob common;
+ GFile *dest_dir;
+ char *filename;
+ gboolean make_dir;
+ GFile *src;
+ char *src_data;
+ int length;
+ GFile *created_file;
+ NautilusCreateCallback done_callback;
+ gpointer done_callback_data;
+} CreateJob;
+
+
+typedef struct
+{
+ CommonJob common;
+ GList *trash_dirs;
+ gboolean should_confirm;
+ NautilusOpCallback done_callback;
+ gpointer done_callback_data;
+} EmptyTrashJob;
+
+typedef struct
+{
+ CommonJob common;
+ GFile *file;
+ gboolean interactive;
+ NautilusOpCallback done_callback;
+ gpointer done_callback_data;
+} MarkTrustedJob;
+
+typedef struct
+{
+ CommonJob common;
+ GFile *file;
+ NautilusOpCallback done_callback;
+ gpointer done_callback_data;
+ guint32 file_permissions;
+ guint32 file_mask;
+ guint32 dir_permissions;
+ guint32 dir_mask;
+} SetPermissionsJob;
+
+typedef enum
+{
+ OP_KIND_COPY,
+ OP_KIND_MOVE,
+ OP_KIND_DELETE,
+ OP_KIND_TRASH,
+ OP_KIND_COMPRESS
+} OpKind;
+
+typedef struct
+{
+ int num_files_children;
+ goffset num_bytes_children;
+} SourceDirInfo;
+
+typedef struct
+{
+ int num_files;
+ goffset num_bytes;
+ int num_files_since_progress;
+ OpKind op;
+ GHashTable *scanned_dirs_info;
+} SourceInfo;
+
+typedef struct
+{
+ int num_files;
+ goffset num_bytes;
+ OpKind op;
+ guint64 last_report_time;
+ int last_reported_files_left;
+
+ /*
+ * This is used when reporting progress for copy/move operations to not show
+ * the remaining time. This is needed because some GVfs backends doesn't
+ * report progress from those operations. Consequently it looks like that it
+ * is hanged when the remaining time is not updated regularly. See:
+ * https://gitlab.gnome.org/GNOME/nautilus/-/merge_requests/605
+ */
+ gboolean partial_progress;
+} TransferInfo;
+
+typedef struct
+{
+ CommonJob common;
+ GList *source_files;
+ GFile *destination_directory;
+ GList *output_files;
+ gboolean destination_decided;
+ gboolean extraction_failed;
+
+ gdouble base_progress;
+
+ guint64 archive_compressed_size;
+ guint64 total_compressed_size;
+ gint total_files;
+
+ NautilusExtractCallback done_callback;
+ gpointer done_callback_data;
+} ExtractJob;
+
+typedef struct
+{
+ CommonJob common;
+ GList *source_files;
+ GFile *output_file;
+
+ AutoarFormat format;
+ AutoarFilter filter;
+ gchar *passphrase;
+
+ guint64 total_size;
+ guint total_files;
+
+ gboolean success;
+
+ NautilusCreateCallback done_callback;
+ gpointer done_callback_data;
+} CompressJob;
+
+static void
+source_info_clear (SourceInfo *source_info)
+{
+ if (source_info->scanned_dirs_info != NULL)
+ {
+ g_hash_table_unref (source_info->scanned_dirs_info);
+ }
+}
+
+G_DEFINE_AUTO_CLEANUP_CLEAR_FUNC (SourceInfo, source_info_clear)
+
+#define SOURCE_INFO_INIT { 0 }
+#define SECONDS_NEEDED_FOR_RELIABLE_TRANSFER_RATE 8
+#define NSEC_PER_MICROSEC 1000
+#define PROGRESS_NOTIFY_INTERVAL 100 * NSEC_PER_MICROSEC
+#define LONG_JOB_THRESHOLD_IN_SECONDS 2
+
+#define MAXIMUM_DISPLAYED_FILE_NAME_LENGTH 50
+
+#define IS_IO_ERROR(__error, KIND) (((__error)->domain == G_IO_ERROR && (__error)->code == G_IO_ERROR_ ## KIND))
+
+#define CANCEL _("_Cancel")
+#define SKIP _("_Skip")
+#define SKIP_ALL _("S_kip All")
+#define RETRY _("_Retry")
+#define DELETE _("_Delete")
+#define DELETE_ALL _("Delete _All")
+#define REPLACE _("_Replace")
+#define REPLACE_ALL _("Replace _All")
+#define MERGE _("_Merge")
+#define MERGE_ALL _("Merge _All")
+#define COPY_FORCE _("Copy _Anyway")
+#define EMPTY_TRASH _("Empty _Trash")
+
+static gboolean
+is_all_button_text (const char *button_text)
+{
+ g_assert (button_text != NULL);
+
+ return !strcmp (button_text, SKIP_ALL) ||
+ !strcmp (button_text, REPLACE_ALL) ||
+ !strcmp (button_text, DELETE_ALL) ||
+ !strcmp (button_text, MERGE_ALL);
+}
+
+static void scan_sources (GList *files,
+ SourceInfo *source_info,
+ CommonJob *job,
+ OpKind kind);
+
+
+static void empty_trash_thread_func (GTask *task,
+ gpointer source_object,
+ gpointer task_data,
+ GCancellable *cancellable);
+
+static void empty_trash_task_done (GObject *source_object,
+ GAsyncResult *res,
+ gpointer user_data);
+
+static char *query_fs_type (GFile *file,
+ GCancellable *cancellable);
+
+static void nautilus_file_operations_copy (GTask *task,
+ gpointer source_object,
+ gpointer task_data,
+ GCancellable *cancellable);
+
+static void nautilus_file_operations_move (GTask *task,
+ gpointer source_object,
+ gpointer task_data,
+ GCancellable *cancellable);
+
+/* keep in time with get_formatted_time ()
+ *
+ * This counts and outputs the number of “time units”
+ * formatted and displayed by get_formatted_time ().
+ * For instance, if get_formatted_time outputs “3 hours, 4 minutes”
+ * it yields 7.
+ */
+static int
+seconds_count_format_time_units (int seconds)
+{
+ int minutes;
+ int hours;
+
+ if (seconds < 0)
+ {
+ /* Just to make sure... */
+ seconds = 0;
+ }
+
+ if (seconds < 60)
+ {
+ /* seconds */
+ return seconds;
+ }
+
+ if (seconds < 60 * 60)
+ {
+ /* minutes */
+ minutes = seconds / 60;
+ return minutes;
+ }
+
+ hours = seconds / (60 * 60);
+
+ if (seconds < 60 * 60 * 4)
+ {
+ /* minutes + hours */
+ minutes = (seconds - hours * 60 * 60) / 60;
+ return minutes + hours;
+ }
+
+ return hours;
+}
+
+static gchar *
+get_formatted_time (int seconds)
+{
+ int minutes;
+ int hours;
+ gchar *res;
+
+ if (seconds < 0)
+ {
+ /* Just to make sure... */
+ seconds = 0;
+ }
+
+ if (seconds < 60)
+ {
+ return g_strdup_printf (ngettext ("%'d second", "%'d seconds", (int) seconds), (int) seconds);
+ }
+
+ if (seconds < 60 * 60)
+ {
+ minutes = seconds / 60;
+ return g_strdup_printf (ngettext ("%'d minute", "%'d minutes", minutes), minutes);
+ }
+
+ hours = seconds / (60 * 60);
+
+ if (seconds < 60 * 60 * 4)
+ {
+ gchar *h, *m;
+
+ minutes = (seconds - hours * 60 * 60) / 60;
+
+ h = g_strdup_printf (ngettext ("%'d hour", "%'d hours", hours), hours);
+ m = g_strdup_printf (ngettext ("%'d minute", "%'d minutes", minutes), minutes);
+ res = g_strconcat (h, ", ", m, NULL);
+ g_free (h);
+ g_free (m);
+ return res;
+ }
+
+ return g_strdup_printf (ngettext ("%'d hour",
+ "%'d hours",
+ hours), hours);
+}
+
+static char *
+shorten_utf8_string (const char *base,
+ int reduce_by_num_bytes)
+{
+ int len;
+ char *ret;
+ const char *p;
+
+ len = strlen (base);
+ len -= reduce_by_num_bytes;
+
+ if (len <= 0)
+ {
+ return NULL;
+ }
+
+ ret = g_new (char, len + 1);
+
+ p = base;
+ while (len)
+ {
+ char *next;
+ next = g_utf8_next_char (p);
+ if (next - p > len || *next == '\0')
+ {
+ break;
+ }
+
+ len -= next - p;
+ p = next;
+ }
+
+ if (p - base == 0)
+ {
+ g_free (ret);
+ return NULL;
+ }
+ else
+ {
+ memcpy (ret, base, p - base);
+ ret[p - base] = '\0';
+ return ret;
+ }
+}
+
+/* Note that we have these two separate functions with separate format
+ * strings for ease of localization.
+ */
+
+static char *
+get_link_name (const char *name,
+ int count,
+ int max_length)
+{
+ const char *format;
+ char *result;
+ int unshortened_length;
+ gboolean use_count;
+
+ g_assert (name != NULL);
+
+ if (count < 0)
+ {
+ g_warning ("bad count in get_link_name");
+ count = 0;
+ }
+
+ if (count <= 2)
+ {
+ /* Handle special cases for low numbers.
+ * Perhaps for some locales we will need to add more.
+ */
+ switch (count)
+ {
+ default:
+ {
+ g_assert_not_reached ();
+ /* fall through */
+ }
+
+ case 0:
+ {
+ /* duplicate original file name */
+ format = "%s";
+ }
+ break;
+
+ case 1:
+ {
+ /* appended to new link file */
+ format = _("Link to %s");
+ }
+ break;
+
+ case 2:
+ {
+ /* appended to new link file */
+ format = _("Another link to %s");
+ }
+ break;
+ }
+
+ use_count = FALSE;
+ }
+ else
+ {
+ /* Handle special cases for the first few numbers of each ten.
+ * For locales where getting this exactly right is difficult,
+ * these can just be made all the same as the general case below.
+ */
+ switch (count % 10)
+ {
+ case 1:
+ {
+ /* Localizers: Feel free to leave out the "st" suffix
+ * if there's no way to do that nicely for a
+ * particular language.
+ */
+ format = _("%'dst link to %s");
+ }
+ break;
+
+ case 2:
+ {
+ /* appended to new link file */
+ format = _("%'dnd link to %s");
+ }
+ break;
+
+ case 3:
+ {
+ /* appended to new link file */
+ format = _("%'drd link to %s");
+ }
+ break;
+
+ default:
+ {
+ /* appended to new link file */
+ format = _("%'dth link to %s");
+ }
+ break;
+ }
+
+ use_count = TRUE;
+ }
+
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wformat-nonliteral"
+ if (use_count)
+ {
+ result = g_strdup_printf (format, count, name);
+ }
+ else
+ {
+ result = g_strdup_printf (format, name);
+ }
+
+ if (max_length > 0 && (unshortened_length = strlen (result)) > max_length)
+ {
+ char *new_name;
+
+ new_name = shorten_utf8_string (name, unshortened_length - max_length);
+ if (new_name)
+ {
+ g_free (result);
+
+ if (use_count)
+ {
+ result = g_strdup_printf (format, count, new_name);
+ }
+ else
+ {
+ result = g_strdup_printf (format, new_name);
+ }
+
+ g_assert (strlen (result) <= max_length);
+ g_free (new_name);
+ }
+ }
+#pragma GCC diagnostic pop
+ return result;
+}
+
+
+/* Localizers:
+ * Feel free to leave out the st, nd, rd and th suffix or
+ * make some or all of them match.
+ */
+
+/* localizers: tag used to detect the first copy of a file */
+static const char untranslated_copy_duplicate_tag[] = N_(" (copy)");
+/* localizers: tag used to detect the second copy of a file */
+static const char untranslated_another_copy_duplicate_tag[] = N_(" (another copy)");
+
+/* localizers: tag used to detect the x11th copy of a file */
+static const char untranslated_x11th_copy_duplicate_tag[] = N_("th copy)");
+/* localizers: tag used to detect the x12th copy of a file */
+static const char untranslated_x12th_copy_duplicate_tag[] = N_("th copy)");
+/* localizers: tag used to detect the x13th copy of a file */
+static const char untranslated_x13th_copy_duplicate_tag[] = N_("th copy)");
+
+/* localizers: tag used to detect the x1st copy of a file */
+static const char untranslated_st_copy_duplicate_tag[] = N_("st copy)");
+/* localizers: tag used to detect the x2nd copy of a file */
+static const char untranslated_nd_copy_duplicate_tag[] = N_("nd copy)");
+/* localizers: tag used to detect the x3rd copy of a file */
+static const char untranslated_rd_copy_duplicate_tag[] = N_("rd copy)");
+
+/* localizers: tag used to detect the xxth copy of a file */
+static const char untranslated_th_copy_duplicate_tag[] = N_("th copy)");
+
+#define COPY_DUPLICATE_TAG _(untranslated_copy_duplicate_tag)
+#define ANOTHER_COPY_DUPLICATE_TAG _(untranslated_another_copy_duplicate_tag)
+#define X11TH_COPY_DUPLICATE_TAG _(untranslated_x11th_copy_duplicate_tag)
+#define X12TH_COPY_DUPLICATE_TAG _(untranslated_x12th_copy_duplicate_tag)
+#define X13TH_COPY_DUPLICATE_TAG _(untranslated_x13th_copy_duplicate_tag)
+
+#define ST_COPY_DUPLICATE_TAG _(untranslated_st_copy_duplicate_tag)
+#define ND_COPY_DUPLICATE_TAG _(untranslated_nd_copy_duplicate_tag)
+#define RD_COPY_DUPLICATE_TAG _(untranslated_rd_copy_duplicate_tag)
+#define TH_COPY_DUPLICATE_TAG _(untranslated_th_copy_duplicate_tag)
+
+/* localizers: appended to first file copy */
+static const char untranslated_first_copy_duplicate_format[] = N_("%s (copy)%s");
+/* localizers: appended to second file copy */
+static const char untranslated_second_copy_duplicate_format[] = N_("%s (another copy)%s");
+
+/* localizers: appended to x11th file copy */
+static const char untranslated_x11th_copy_duplicate_format[] = N_("%s (%'dth copy)%s");
+/* localizers: appended to x12th file copy */
+static const char untranslated_x12th_copy_duplicate_format[] = N_("%s (%'dth copy)%s");
+/* localizers: appended to x13th file copy */
+static const char untranslated_x13th_copy_duplicate_format[] = N_("%s (%'dth copy)%s");
+
+/* localizers: if in your language there's no difference between 1st, 2nd, 3rd and nth
+ * plurals, you can leave the st, nd, rd suffixes out and just make all the translated
+ * strings look like "%s (copy %'d)%s".
+ */
+
+/* localizers: appended to x1st file copy */
+static const char untranslated_st_copy_duplicate_format[] = N_("%s (%'dst copy)%s");
+/* localizers: appended to x2nd file copy */
+static const char untranslated_nd_copy_duplicate_format[] = N_("%s (%'dnd copy)%s");
+/* localizers: appended to x3rd file copy */
+static const char untranslated_rd_copy_duplicate_format[] = N_("%s (%'drd copy)%s");
+/* localizers: appended to xxth file copy */
+static const char untranslated_th_copy_duplicate_format[] = N_("%s (%'dth copy)%s");
+
+#define FIRST_COPY_DUPLICATE_FORMAT _(untranslated_first_copy_duplicate_format)
+#define SECOND_COPY_DUPLICATE_FORMAT _(untranslated_second_copy_duplicate_format)
+#define X11TH_COPY_DUPLICATE_FORMAT _(untranslated_x11th_copy_duplicate_format)
+#define X12TH_COPY_DUPLICATE_FORMAT _(untranslated_x12th_copy_duplicate_format)
+#define X13TH_COPY_DUPLICATE_FORMAT _(untranslated_x13th_copy_duplicate_format)
+
+#define ST_COPY_DUPLICATE_FORMAT _(untranslated_st_copy_duplicate_format)
+#define ND_COPY_DUPLICATE_FORMAT _(untranslated_nd_copy_duplicate_format)
+#define RD_COPY_DUPLICATE_FORMAT _(untranslated_rd_copy_duplicate_format)
+#define TH_COPY_DUPLICATE_FORMAT _(untranslated_th_copy_duplicate_format)
+
+static char *
+extract_string_until (const char *original,
+ const char *until_substring)
+{
+ char *result;
+
+ g_assert ((int) strlen (original) >= until_substring - original);
+ g_assert (until_substring - original >= 0);
+
+ result = g_malloc (until_substring - original + 1);
+ strncpy (result, original, until_substring - original);
+ result[until_substring - original] = '\0';
+
+ return result;
+}
+
+/* Dismantle a file name, separating the base name, the file suffix and removing any
+ * (xxxcopy), etc. string. Figure out the count that corresponds to the given
+ * (xxxcopy) substring.
+ */
+static void
+parse_previous_duplicate_name (const char *name,
+ char **name_base,
+ const char **suffix,
+ int *count,
+ gboolean ignore_extension)
+{
+ const char *tag;
+
+ g_assert (name[0] != '\0');
+
+ *suffix = eel_filename_get_extension_offset (name);
+
+ if (*suffix == NULL || (*suffix)[1] == '\0')
+ {
+ /* no suffix */
+ *suffix = "";
+ }
+
+ tag = strstr (name, COPY_DUPLICATE_TAG);
+ if (tag != NULL)
+ {
+ if (tag > *suffix)
+ {
+ /* handle case "foo. (copy)" */
+ *suffix = "";
+ }
+ *name_base = extract_string_until (name, tag);
+ *count = 1;
+ return;
+ }
+
+
+ tag = strstr (name, ANOTHER_COPY_DUPLICATE_TAG);
+ if (tag != NULL)
+ {
+ if (tag > *suffix)
+ {
+ /* handle case "foo. (another copy)" */
+ *suffix = "";
+ }
+ *name_base = extract_string_until (name, tag);
+ *count = 2;
+ return;
+ }
+
+
+ /* Check to see if we got one of st, nd, rd, th. */
+ tag = strstr (name, X11TH_COPY_DUPLICATE_TAG);
+
+ if (tag == NULL)
+ {
+ tag = strstr (name, X12TH_COPY_DUPLICATE_TAG);
+ }
+ if (tag == NULL)
+ {
+ tag = strstr (name, X13TH_COPY_DUPLICATE_TAG);
+ }
+
+ if (tag == NULL)
+ {
+ tag = strstr (name, ST_COPY_DUPLICATE_TAG);
+ }
+ if (tag == NULL)
+ {
+ tag = strstr (name, ND_COPY_DUPLICATE_TAG);
+ }
+ if (tag == NULL)
+ {
+ tag = strstr (name, RD_COPY_DUPLICATE_TAG);
+ }
+ if (tag == NULL)
+ {
+ tag = strstr (name, TH_COPY_DUPLICATE_TAG);
+ }
+
+ /* If we got one of st, nd, rd, th, fish out the duplicate number. */
+ if (tag != NULL)
+ {
+ /* localizers: opening parentheses to match the "th copy)" string */
+ tag = strstr (name, _(" ("));
+ if (tag != NULL)
+ {
+ if (tag > *suffix)
+ {
+ /* handle case "foo. (22nd copy)" */
+ *suffix = "";
+ }
+ *name_base = extract_string_until (name, tag);
+ /* localizers: opening parentheses of the "th copy)" string */
+ if (sscanf (tag, _(" (%'d"), count) == 1)
+ {
+ if (*count < 1 || *count > 1000000)
+ {
+ /* keep the count within a reasonable range */
+ *count = 0;
+ }
+ return;
+ }
+ *count = 0;
+ return;
+ }
+ }
+
+
+ *count = 0;
+ /* ignore_extension was not used before to let above code handle case "dir (copy).dir" for directories */
+ if (**suffix != '\0' && !ignore_extension)
+ {
+ *name_base = extract_string_until (name, *suffix);
+ }
+ else
+ {
+ /* making sure extension is ignored in directories */
+ *suffix = "";
+ *name_base = g_strdup (name);
+ }
+}
+
+static char *
+make_next_duplicate_name (const char *base,
+ const char *suffix,
+ int count,
+ int max_length)
+{
+ const char *format;
+ char *result;
+ int unshortened_length;
+ gboolean use_count;
+
+ if (count < 1)
+ {
+ g_warning ("bad count %d in get_duplicate_name", count);
+ count = 1;
+ }
+
+ if (count <= 2)
+ {
+ /* Handle special cases for low numbers.
+ * Perhaps for some locales we will need to add more.
+ */
+ switch (count)
+ {
+ default:
+ {
+ g_assert_not_reached ();
+ /* fall through */
+ }
+
+ case 1:
+ {
+ format = FIRST_COPY_DUPLICATE_FORMAT;
+ }
+ break;
+
+ case 2:
+ {
+ format = SECOND_COPY_DUPLICATE_FORMAT;
+ }
+ break;
+ }
+
+ use_count = FALSE;
+ }
+ else
+ {
+ /* Handle special cases for the first few numbers of each ten.
+ * For locales where getting this exactly right is difficult,
+ * these can just be made all the same as the general case below.
+ */
+
+ /* Handle special cases for x11th - x20th.
+ */
+ switch (count % 100)
+ {
+ case 11:
+ {
+ format = X11TH_COPY_DUPLICATE_FORMAT;
+ }
+ break;
+
+ case 12:
+ {
+ format = X12TH_COPY_DUPLICATE_FORMAT;
+ }
+ break;
+
+ case 13:
+ {
+ format = X13TH_COPY_DUPLICATE_FORMAT;
+ }
+ break;
+
+ default:
+ {
+ format = NULL;
+ }
+ break;
+ }
+
+ if (format == NULL)
+ {
+ switch (count % 10)
+ {
+ case 1:
+ {
+ format = ST_COPY_DUPLICATE_FORMAT;
+ }
+ break;
+
+ case 2:
+ {
+ format = ND_COPY_DUPLICATE_FORMAT;
+ }
+ break;
+
+ case 3:
+ {
+ format = RD_COPY_DUPLICATE_FORMAT;
+ }
+ break;
+
+ default:
+ {
+ /* The general case. */
+ format = TH_COPY_DUPLICATE_FORMAT;
+ }
+ break;
+ }
+ }
+
+ use_count = TRUE;
+ }
+
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wformat-nonliteral"
+ if (use_count)
+ {
+ result = g_strdup_printf (format, base, count, suffix);
+ }
+ else
+ {
+ result = g_strdup_printf (format, base, suffix);
+ }
+
+ if (max_length > 0 && (unshortened_length = strlen (result)) > max_length)
+ {
+ char *new_base;
+
+ new_base = shorten_utf8_string (base, unshortened_length - max_length);
+ if (new_base)
+ {
+ g_free (result);
+
+ if (use_count)
+ {
+ result = g_strdup_printf (format, new_base, count, suffix);
+ }
+ else
+ {
+ result = g_strdup_printf (format, new_base, suffix);
+ }
+
+ g_assert (strlen (result) <= max_length);
+ g_free (new_base);
+ }
+ }
+#pragma GCC diagnostic pop
+
+ return result;
+}
+
+static char *
+get_duplicate_name (const char *name,
+ int count_increment,
+ int max_length,
+ gboolean ignore_extension)
+{
+ char *result;
+ char *name_base;
+ const char *suffix;
+ int count;
+
+ parse_previous_duplicate_name (name, &name_base, &suffix, &count, ignore_extension);
+ result = make_next_duplicate_name (name_base, suffix, count + count_increment, max_length);
+
+ g_free (name_base);
+
+ return result;
+}
+
+static gboolean
+has_invalid_xml_char (char *str)
+{
+ gunichar c;
+
+ while (*str != 0)
+ {
+ c = g_utf8_get_char (str);
+ /* characters XML permits */
+ if (!(c == 0x9 ||
+ c == 0xA ||
+ c == 0xD ||
+ (c >= 0x20 && c <= 0xD7FF) ||
+ (c >= 0xE000 && c <= 0xFFFD) ||
+ (c >= 0x10000 && c <= 0x10FFFF)))
+ {
+ return TRUE;
+ }
+ str = g_utf8_next_char (str);
+ }
+ return FALSE;
+}
+
+static gchar *
+get_basename (GFile *file)
+{
+ GFileInfo *info;
+ gchar *name, *basename, *tmp;
+ GMount *mount;
+
+ if ((mount = nautilus_get_mounted_mount_for_root (file)) != NULL)
+ {
+ name = g_mount_get_name (mount);
+ g_object_unref (mount);
+ }
+ else
+ {
+ info = g_file_query_info (file,
+ G_FILE_ATTRIBUTE_STANDARD_DISPLAY_NAME,
+ 0,
+ g_cancellable_get_current (),
+ NULL);
+ name = NULL;
+ if (info)
+ {
+ name = g_strdup (g_file_info_get_display_name (info));
+ g_object_unref (info);
+ }
+ }
+
+ if (name == NULL)
+ {
+ basename = g_file_get_basename (file);
+ if (basename == NULL)
+ {
+ return g_strdup (_("unknown"));
+ }
+
+ if (g_utf8_validate (basename, -1, NULL))
+ {
+ name = basename;
+ }
+ else
+ {
+ name = g_uri_escape_string (basename, G_URI_RESERVED_CHARS_ALLOWED_IN_PATH, TRUE);
+ g_free (basename);
+ }
+ }
+
+ /* Some chars can't be put in the markup we use for the dialogs... */
+ if (has_invalid_xml_char (name))
+ {
+ tmp = name;
+ name = g_uri_escape_string (name, G_URI_RESERVED_CHARS_ALLOWED_IN_PATH, TRUE);
+ g_free (tmp);
+ }
+
+ /* Finally, if the string is too long, truncate it. */
+ if (name != NULL)
+ {
+ tmp = name;
+ name = eel_str_middle_truncate (tmp, MAXIMUM_DISPLAYED_FILE_NAME_LENGTH);
+ g_free (tmp);
+ }
+
+ return name;
+}
+
+static gchar *
+get_truncated_parse_name (GFile *file)
+{
+ g_autofree gchar *parse_name = NULL;
+
+ g_assert (G_IS_FILE (file));
+
+ parse_name = g_file_get_parse_name (file);
+
+ return eel_str_middle_truncate (parse_name, MAXIMUM_DISPLAYED_FILE_NAME_LENGTH);
+}
+
+#define op_job_new(__type, parent_window, dbus_data) ((__type *) (init_common (sizeof (__type), parent_window, dbus_data)))
+
+static gpointer
+init_common (gsize job_size,
+ GtkWindow *parent_window,
+ NautilusFileOperationsDBusData *dbus_data)
+{
+ CommonJob *common;
+
+ common = g_malloc0 (job_size);
+
+ if (parent_window)
+ {
+ common->parent_window = parent_window;
+ g_object_add_weak_pointer (G_OBJECT (common->parent_window),
+ (gpointer *) &common->parent_window);
+ }
+
+ if (dbus_data)
+ {
+ common->dbus_data = nautilus_file_operations_dbus_data_ref (dbus_data);
+ }
+
+ common->progress = nautilus_progress_info_new ();
+ common->cancellable = nautilus_progress_info_get_cancellable (common->progress);
+ common->time = g_timer_new ();
+ common->inhibit_cookie = 0;
+
+ return common;
+}
+
+static void
+finalize_common (CommonJob *common)
+{
+ nautilus_progress_info_finish (common->progress);
+
+ if (common->inhibit_cookie != 0)
+ {
+ gtk_application_uninhibit (GTK_APPLICATION (g_application_get_default ()),
+ common->inhibit_cookie);
+ }
+
+ common->inhibit_cookie = 0;
+ g_timer_destroy (common->time);
+
+ if (common->parent_window)
+ {
+ g_object_remove_weak_pointer (G_OBJECT (common->parent_window),
+ (gpointer *) &common->parent_window);
+ }
+
+ if (common->dbus_data)
+ {
+ nautilus_file_operations_dbus_data_unref (common->dbus_data);
+ }
+
+ if (common->skip_files)
+ {
+ g_hash_table_destroy (common->skip_files);
+ }
+ if (common->skip_readdir_error)
+ {
+ g_hash_table_destroy (common->skip_readdir_error);
+ }
+
+ if (common->undo_info != NULL)
+ {
+ nautilus_file_undo_manager_set_action (common->undo_info);
+ g_object_unref (common->undo_info);
+ }
+
+ g_object_unref (common->progress);
+ g_object_unref (common->cancellable);
+ g_free (common);
+}
+
+static void
+skip_file (CommonJob *common,
+ GFile *file)
+{
+ if (common->skip_files == NULL)
+ {
+ common->skip_files =
+ g_hash_table_new_full (g_file_hash, (GEqualFunc) g_file_equal, g_object_unref, NULL);
+ }
+
+ g_hash_table_insert (common->skip_files, g_object_ref (file), file);
+}
+
+static void
+skip_readdir_error (CommonJob *common,
+ GFile *dir)
+{
+ if (common->skip_readdir_error == NULL)
+ {
+ common->skip_readdir_error =
+ g_hash_table_new_full (g_file_hash, (GEqualFunc) g_file_equal, g_object_unref, NULL);
+ }
+
+ g_hash_table_insert (common->skip_readdir_error, g_object_ref (dir), dir);
+}
+
+static gboolean
+should_skip_file (CommonJob *common,
+ GFile *file)
+{
+ if (common->skip_files != NULL)
+ {
+ return g_hash_table_lookup (common->skip_files, file) != NULL;
+ }
+ return FALSE;
+}
+
+static gboolean
+should_skip_readdir_error (CommonJob *common,
+ GFile *dir)
+{
+ if (common->skip_readdir_error != NULL)
+ {
+ return g_hash_table_lookup (common->skip_readdir_error, dir) != NULL;
+ }
+ return FALSE;
+}
+
+static gboolean
+can_delete_without_confirm (GFile *file)
+{
+ /* In the case of testing, we want to be able to delete
+ * without asking for confirmation from the user.
+ */
+ if (g_file_has_uri_scheme (file, "burn") ||
+ g_file_has_uri_scheme (file, "recent") ||
+ !g_strcmp0 (g_getenv ("RUNNING_TESTS"), "TRUE"))
+ {
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static gboolean
+can_delete_files_without_confirm (GList *files)
+{
+ g_assert (files != NULL);
+
+ while (files != NULL)
+ {
+ if (!can_delete_without_confirm (files->data))
+ {
+ return FALSE;
+ }
+
+ files = files->next;
+ }
+
+ return TRUE;
+}
+
+typedef struct
+{
+ GtkWindow **parent_window;
+ NautilusFileOperationsDBusData *dbus_data;
+ gboolean ignore_close_box;
+ GtkMessageType message_type;
+ const char *primary_text;
+ const char *secondary_text;
+ const char *details_text;
+ const char **button_titles;
+ gboolean show_all;
+ gboolean should_start_inactive;
+ int result;
+ /* Dialogs are ran from operation threads, which need to be blocked until
+ * the user gives a valid response
+ */
+ gboolean completed;
+ GMutex mutex;
+ GCond cond;
+} RunSimpleDialogData;
+
+static void
+set_transient_for (GdkSurface *child_surface,
+ const char *parent_handle)
+{
+ GdkDisplay *display;
+ const char *prefix;
+
+ display = gdk_surface_get_display (child_surface);
+
+#ifdef GDK_WINDOWING_X11
+ if (GDK_IS_X11_DISPLAY (display))
+ {
+ prefix = "x11:";
+
+ if (g_str_has_prefix (parent_handle, prefix))
+ {
+ const char *handle;
+ GdkSurface *surface;
+
+ handle = parent_handle + strlen (prefix);
+ surface = gdk_x11_surface_lookup_for_display (display, strtol (handle, NULL, 16));
+
+ if (surface != NULL)
+ {
+ gdk_toplevel_set_transient_for (GDK_TOPLEVEL (child_surface), surface);
+ }
+ }
+ }
+#endif
+
+#ifdef GDK_WINDOWING_WAYLAND
+ if (GDK_IS_WAYLAND_DISPLAY (display))
+ {
+ prefix = "wayland:";
+
+ if (g_str_has_prefix (parent_handle, prefix))
+ {
+ const char *handle;
+
+ handle = parent_handle + strlen (prefix);
+
+ gdk_wayland_toplevel_set_transient_for_exported (GDK_TOPLEVEL (child_surface), (char *) handle);
+ }
+ }
+#endif
+}
+
+static void
+dialog_realize_cb (GtkWidget *widget,
+ gpointer user_data)
+{
+ NautilusFileOperationsDBusData *dbus_data = user_data;
+ const char *parent_handle;
+
+ parent_handle = nautilus_file_operations_dbus_data_get_parent_handle (dbus_data);
+ set_transient_for (gtk_native_get_surface (gtk_widget_get_native (widget)), parent_handle);
+}
+
+static gboolean
+is_long_job (CommonJob *job)
+{
+ double elapsed = nautilus_progress_info_get_total_elapsed_time (job->progress);
+ return elapsed > LONG_JOB_THRESHOLD_IN_SECONDS ? TRUE : FALSE;
+}
+
+static gboolean
+simple_dialog_button_activate (GtkWidget *button)
+{
+ gtk_widget_set_sensitive (button, TRUE);
+ return G_SOURCE_REMOVE;
+}
+
+static void
+simple_dialog_cb (GtkDialog *dialog,
+ gint response_id,
+ gpointer user_data)
+{
+ RunSimpleDialogData *data = user_data;
+
+ gtk_window_destroy (GTK_WINDOW (dialog));
+
+ data->result = response_id;
+ data->completed = TRUE;
+
+ g_cond_signal (&data->cond);
+ g_mutex_unlock (&data->mutex);
+}
+
+static gboolean
+do_run_simple_dialog (gpointer _data)
+{
+ RunSimpleDialogData *data = _data;
+ const char *button_title;
+ GtkWidget *dialog;
+ GtkWidget *button;
+ int response_id;
+
+ g_mutex_lock (&data->mutex);
+
+ /* Create the dialog. */
+ dialog = gtk_message_dialog_new (*data->parent_window,
+ GTK_DIALOG_MODAL,
+ data->message_type,
+ GTK_BUTTONS_NONE,
+ NULL);
+
+ g_object_set (dialog,
+ "text", data->primary_text,
+ "secondary-text", data->secondary_text,
+ NULL);
+
+ for (response_id = 0;
+ data->button_titles[response_id] != NULL;
+ response_id++)
+ {
+ button_title = data->button_titles[response_id];
+ if (!data->show_all && is_all_button_text (button_title))
+ {
+ continue;
+ }
+
+ button = gtk_dialog_add_button (GTK_DIALOG (dialog), button_title, response_id);
+ gtk_dialog_set_default_response (GTK_DIALOG (dialog), response_id);
+
+ if (g_strcmp0 (button_title, DELETE) == 0 ||
+ g_strcmp0 (button_title, EMPTY_TRASH) == 0 ||
+ g_strcmp0 (button_title, DELETE_ALL) == 0)
+ {
+ gtk_style_context_add_class (gtk_widget_get_style_context (button),
+ "destructive-action");
+ }
+
+ if (data->should_start_inactive)
+ {
+ gtk_widget_set_sensitive (button, FALSE);
+ g_timeout_add_seconds (BUTTON_ACTIVATION_DELAY_IN_SECONDS,
+ G_SOURCE_FUNC (simple_dialog_button_activate),
+ button);
+ }
+ }
+
+ if (data->details_text)
+ {
+ GtkWidget *content_area, *label;
+ content_area = gtk_message_dialog_get_message_area (GTK_MESSAGE_DIALOG (dialog));
+
+ label = gtk_label_new (data->details_text);
+ gtk_label_set_wrap (GTK_LABEL (label), TRUE);
+ gtk_label_set_selectable (GTK_LABEL (label), TRUE);
+ gtk_label_set_xalign (GTK_LABEL (label), 0);
+ /* Ideally, we shouldn’t do this.
+ *
+ * Refer to https://gitlab.gnome.org/GNOME/nautilus/merge_requests/94
+ * and https://gitlab.gnome.org/GNOME/nautilus/issues/270.
+ */
+ gtk_label_set_ellipsize (GTK_LABEL (label), PANGO_ELLIPSIZE_MIDDLE);
+ gtk_label_set_max_width_chars (GTK_LABEL (label),
+ MAXIMUM_DISPLAYED_ERROR_MESSAGE_LENGTH);
+
+ gtk_box_append (GTK_BOX (content_area), label);
+
+ gtk_widget_show (label);
+ }
+
+ if (data->dbus_data != NULL)
+ {
+ guint32 timestamp;
+
+ timestamp = nautilus_file_operations_dbus_data_get_timestamp (data->dbus_data);
+
+ if (nautilus_file_operations_dbus_data_get_parent_handle (data->dbus_data) != NULL)
+ {
+ g_signal_connect (dialog, "realize", G_CALLBACK (dialog_realize_cb), data->dbus_data);
+ }
+
+ if (timestamp != 0)
+ {
+ gtk_window_present_with_time (GTK_WINDOW (dialog), timestamp);
+ }
+ }
+
+ /* Run it. */
+ g_signal_connect (dialog, "response", G_CALLBACK (simple_dialog_cb), data);
+ gtk_widget_show (dialog);
+
+ return FALSE;
+}
+
+/* NOTE: This frees the primary / secondary strings, in order to
+ * avoid doing that everywhere. So, make sure they are strduped */
+
+static int
+run_simple_dialog_va (CommonJob *job,
+ gboolean ignore_close_box,
+ GtkMessageType message_type,
+ char *primary_text,
+ char *secondary_text,
+ const char *details_text,
+ gboolean show_all,
+ va_list varargs)
+{
+ RunSimpleDialogData *data;
+ int res;
+ const char *button_title;
+ GPtrArray *ptr_array;
+
+ g_timer_stop (job->time);
+
+ data = g_new0 (RunSimpleDialogData, 1);
+ data->parent_window = &job->parent_window;
+ data->dbus_data = job->dbus_data;
+ data->ignore_close_box = ignore_close_box;
+ data->message_type = message_type;
+ data->primary_text = primary_text;
+ data->secondary_text = secondary_text;
+ data->details_text = details_text;
+ data->show_all = show_all;
+ data->completed = FALSE;
+ g_mutex_init (&data->mutex);
+ g_cond_init (&data->cond);
+
+ ptr_array = g_ptr_array_new ();
+ while ((button_title = va_arg (varargs, const char *)) != NULL)
+ {
+ g_ptr_array_add (ptr_array, (char *) button_title);
+ }
+ g_ptr_array_add (ptr_array, NULL);
+ data->button_titles = (const char **) g_ptr_array_free (ptr_array, FALSE);
+
+ nautilus_progress_info_pause (job->progress);
+
+ data->should_start_inactive = is_long_job (job);
+
+ g_mutex_lock (&data->mutex);
+
+ g_main_context_invoke (NULL,
+ do_run_simple_dialog,
+ data);
+
+ while (!data->completed)
+ {
+ g_cond_wait (&data->cond, &data->mutex);
+ }
+
+ nautilus_progress_info_resume (job->progress);
+ res = data->result;
+
+ g_mutex_unlock (&data->mutex);
+ g_mutex_clear (&data->mutex);
+ g_cond_clear (&data->cond);
+
+ g_free (data->button_titles);
+ g_free (data);
+
+ g_timer_continue (job->time);
+
+ g_free (primary_text);
+ g_free (secondary_text);
+
+ return res;
+}
+
+#if 0 /* Not used at the moment */
+static int
+run_simple_dialog (CommonJob *job,
+ gboolean ignore_close_box,
+ GtkMessageType message_type,
+ char *primary_text,
+ char *secondary_text,
+ const char *details_text,
+ ...)
+{
+ va_list varargs;
+ int res;
+
+ va_start (varargs, details_text);
+ res = run_simple_dialog_va (job,
+ ignore_close_box,
+ message_type,
+ primary_text,
+ secondary_text,
+ details_text,
+ varargs);
+ va_end (varargs);
+ return res;
+}
+#endif
+
+static int
+run_error (CommonJob *job,
+ char *primary_text,
+ char *secondary_text,
+ const char *details_text,
+ gboolean show_all,
+ ...)
+{
+ va_list varargs;
+ int res;
+
+ va_start (varargs, show_all);
+ res = run_simple_dialog_va (job,
+ FALSE,
+ GTK_MESSAGE_ERROR,
+ primary_text,
+ secondary_text,
+ details_text,
+ show_all,
+ varargs);
+ va_end (varargs);
+ return res;
+}
+
+static int
+run_warning (CommonJob *job,
+ char *primary_text,
+ char *secondary_text,
+ const char *details_text,
+ gboolean show_all,
+ ...)
+{
+ va_list varargs;
+ int res;
+
+ va_start (varargs, show_all);
+ res = run_simple_dialog_va (job,
+ FALSE,
+ GTK_MESSAGE_WARNING,
+ primary_text,
+ secondary_text,
+ details_text,
+ show_all,
+ varargs);
+ va_end (varargs);
+ return res;
+}
+
+static int
+run_question (CommonJob *job,
+ char *primary_text,
+ char *secondary_text,
+ const char *details_text,
+ gboolean show_all,
+ ...)
+{
+ va_list varargs;
+ int res;
+
+ va_start (varargs, show_all);
+ res = run_simple_dialog_va (job,
+ FALSE,
+ GTK_MESSAGE_QUESTION,
+ primary_text,
+ secondary_text,
+ details_text,
+ show_all,
+ varargs);
+ va_end (varargs);
+ return res;
+}
+
+static int
+run_cancel_or_skip_warning (CommonJob *job,
+ char *primary_text,
+ char *secondary_text,
+ const char *details_text,
+ int total_operations,
+ int operations_remaining)
+{
+ int response;
+
+ if (total_operations == 1)
+ {
+ response = run_warning (job,
+ primary_text,
+ secondary_text,
+ details_text,
+ FALSE,
+ CANCEL,
+ NULL);
+ }
+ else
+ {
+ response = run_warning (job,
+ primary_text,
+ secondary_text,
+ details_text,
+ operations_remaining > 1,
+ CANCEL, SKIP_ALL, SKIP,
+ NULL);
+ }
+
+ return response;
+}
+
+static void
+inhibit_power_manager (CommonJob *job,
+ const char *message)
+{
+ job->inhibit_cookie = gtk_application_inhibit (GTK_APPLICATION (g_application_get_default ()),
+ GTK_WINDOW (job->parent_window),
+ GTK_APPLICATION_INHIBIT_LOGOUT |
+ GTK_APPLICATION_INHIBIT_SUSPEND,
+ message);
+}
+
+static void
+abort_job (CommonJob *job)
+{
+ /* destroy the undo action data too */
+ g_clear_object (&job->undo_info);
+
+ g_cancellable_cancel (job->cancellable);
+}
+
+static gboolean
+job_aborted (CommonJob *job)
+{
+ return g_cancellable_is_cancelled (job->cancellable);
+}
+
+static gboolean
+confirm_delete_from_trash (CommonJob *job,
+ GList *files)
+{
+ char *prompt;
+ int file_count;
+ int response;
+
+ file_count = g_list_length (files);
+ g_assert (file_count > 0);
+
+ if (file_count == 1)
+ {
+ g_autofree gchar *basename = NULL;
+
+ basename = get_basename (files->data);
+ prompt = g_strdup_printf (_("Are you sure you want to permanently delete “%s” "
+ "from the trash?"), basename);
+ }
+ else
+ {
+ prompt = g_strdup_printf (ngettext ("Are you sure you want to permanently delete "
+ "the %'d selected item from the trash?",
+ "Are you sure you want to permanently delete "
+ "the %'d selected items from the trash?",
+ file_count),
+ file_count);
+ }
+
+ response = run_warning (job,
+ prompt,
+ g_strdup (_("If you delete an item, it will be permanently lost.")),
+ NULL,
+ FALSE,
+ CANCEL, DELETE,
+ NULL);
+
+ return (response == 1);
+}
+
+static gboolean
+confirm_empty_trash (CommonJob *job)
+{
+ char *prompt;
+ int response;
+
+ prompt = g_strdup (_("Empty all items from Trash?"));
+
+ response = run_warning (job,
+ prompt,
+ g_strdup (_("All items in the Trash will be permanently deleted.")),
+ NULL,
+ FALSE,
+ CANCEL, EMPTY_TRASH,
+ NULL);
+
+ return (response == 1);
+}
+
+static gboolean
+confirm_delete_directly (CommonJob *job,
+ GList *files)
+{
+ char *prompt;
+ int file_count;
+ int response;
+
+ file_count = g_list_length (files);
+ g_assert (file_count > 0);
+
+ if (can_delete_files_without_confirm (files))
+ {
+ return TRUE;
+ }
+
+ if (file_count == 1)
+ {
+ g_autofree gchar *basename = NULL;
+
+ basename = get_basename (files->data);
+ prompt = g_strdup_printf (_("Are you sure you want to permanently delete “%s”?"),
+ basename);
+ }
+ else
+ {
+ prompt = g_strdup_printf (ngettext ("Are you sure you want to permanently delete "
+ "the %'d selected item?",
+ "Are you sure you want to permanently delete "
+ "the %'d selected items?", file_count),
+ file_count);
+ }
+
+ response = run_warning (job,
+ prompt,
+ g_strdup (_("If you delete an item, it will be permanently lost.")),
+ NULL,
+ FALSE,
+ CANCEL, DELETE,
+ NULL);
+
+ return response == 1;
+}
+
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wformat-nonliteral"
+static void
+report_delete_progress (CommonJob *job,
+ SourceInfo *source_info,
+ TransferInfo *transfer_info)
+{
+ int files_left;
+ double elapsed, transfer_rate;
+ int remaining_time;
+ gint64 now;
+ char *details;
+ char *status;
+ DeleteJob *delete_job;
+
+ delete_job = (DeleteJob *) job;
+ now = g_get_monotonic_time ();
+ files_left = source_info->num_files - transfer_info->num_files;
+
+ /* Races and whatnot could cause this to be negative... */
+ if (files_left < 0)
+ {
+ files_left = 0;
+ }
+
+ /* If the number of files left is 0, we want to update the status without
+ * considering this time, since we want to change the status to completed
+ * and probably we won't get more calls to this function */
+ if (transfer_info->last_report_time != 0 &&
+ ABS ((gint64) (transfer_info->last_report_time - now)) < 100 * NSEC_PER_MICROSEC &&
+ files_left > 0)
+ {
+ return;
+ }
+
+ transfer_info->last_report_time = now;
+
+ if (source_info->num_files == 1)
+ {
+ g_autofree gchar *basename = NULL;
+
+ if (files_left == 0)
+ {
+ status = _("Deleted “%s”");
+ }
+ else
+ {
+ status = _("Deleting “%s”");
+ }
+
+ basename = get_basename (G_FILE (delete_job->files->data));
+ nautilus_progress_info_take_status (job->progress,
+ g_strdup_printf (status, basename));
+ }
+ else
+ {
+ if (files_left == 0)
+ {
+ status = ngettext ("Deleted %'d file",
+ "Deleted %'d files",
+ source_info->num_files);
+ }
+ else
+ {
+ status = ngettext ("Deleting %'d file",
+ "Deleting %'d files",
+ source_info->num_files);
+ }
+ nautilus_progress_info_take_status (job->progress,
+ g_strdup_printf (status,
+ source_info->num_files));
+ }
+
+ elapsed = g_timer_elapsed (job->time, NULL);
+ transfer_rate = 0;
+ remaining_time = INT_MAX;
+ if (elapsed > 0)
+ {
+ transfer_rate = transfer_info->num_files / elapsed;
+ if (transfer_rate > 0)
+ {
+ remaining_time = (source_info->num_files - transfer_info->num_files) / transfer_rate;
+ }
+ }
+
+ if (elapsed < SECONDS_NEEDED_FOR_RELIABLE_TRANSFER_RATE ||
+ transfer_rate == 0)
+ {
+ if (files_left > 0)
+ {
+ /* To translators: %'d is the number of files completed for the operation,
+ * so it will be something like 2/14. */
+ details = g_strdup_printf (_("%'d / %'d"),
+ transfer_info->num_files + 1,
+ source_info->num_files);
+ }
+ else
+ {
+ /* To translators: %'d is the number of files completed for the operation,
+ * so it will be something like 2/14. */
+ details = g_strdup_printf (_("%'d / %'d"),
+ transfer_info->num_files,
+ source_info->num_files);
+ }
+ }
+ else
+ {
+ if (files_left > 0)
+ {
+ gchar *time_left_message;
+ gchar *files_per_second_message;
+ gchar *concat_detail;
+ g_autofree gchar *formatted_time = NULL;
+
+ /* To translators: %s will expand to a time duration like "2 minutes".
+ * So the whole thing will be something like "1 / 5 -- 2 hours left (4 files/sec)"
+ *
+ * The singular/plural form will be used depending on the remaining time (i.e. the %s argument).
+ */
+ time_left_message = ngettext ("%'d / %'d \xE2\x80\x94 %s left",
+ "%'d / %'d \xE2\x80\x94 %s left",
+ seconds_count_format_time_units (remaining_time));
+ transfer_rate += 0.5;
+ files_per_second_message = ngettext ("(%d file/sec)",
+ "(%d files/sec)",
+ (int) transfer_rate);
+ concat_detail = g_strconcat (time_left_message, " ", files_per_second_message, NULL);
+
+ formatted_time = get_formatted_time (remaining_time);
+ details = g_strdup_printf (concat_detail,
+ transfer_info->num_files + 1, source_info->num_files,
+ formatted_time,
+ (int) transfer_rate);
+
+ g_free (concat_detail);
+ }
+ else
+ {
+ /* To translators: %'d is the number of files completed for the operation,
+ * so it will be something like 2/14. */
+ details = g_strdup_printf (_("%'d / %'d"),
+ transfer_info->num_files,
+ source_info->num_files);
+ }
+ }
+ nautilus_progress_info_take_details (job->progress, details);
+
+ if (elapsed > SECONDS_NEEDED_FOR_APROXIMATE_TRANSFER_RATE)
+ {
+ nautilus_progress_info_set_remaining_time (job->progress,
+ remaining_time);
+ nautilus_progress_info_set_elapsed_time (job->progress,
+ elapsed);
+ }
+
+ if (source_info->num_files != 0)
+ {
+ nautilus_progress_info_set_progress (job->progress, transfer_info->num_files, source_info->num_files);
+ }
+}
+#pragma GCC diagnostic pop
+
+typedef void (*DeleteCallback) (GFile *file,
+ GError *error,
+ gpointer callback_data);
+
+static gboolean
+delete_file_recursively (GFile *file,
+ GCancellable *cancellable,
+ DeleteCallback callback,
+ gpointer callback_data)
+{
+ gboolean success;
+ g_autoptr (GError) error = NULL;
+
+ do
+ {
+ g_autoptr (GFileEnumerator) enumerator = NULL;
+
+ success = g_file_delete (file, cancellable, &error);
+ if (success ||
+ !g_error_matches (error, G_IO_ERROR, G_IO_ERROR_NOT_EMPTY))
+ {
+ break;
+ }
+
+ g_clear_error (&error);
+
+ enumerator = g_file_enumerate_children (file,
+ G_FILE_ATTRIBUTE_STANDARD_NAME,
+ G_FILE_QUERY_INFO_NONE,
+ cancellable, &error);
+
+ if (enumerator)
+ {
+ GFileInfo *info;
+
+ success = TRUE;
+
+ info = g_file_enumerator_next_file (enumerator,
+ cancellable,
+ &error);
+
+ while (info != NULL)
+ {
+ g_autoptr (GFile) child = NULL;
+
+ child = g_file_enumerator_get_child (enumerator, info);
+
+ success = success && delete_file_recursively (child,
+ cancellable,
+ callback,
+ callback_data);
+
+ g_object_unref (info);
+
+ info = g_file_enumerator_next_file (enumerator,
+ cancellable,
+ &error);
+ }
+ }
+
+ if (error != NULL)
+ {
+ success = FALSE;
+ }
+ }
+ while (success);
+
+ if (callback)
+ {
+ callback (file, error, callback_data);
+ }
+
+ return success;
+}
+
+typedef struct
+{
+ CommonJob *job;
+ SourceInfo *source_info;
+ TransferInfo *transfer_info;
+} DeleteData;
+
+static void
+file_deleted_callback (GFile *file,
+ GError *error,
+ gpointer callback_data)
+{
+ DeleteData *data = callback_data;
+ CommonJob *job;
+ SourceInfo *source_info;
+ TransferInfo *transfer_info;
+ GFileType file_type;
+ char *primary;
+ char *secondary;
+ char *details = NULL;
+ int response;
+ g_autofree gchar *basename = NULL;
+
+ job = data->job;
+ source_info = data->source_info;
+ transfer_info = data->transfer_info;
+
+ data->transfer_info->num_files++;
+
+ if (error == NULL)
+ {
+ nautilus_file_changes_queue_file_removed (file);
+ report_delete_progress (data->job, data->source_info, data->transfer_info);
+
+ return;
+ }
+
+ if (job_aborted (job) ||
+ job->skip_all_error ||
+ should_skip_file (job, file) ||
+ should_skip_readdir_error (job, file))
+ {
+ return;
+ }
+
+ primary = g_strdup (_("Error while deleting."));
+
+ file_type = g_file_query_file_type (file,
+ G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
+ job->cancellable);
+
+ basename = get_basename (file);
+
+ if (file_type == G_FILE_TYPE_DIRECTORY)
+ {
+ secondary = IS_IO_ERROR (error, PERMISSION_DENIED) ?
+ g_strdup_printf (_("There was an error deleting the "
+ "folder “%s”."),
+ basename) :
+ g_strdup_printf (_("You do not have sufficient permissions "
+ "to delete the folder “%s”."),
+ basename);
+ }
+ else
+ {
+ secondary = IS_IO_ERROR (error, PERMISSION_DENIED) ?
+ g_strdup_printf (_("There was an error deleting the "
+ "file “%s”."),
+ basename) :
+ g_strdup_printf (_("You do not have sufficient permissions "
+ "to delete the file “%s”."),
+ basename);
+ }
+
+ details = error->message;
+
+ response = run_cancel_or_skip_warning (job,
+ primary,
+ secondary,
+ details,
+ source_info->num_files,
+ source_info->num_files - transfer_info->num_files);
+
+ if (response == 0 || response == GTK_RESPONSE_DELETE_EVENT)
+ {
+ abort_job (job);
+ }
+ else if (response == 1)
+ {
+ /* skip all */
+ job->skip_all_error = TRUE;
+ }
+}
+
+static void
+delete_files (CommonJob *job,
+ GList *files,
+ int *files_skipped)
+{
+ GList *l;
+ GFile *file;
+ g_auto (SourceInfo) source_info = SOURCE_INFO_INIT;
+ TransferInfo transfer_info;
+ DeleteData data;
+
+ if (job_aborted (job))
+ {
+ return;
+ }
+
+ scan_sources (files,
+ &source_info,
+ job,
+ OP_KIND_DELETE);
+ if (job_aborted (job))
+ {
+ return;
+ }
+
+ g_timer_start (job->time);
+
+ memset (&transfer_info, 0, sizeof (transfer_info));
+ report_delete_progress (job, &source_info, &transfer_info);
+
+ data.job = job;
+ data.source_info = &source_info;
+ data.transfer_info = &transfer_info;
+
+ for (l = files;
+ l != NULL && !job_aborted (job);
+ l = l->next)
+ {
+ gboolean success;
+
+ file = l->data;
+
+ if (should_skip_file (job, file))
+ {
+ (*files_skipped)++;
+ continue;
+ }
+
+ success = delete_file_recursively (file, job->cancellable,
+ file_deleted_callback,
+ &data);
+
+ if (!success)
+ {
+ (*files_skipped)++;
+ }
+ }
+}
+
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wformat-nonliteral"
+static void
+report_trash_progress (CommonJob *job,
+ SourceInfo *source_info,
+ TransferInfo *transfer_info)
+{
+ int files_left;
+ double elapsed, transfer_rate;
+ int remaining_time;
+ gint64 now;
+ char *details;
+ char *status;
+ DeleteJob *delete_job;
+
+ delete_job = (DeleteJob *) job;
+ now = g_get_monotonic_time ();
+ files_left = source_info->num_files - transfer_info->num_files;
+
+ /* Races and whatnot could cause this to be negative... */
+ if (files_left < 0)
+ {
+ files_left = 0;
+ }
+
+ /* If the number of files left is 0, we want to update the status without
+ * considering this time, since we want to change the status to completed
+ * and probably we won't get more calls to this function */
+ if (transfer_info->last_report_time != 0 &&
+ ABS ((gint64) (transfer_info->last_report_time - now)) < 100 * NSEC_PER_MICROSEC &&
+ files_left > 0)
+ {
+ return;
+ }
+
+ transfer_info->last_report_time = now;
+
+ if (source_info->num_files == 1)
+ {
+ g_autofree gchar *basename = NULL;
+
+ if (files_left > 0)
+ {
+ status = _("Trashing “%s”");
+ }
+ else
+ {
+ status = _("Trashed “%s”");
+ }
+
+ basename = get_basename (G_FILE (delete_job->files->data));
+ nautilus_progress_info_take_status (job->progress,
+ g_strdup_printf (status, basename));
+ }
+ else
+ {
+ if (files_left > 0)
+ {
+ status = ngettext ("Trashing %'d file",
+ "Trashing %'d files",
+ source_info->num_files);
+ }
+ else
+ {
+ status = ngettext ("Trashed %'d file",
+ "Trashed %'d files",
+ source_info->num_files);
+ }
+ nautilus_progress_info_take_status (job->progress,
+ g_strdup_printf (status,
+ source_info->num_files));
+ }
+
+
+ elapsed = g_timer_elapsed (job->time, NULL);
+ transfer_rate = 0;
+ remaining_time = INT_MAX;
+ if (elapsed > 0)
+ {
+ transfer_rate = transfer_info->num_files / elapsed;
+ if (transfer_rate > 0)
+ {
+ remaining_time = (source_info->num_files - transfer_info->num_files) / transfer_rate;
+ }
+ }
+
+ if (elapsed < SECONDS_NEEDED_FOR_RELIABLE_TRANSFER_RATE ||
+ transfer_rate == 0)
+ {
+ if (files_left > 0)
+ {
+ /* To translators: %'d is the number of files completed for the operation,
+ * so it will be something like 2/14. */
+ details = g_strdup_printf (_("%'d / %'d"),
+ transfer_info->num_files + 1,
+ source_info->num_files);
+ }
+ else
+ {
+ /* To translators: %'d is the number of files completed for the operation,
+ * so it will be something like 2/14. */
+ details = g_strdup_printf (_("%'d / %'d"),
+ transfer_info->num_files,
+ source_info->num_files);
+ }
+ }
+ else
+ {
+ if (files_left > 0)
+ {
+ gchar *time_left_message;
+ gchar *files_per_second_message;
+ gchar *concat_detail;
+ g_autofree gchar *formatted_time = NULL;
+
+ /* To translators: %s will expand to a time duration like "2 minutes".
+ * So the whole thing will be something like "1 / 5 -- 2 hours left (4 files/sec)"
+ *
+ * The singular/plural form will be used depending on the remaining time (i.e. the %s argument).
+ */
+ time_left_message = ngettext ("%'d / %'d \xE2\x80\x94 %s left",
+ "%'d / %'d \xE2\x80\x94 %s left",
+ seconds_count_format_time_units (remaining_time));
+ files_per_second_message = ngettext ("(%d file/sec)",
+ "(%d files/sec)",
+ (int) (transfer_rate + 0.5));
+ concat_detail = g_strconcat (time_left_message, " ", files_per_second_message, NULL);
+
+ formatted_time = get_formatted_time (remaining_time);
+ details = g_strdup_printf (concat_detail,
+ transfer_info->num_files + 1,
+ source_info->num_files,
+ formatted_time,
+ (int) transfer_rate + 0.5);
+
+ g_free (concat_detail);
+ }
+ else
+ {
+ /* To translators: %'d is the number of files completed for the operation,
+ * so it will be something like 2/14. */
+ details = g_strdup_printf (_("%'d / %'d"),
+ transfer_info->num_files,
+ source_info->num_files);
+ }
+ }
+ nautilus_progress_info_set_details (job->progress, details);
+
+ if (elapsed > SECONDS_NEEDED_FOR_APROXIMATE_TRANSFER_RATE)
+ {
+ nautilus_progress_info_set_remaining_time (job->progress,
+ remaining_time);
+ nautilus_progress_info_set_elapsed_time (job->progress,
+ elapsed);
+ }
+
+ if (source_info->num_files != 0)
+ {
+ nautilus_progress_info_set_progress (job->progress, transfer_info->num_files, source_info->num_files);
+ }
+}
+#pragma GCC diagnostic pop
+
+static void
+trash_file (CommonJob *job,
+ GFile *file,
+ gboolean *skipped_file,
+ SourceInfo *source_info,
+ TransferInfo *transfer_info,
+ gboolean toplevel,
+ GList **to_delete)
+{
+ GError *error;
+ char *primary, *secondary, *details;
+ int response;
+ g_autofree gchar *basename = NULL;
+
+ if (should_skip_file (job, file))
+ {
+ *skipped_file = TRUE;
+ return;
+ }
+
+ error = NULL;
+
+ if (g_file_trash (file, job->cancellable, &error))
+ {
+ transfer_info->num_files++;
+ nautilus_file_changes_queue_file_removed (file);
+
+ if (job->undo_info != NULL)
+ {
+ nautilus_file_undo_info_trash_add_file (NAUTILUS_FILE_UNDO_INFO_TRASH (job->undo_info), file);
+ }
+
+ report_trash_progress (job, source_info, transfer_info);
+ return;
+ }
+
+ if (job->skip_all_error)
+ {
+ *skipped_file = TRUE;
+ goto skip;
+ }
+
+ if (job->delete_all)
+ {
+ *to_delete = g_list_prepend (*to_delete, file);
+ goto skip;
+ }
+
+ basename = get_basename (file);
+ /* Translators: %s is a file name */
+ primary = g_strdup_printf (_("“%s” can’t be put in the trash. Do you want "
+ "to delete it immediately?"),
+ basename);
+
+ details = NULL;
+ secondary = NULL;
+ if (!IS_IO_ERROR (error, NOT_SUPPORTED))
+ {
+ details = error->message;
+ }
+ else if (!g_file_is_native (file))
+ {
+ secondary = g_strdup (_("This remote location does not support sending items to the trash."));
+ }
+
+ response = run_question (job,
+ primary,
+ secondary,
+ details,
+ (source_info->num_files - transfer_info->num_files) > 1,
+ CANCEL, SKIP_ALL, SKIP, DELETE_ALL, DELETE,
+ NULL);
+
+ if (response == 0 || response == GTK_RESPONSE_DELETE_EVENT)
+ {
+ ((DeleteJob *) job)->user_cancel = TRUE;
+ abort_job (job);
+ }
+ else if (response == 1) /* skip all */
+ {
+ *skipped_file = TRUE;
+ job->skip_all_error = TRUE;
+ }
+ else if (response == 2) /* skip */
+ {
+ *skipped_file = TRUE;
+ job->skip_all_error = TRUE;
+ }
+ else if (response == 3) /* delete all */
+ {
+ *to_delete = g_list_prepend (*to_delete, file);
+ job->delete_all = TRUE;
+ }
+ else if (response == 4) /* delete */
+ {
+ *to_delete = g_list_prepend (*to_delete, file);
+ }
+
+skip:
+ g_error_free (error);
+}
+
+static void
+source_info_remove_descendent_files_from_count (GFile *dir,
+ SourceDirInfo *dir_info,
+ SourceInfo *source_info)
+{
+ GFile *other_dir;
+ SourceDirInfo *other_dir_info;
+ GHashTableIter dir_info_iter;
+
+ source_info->num_files -= dir_info->num_files_children;
+ source_info->num_bytes -= dir_info->num_bytes_children;
+
+ g_hash_table_iter_init (&dir_info_iter, source_info->scanned_dirs_info);
+ while (g_hash_table_iter_next (&dir_info_iter, (gpointer *) &other_dir, (gpointer *) &other_dir_info))
+ {
+ g_assert (other_dir != NULL);
+ g_assert (other_dir_info != NULL);
+
+ if (other_dir_info != dir_info &&
+ g_file_has_parent (other_dir, dir))
+ {
+ source_info_remove_descendent_files_from_count (other_dir,
+ other_dir_info,
+ source_info);
+ }
+ }
+}
+
+static void
+source_info_remove_file_from_count (GFile *file,
+ CommonJob *job,
+ SourceInfo *source_info)
+{
+ g_autoptr (GFileInfo) file_info = NULL;
+ SourceDirInfo *dir_info;
+
+ if (g_cancellable_is_cancelled (job->cancellable))
+ {
+ return;
+ }
+
+ file_info = g_file_query_info (file,
+ G_FILE_ATTRIBUTE_STANDARD_SIZE,
+ G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
+ job->cancellable,
+ NULL);
+
+ source_info->num_files--;
+ if (file_info != NULL)
+ {
+ source_info->num_bytes -= g_file_info_get_size (file_info);
+ }
+
+ dir_info = g_hash_table_lookup (source_info->scanned_dirs_info, file);
+
+ if (dir_info != NULL)
+ {
+ source_info_remove_descendent_files_from_count (file,
+ dir_info,
+ source_info);
+ }
+}
+
+static void
+trash_files (CommonJob *job,
+ GList *files,
+ int *files_skipped)
+{
+ GList *l;
+ GFile *file;
+ GList *to_delete;
+ g_auto (SourceInfo) source_info = SOURCE_INFO_INIT;
+ TransferInfo transfer_info;
+ gboolean skipped_file;
+
+ if (job_aborted (job))
+ {
+ return;
+ }
+
+ scan_sources (files,
+ &source_info,
+ job,
+ OP_KIND_TRASH);
+ if (job_aborted (job))
+ {
+ return;
+ }
+
+ g_timer_start (job->time);
+
+ memset (&transfer_info, 0, sizeof (transfer_info));
+ report_trash_progress (job, &source_info, &transfer_info);
+
+ to_delete = NULL;
+ for (l = files;
+ l != NULL && !job_aborted (job);
+ l = l->next)
+ {
+ file = l->data;
+
+ skipped_file = FALSE;
+ trash_file (job, file,
+ &skipped_file,
+ &source_info, &transfer_info,
+ TRUE, &to_delete);
+ if (skipped_file)
+ {
+ (*files_skipped)++;
+ source_info_remove_file_from_count (file, job, &source_info);
+ report_trash_progress (job, &source_info, &transfer_info);
+ }
+ }
+
+ if (to_delete)
+ {
+ to_delete = g_list_reverse (to_delete);
+ delete_files (job, to_delete, files_skipped);
+ g_list_free (to_delete);
+ }
+}
+
+static void
+delete_task_done (GObject *source_object,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ DeleteJob *job;
+ GHashTable *debuting_uris;
+
+ job = user_data;
+
+ g_list_free_full (job->files, g_object_unref);
+
+ if (job->done_callback)
+ {
+ debuting_uris = g_hash_table_new_full (g_file_hash, (GEqualFunc) g_file_equal, g_object_unref, NULL);
+ job->done_callback (debuting_uris, job->user_cancel, job->done_callback_data);
+ g_hash_table_unref (debuting_uris);
+ }
+
+ finalize_common ((CommonJob *) job);
+
+ nautilus_file_changes_consume_changes (TRUE);
+}
+
+static void
+trash_or_delete_internal (GTask *task,
+ gpointer source_object,
+ gpointer task_data,
+ GCancellable *cancellable)
+{
+ DeleteJob *job = task_data;
+ g_autoptr (GList) to_trash_files = NULL;
+ g_autoptr (GList) to_delete_files = NULL;
+ GList *l;
+ GFile *file;
+ gboolean confirmed;
+ CommonJob *common;
+ gboolean must_confirm_delete_in_trash;
+ gboolean must_confirm_delete;
+ int files_skipped;
+
+ common = (CommonJob *) job;
+
+ nautilus_progress_info_start (job->common.progress);
+
+ must_confirm_delete_in_trash = FALSE;
+ must_confirm_delete = FALSE;
+ files_skipped = 0;
+
+ for (l = job->files; l != NULL; l = l->next)
+ {
+ file = l->data;
+
+ if (job->try_trash &&
+ g_file_has_uri_scheme (file, "trash"))
+ {
+ must_confirm_delete_in_trash = TRUE;
+ to_delete_files = g_list_prepend (to_delete_files, file);
+ }
+ else if (can_delete_without_confirm (file))
+ {
+ to_delete_files = g_list_prepend (to_delete_files, file);
+ }
+ else
+ {
+ if (job->try_trash)
+ {
+ to_trash_files = g_list_prepend (to_trash_files, file);
+ }
+ else
+ {
+ must_confirm_delete = TRUE;
+ to_delete_files = g_list_prepend (to_delete_files, file);
+ }
+ }
+ }
+
+ if (to_delete_files != NULL)
+ {
+ to_delete_files = g_list_reverse (to_delete_files);
+ confirmed = TRUE;
+ if (must_confirm_delete_in_trash)
+ {
+ confirmed = confirm_delete_from_trash (common, to_delete_files);
+ }
+ else if (must_confirm_delete)
+ {
+ confirmed = confirm_delete_directly (common, to_delete_files);
+ }
+ if (confirmed)
+ {
+ delete_files (common, to_delete_files, &files_skipped);
+ }
+ else
+ {
+ job->user_cancel = TRUE;
+ }
+ }
+
+ if (to_trash_files != NULL)
+ {
+ to_trash_files = g_list_reverse (to_trash_files);
+
+ trash_files (common, to_trash_files, &files_skipped);
+ }
+
+ if (files_skipped == g_list_length (job->files))
+ {
+ /* User has skipped all files, report user cancel */
+ job->user_cancel = TRUE;
+ }
+}
+
+static DeleteJob *
+setup_delete_job (GList *files,
+ GtkWindow *parent_window,
+ NautilusFileOperationsDBusData *dbus_data,
+ gboolean try_trash,
+ NautilusDeleteCallback done_callback,
+ gpointer done_callback_data)
+{
+ DeleteJob *job;
+
+ /* TODO: special case desktop icon link files ... */
+ job = op_job_new (DeleteJob, parent_window, dbus_data);
+ job->files = g_list_copy_deep (files, (GCopyFunc) g_object_ref, NULL);
+ job->try_trash = try_trash;
+ job->user_cancel = FALSE;
+ job->done_callback = done_callback;
+ job->done_callback_data = done_callback_data;
+
+ if (g_strcmp0 (g_getenv ("RUNNING_TESTS"), "TRUE"))
+ {
+ if (try_trash)
+ {
+ inhibit_power_manager ((CommonJob *) job, _("Trashing Files"));
+ }
+ else
+ {
+ inhibit_power_manager ((CommonJob *) job, _("Deleting Files"));
+ }
+ }
+
+ if (!nautilus_file_undo_manager_is_operating () && try_trash)
+ {
+ job->common.undo_info = nautilus_file_undo_info_trash_new (g_list_length (files));
+ }
+
+ return job;
+}
+
+static void
+trash_or_delete_internal_sync (GList *files,
+ GtkWindow *parent_window,
+ NautilusFileOperationsDBusData *dbus_data,
+ gboolean try_trash)
+{
+ GTask *task;
+ DeleteJob *job;
+
+ job = setup_delete_job (files,
+ parent_window,
+ dbus_data,
+ try_trash,
+ NULL,
+ NULL);
+
+ task = g_task_new (NULL, NULL, NULL, job);
+ g_task_set_task_data (task, job, NULL);
+ g_task_run_in_thread_sync (task, trash_or_delete_internal);
+ g_object_unref (task);
+ /* Since g_task_run_in_thread_sync doesn't work with callbacks (in this case not reaching
+ * delete_task_done) we need to set up the undo information ourselves.
+ */
+ delete_task_done (NULL, NULL, job);
+}
+
+static void
+trash_or_delete_internal_async (GList *files,
+ GtkWindow *parent_window,
+ NautilusFileOperationsDBusData *dbus_data,
+ gboolean try_trash,
+ NautilusDeleteCallback done_callback,
+ gpointer done_callback_data)
+{
+ GTask *task;
+ DeleteJob *job;
+
+ job = setup_delete_job (files,
+ parent_window,
+ dbus_data,
+ try_trash,
+ done_callback,
+ done_callback_data);
+
+ task = g_task_new (NULL, NULL, delete_task_done, job);
+ g_task_set_task_data (task, job, NULL);
+ g_task_run_in_thread (task, trash_or_delete_internal);
+ g_object_unref (task);
+}
+
+void
+nautilus_file_operations_trash_or_delete_sync (GList *files)
+{
+ trash_or_delete_internal_sync (files, NULL, NULL, TRUE);
+}
+
+void
+nautilus_file_operations_delete_sync (GList *files)
+{
+ trash_or_delete_internal_sync (files, NULL, NULL, FALSE);
+}
+
+void
+nautilus_file_operations_trash_or_delete_async (GList *files,
+ GtkWindow *parent_window,
+ NautilusFileOperationsDBusData *dbus_data,
+ NautilusDeleteCallback done_callback,
+ gpointer done_callback_data)
+{
+ trash_or_delete_internal_async (files, parent_window,
+ dbus_data,
+ TRUE,
+ done_callback, done_callback_data);
+}
+
+void
+nautilus_file_operations_delete_async (GList *files,
+ GtkWindow *parent_window,
+ NautilusFileOperationsDBusData *dbus_data,
+ NautilusDeleteCallback done_callback,
+ gpointer done_callback_data)
+{
+ trash_or_delete_internal_async (files, parent_window,
+ dbus_data,
+ FALSE,
+ done_callback, done_callback_data);
+}
+
+
+
+typedef struct
+{
+ gboolean eject;
+ GMount *mount;
+ GMountOperation *mount_operation;
+ GtkWindow *parent_window;
+ NautilusUnmountCallback callback;
+ gpointer callback_data;
+} UnmountData;
+
+static void
+unmount_data_free (UnmountData *data)
+{
+ if (data->parent_window)
+ {
+ g_object_remove_weak_pointer (G_OBJECT (data->parent_window),
+ (gpointer *) &data->parent_window);
+ }
+
+ g_clear_object (&data->mount_operation);
+ g_object_unref (data->mount);
+ g_free (data);
+}
+
+static void
+unmount_mount_callback (GObject *source_object,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ UnmountData *data = user_data;
+ GError *error;
+ char *primary;
+ gboolean unmounted;
+
+ error = NULL;
+ if (data->eject)
+ {
+ unmounted = g_mount_eject_with_operation_finish (G_MOUNT (source_object),
+ res, &error);
+ }
+ else
+ {
+ unmounted = g_mount_unmount_with_operation_finish (G_MOUNT (source_object),
+ res, &error);
+ }
+
+ if (!unmounted)
+ {
+ if (error->code != G_IO_ERROR_FAILED_HANDLED)
+ {
+ g_autofree gchar *mount_name = NULL;
+
+ mount_name = g_mount_get_name (G_MOUNT (source_object));
+ if (data->eject)
+ {
+ primary = g_strdup_printf (_("Unable to eject %s"),
+ mount_name);
+ }
+ else
+ {
+ primary = g_strdup_printf (_("Unable to unmount %s"),
+ mount_name);
+ }
+ show_dialog (primary,
+ error->message,
+ data->parent_window,
+ GTK_MESSAGE_ERROR);
+ g_free (primary);
+ }
+ }
+
+ if (data->callback)
+ {
+ data->callback (data->callback_data);
+ }
+
+ if (error != NULL)
+ {
+ g_error_free (error);
+ }
+
+ unmount_data_free (data);
+}
+
+static void
+do_unmount (UnmountData *data)
+{
+ GMountOperation *mount_op;
+
+ if (data->mount_operation)
+ {
+ mount_op = g_object_ref (data->mount_operation);
+ }
+ else
+ {
+ mount_op = gtk_mount_operation_new (data->parent_window);
+ }
+
+ g_signal_connect (mount_op, "show-unmount-progress",
+ G_CALLBACK (show_unmount_progress_cb), NULL);
+ g_signal_connect (mount_op, "aborted",
+ G_CALLBACK (show_unmount_progress_aborted_cb), NULL);
+
+ if (data->eject)
+ {
+ g_mount_eject_with_operation (data->mount,
+ 0,
+ mount_op,
+ NULL,
+ unmount_mount_callback,
+ data);
+ }
+ else
+ {
+ g_mount_unmount_with_operation (data->mount,
+ 0,
+ mount_op,
+ NULL,
+ unmount_mount_callback,
+ data);
+ }
+ g_object_unref (mount_op);
+}
+
+static gboolean
+dir_has_files (GFile *dir)
+{
+ GFileEnumerator *enumerator;
+ gboolean res;
+ GFileInfo *file_info;
+
+ res = FALSE;
+
+ enumerator = g_file_enumerate_children (dir,
+ G_FILE_ATTRIBUTE_STANDARD_NAME,
+ 0,
+ NULL, NULL);
+ if (enumerator)
+ {
+ file_info = g_file_enumerator_next_file (enumerator, NULL, NULL);
+ if (file_info != NULL)
+ {
+ res = TRUE;
+ g_object_unref (file_info);
+ }
+
+ g_file_enumerator_close (enumerator, NULL, NULL);
+ g_object_unref (enumerator);
+ }
+
+
+ return res;
+}
+
+static GList *
+get_trash_dirs_for_mount (GMount *mount)
+{
+ GFile *root;
+ GFile *trash;
+ char *relpath;
+ GList *list;
+
+ root = g_mount_get_root (mount);
+ if (root == NULL)
+ {
+ return NULL;
+ }
+
+ list = NULL;
+
+ if (g_file_is_native (root))
+ {
+ relpath = g_strdup_printf (".Trash/%d", getuid ());
+ trash = g_file_resolve_relative_path (root, relpath);
+ g_free (relpath);
+
+ list = g_list_prepend (list, g_file_get_child (trash, "files"));
+ list = g_list_prepend (list, g_file_get_child (trash, "info"));
+
+ g_object_unref (trash);
+
+ relpath = g_strdup_printf (".Trash-%d", getuid ());
+ trash = g_file_get_child (root, relpath);
+ g_free (relpath);
+
+ list = g_list_prepend (list, g_file_get_child (trash, "files"));
+ list = g_list_prepend (list, g_file_get_child (trash, "info"));
+
+ g_object_unref (trash);
+ }
+
+ g_object_unref (root);
+
+ return list;
+}
+
+static gboolean
+has_trash_files (GMount *mount)
+{
+ GList *dirs, *l;
+ GFile *dir;
+ gboolean res;
+
+ dirs = get_trash_dirs_for_mount (mount);
+
+ res = FALSE;
+
+ for (l = dirs; l != NULL; l = l->next)
+ {
+ dir = l->data;
+
+ if (dir_has_files (dir))
+ {
+ res = TRUE;
+ break;
+ }
+ }
+
+ g_list_free_full (dirs, g_object_unref);
+
+ return res;
+}
+
+static GtkWidget *
+create_empty_trash_prompt (GtkWindow *parent_window)
+{
+ GtkWidget *dialog;
+
+ dialog = adw_message_dialog_new (parent_window,
+ _("Do you want to empty the trash before you unmount?"),
+ _("In order to regain the free space on this volume "
+ "the trash must be emptied. All trashed items on the volume "
+ "will be permanently lost."));
+ adw_message_dialog_add_responses (ADW_MESSAGE_DIALOG (dialog),
+ "do-not-empty", _("Do _not Empty Trash"),
+ "cancel", _("Cancel"),
+ "empty-trash", _("Empty _Trash"),
+ NULL);
+ adw_message_dialog_set_default_response (ADW_MESSAGE_DIALOG (dialog), "empty-trash");
+ adw_message_dialog_set_close_response (ADW_MESSAGE_DIALOG (dialog), "cancel");
+ adw_message_dialog_set_response_appearance (ADW_MESSAGE_DIALOG (dialog),
+ "empty-trash", ADW_RESPONSE_DESTRUCTIVE);
+
+ return dialog;
+}
+
+static void
+empty_trash_for_unmount_done (gboolean success,
+ gpointer user_data)
+{
+ UnmountData *data = user_data;
+ do_unmount (data);
+}
+
+static void
+empty_trash_prompt_cb (GtkDialog *dialog,
+ char *response,
+ gpointer user_data)
+{
+ UnmountData *data = user_data;
+
+ if (g_strcmp0 (response, "empty-trash") == 0)
+ {
+ GTask *task;
+ EmptyTrashJob *job;
+
+ job = op_job_new (EmptyTrashJob, data->parent_window, NULL);
+ job->should_confirm = FALSE;
+ job->trash_dirs = get_trash_dirs_for_mount (data->mount);
+ job->done_callback = empty_trash_for_unmount_done;
+ job->done_callback_data = data;
+
+ task = g_task_new (NULL, NULL, empty_trash_task_done, job);
+ g_task_set_task_data (task, job, NULL);
+ g_task_run_in_thread (task, empty_trash_thread_func);
+ g_object_unref (task);
+ }
+ else if (g_strcmp0 (response, "cancel") == 0)
+ {
+ if (data->callback)
+ {
+ data->callback (data->callback_data);
+ }
+
+ unmount_data_free (data);
+ }
+ else if (g_strcmp0 (response, "do-not-empty") == 0)
+ {
+ do_unmount (data);
+ }
+}
+
+void
+nautilus_file_operations_unmount_mount_full (GtkWindow *parent_window,
+ GMount *mount,
+ GMountOperation *mount_operation,
+ gboolean eject,
+ gboolean check_trash,
+ NautilusUnmountCallback callback,
+ gpointer callback_data)
+{
+ UnmountData *data;
+
+ data = g_new0 (UnmountData, 1);
+ data->callback = callback;
+ data->callback_data = callback_data;
+ if (parent_window)
+ {
+ data->parent_window = parent_window;
+ g_object_add_weak_pointer (G_OBJECT (data->parent_window),
+ (gpointer *) &data->parent_window);
+ }
+ if (mount_operation)
+ {
+ data->mount_operation = g_object_ref (mount_operation);
+ }
+ data->eject = eject;
+ data->mount = g_object_ref (mount);
+
+ if (check_trash && has_trash_files (mount))
+ {
+ GtkWidget *dialog;
+ dialog = create_empty_trash_prompt (parent_window);
+
+ g_signal_connect (dialog, "response", G_CALLBACK (empty_trash_prompt_cb), data);
+ gtk_widget_show (dialog);
+ return;
+ }
+
+ do_unmount (data);
+}
+
+void
+nautilus_file_operations_unmount_mount (GtkWindow *parent_window,
+ GMount *mount,
+ gboolean eject,
+ gboolean check_trash)
+{
+ nautilus_file_operations_unmount_mount_full (parent_window, mount, NULL, eject,
+ check_trash, NULL, NULL);
+}
+
+static void
+mount_callback_data_notify (gpointer data,
+ GObject *object)
+{
+ GMountOperation *mount_op;
+
+ mount_op = G_MOUNT_OPERATION (data);
+ g_object_set_data (G_OBJECT (mount_op), "mount-callback", NULL);
+ g_object_set_data (G_OBJECT (mount_op), "mount-callback-data", NULL);
+}
+
+static void
+volume_mount_cb (GObject *source_object,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ NautilusMountCallback mount_callback;
+ GObject *mount_callback_data_object;
+ GMountOperation *mount_op = user_data;
+ GError *error;
+ char *primary;
+ char *name;
+ gboolean success;
+
+ success = TRUE;
+ error = NULL;
+ if (!g_volume_mount_finish (G_VOLUME (source_object), res, &error))
+ {
+ if (error->code != G_IO_ERROR_FAILED_HANDLED &&
+ error->code != G_IO_ERROR_ALREADY_MOUNTED)
+ {
+ GtkWindow *parent;
+
+ parent = gtk_mount_operation_get_parent (GTK_MOUNT_OPERATION (mount_op));
+ name = g_volume_get_name (G_VOLUME (source_object));
+ primary = g_strdup_printf (_("Unable to access “%s”"), name);
+ g_free (name);
+ success = FALSE;
+ show_dialog (primary,
+ error->message,
+ parent,
+ GTK_MESSAGE_ERROR);
+ g_free (primary);
+ }
+ g_error_free (error);
+ }
+
+ mount_callback = (NautilusMountCallback)
+ g_object_get_data (G_OBJECT (mount_op), "mount-callback");
+ mount_callback_data_object =
+ g_object_get_data (G_OBJECT (mount_op), "mount-callback-data");
+
+ if (mount_callback != NULL)
+ {
+ (*mount_callback)(G_VOLUME (source_object),
+ success,
+ mount_callback_data_object);
+
+ if (mount_callback_data_object != NULL)
+ {
+ g_object_weak_unref (mount_callback_data_object,
+ mount_callback_data_notify,
+ mount_op);
+ }
+ }
+
+ g_object_unref (mount_op);
+}
+
+
+void
+nautilus_file_operations_mount_volume (GtkWindow *parent_window,
+ GVolume *volume)
+{
+ nautilus_file_operations_mount_volume_full (parent_window, volume,
+ NULL, NULL);
+}
+
+void
+nautilus_file_operations_mount_volume_full (GtkWindow *parent_window,
+ GVolume *volume,
+ NautilusMountCallback mount_callback,
+ GObject *mount_callback_data_object)
+{
+ GMountOperation *mount_op;
+
+ mount_op = gtk_mount_operation_new (parent_window);
+ g_mount_operation_set_password_save (mount_op, G_PASSWORD_SAVE_FOR_SESSION);
+ g_object_set_data (G_OBJECT (mount_op),
+ "mount-callback",
+ mount_callback);
+
+ if (mount_callback != NULL &&
+ mount_callback_data_object != NULL)
+ {
+ g_object_weak_ref (mount_callback_data_object,
+ mount_callback_data_notify,
+ mount_op);
+ }
+ g_object_set_data (G_OBJECT (mount_op),
+ "mount-callback-data",
+ mount_callback_data_object);
+
+ g_volume_mount (volume, 0, mount_op, NULL, volume_mount_cb, mount_op);
+}
+
+static void
+report_preparing_count_progress (CommonJob *job,
+ SourceInfo *source_info)
+{
+ char *s;
+
+ switch (source_info->op)
+ {
+ default:
+ case OP_KIND_COPY:
+ {
+ g_autofree gchar *formatted_size = NULL;
+
+ formatted_size = g_format_size (source_info->num_bytes);
+ s = g_strdup_printf (ngettext ("Preparing to copy %'d file (%s)",
+ "Preparing to copy %'d files (%s)",
+ source_info->num_files),
+ source_info->num_files,
+ formatted_size);
+ }
+ break;
+
+ case OP_KIND_MOVE:
+ {
+ g_autofree gchar *formatted_size = NULL;
+
+ formatted_size = g_format_size (source_info->num_bytes);
+ s = g_strdup_printf (ngettext ("Preparing to move %'d file (%s)",
+ "Preparing to move %'d files (%s)",
+ source_info->num_files),
+ source_info->num_files,
+ formatted_size);
+ }
+ break;
+
+ case OP_KIND_DELETE:
+ {
+ g_autofree gchar *formatted_size = NULL;
+
+ formatted_size = g_format_size (source_info->num_bytes);
+ s = g_strdup_printf (ngettext ("Preparing to delete %'d file (%s)",
+ "Preparing to delete %'d files (%s)",
+ source_info->num_files),
+ source_info->num_files,
+ formatted_size);
+ }
+ break;
+
+ case OP_KIND_TRASH:
+ {
+ s = g_strdup_printf (ngettext ("Preparing to trash %'d file",
+ "Preparing to trash %'d files",
+ source_info->num_files),
+ source_info->num_files);
+ }
+ break;
+
+ case OP_KIND_COMPRESS:
+ {
+ s = g_strdup_printf (ngettext ("Preparing to compress %'d file",
+ "Preparing to compress %'d files",
+ source_info->num_files),
+ source_info->num_files);
+ }
+ }
+
+ nautilus_progress_info_take_details (job->progress, s);
+ nautilus_progress_info_pulse_progress (job->progress);
+}
+
+static void
+count_file (GFileInfo *info,
+ CommonJob *job,
+ SourceInfo *source_info,
+ SourceDirInfo *dir_info)
+{
+ goffset num_bytes = g_file_info_get_size (info);
+
+ source_info->num_files += 1;
+ source_info->num_bytes += num_bytes;
+
+ if (dir_info != NULL)
+ {
+ dir_info->num_files_children += 1;
+ dir_info->num_bytes_children += num_bytes;
+ }
+
+ if (source_info->num_files_since_progress++ > 100)
+ {
+ report_preparing_count_progress (job, source_info);
+ source_info->num_files_since_progress = 0;
+ }
+}
+
+static char *
+get_scan_primary (OpKind kind)
+{
+ switch (kind)
+ {
+ default:
+ case OP_KIND_COPY:
+ {
+ return g_strdup (_("Error while copying."));
+ }
+
+ case OP_KIND_MOVE:
+ {
+ return g_strdup (_("Error while moving."));
+ }
+
+ case OP_KIND_DELETE:
+ {
+ return g_strdup (_("Error while deleting."));
+ }
+
+ case OP_KIND_TRASH:
+ {
+ return g_strdup (_("Error while moving files to trash."));
+ }
+
+ case OP_KIND_COMPRESS:
+ {
+ return g_strdup (_("Error while compressing files."));
+ }
+ }
+}
+
+static void
+scan_dir (GFile *dir,
+ SourceInfo *source_info,
+ CommonJob *job,
+ GQueue *dirs)
+{
+ GFileInfo *info;
+ GError *error;
+ GFile *subdir;
+ GFileEnumerator *enumerator;
+ char *primary, *secondary, *details;
+ int response;
+ SourceInfo saved_info;
+ g_autolist (GFile) subdirs = NULL;
+ SourceDirInfo *dir_info = NULL;
+ gboolean skip_subdirs = FALSE;
+
+ /* It is possible for this function to be called multiple times for
+ * the same directory.
+ * We pass a NULL SourceDirInfo into count_file() if this directory has
+ * already been scanned once so that its children are not counted more
+ * than once in the SourceDirInfo corresponding to this directory.
+ */
+
+ if (!g_hash_table_contains (source_info->scanned_dirs_info, dir))
+ {
+ dir_info = g_new0 (SourceDirInfo, 1);
+
+ g_hash_table_insert (source_info->scanned_dirs_info,
+ g_object_ref (dir),
+ dir_info);
+ }
+
+ /* Stash a copy of the struct to restore state before goto retry. Note that
+ * this assumes the code below does not access any pointer member */
+ saved_info = *source_info;
+
+retry:
+
+ if (dir_info != NULL)
+ {
+ dir_info->num_files_children = 0;
+ dir_info->num_bytes_children = 0;
+ }
+
+ error = NULL;
+ enumerator = g_file_enumerate_children (dir,
+ G_FILE_ATTRIBUTE_STANDARD_NAME ","
+ G_FILE_ATTRIBUTE_STANDARD_TYPE ","
+ G_FILE_ATTRIBUTE_STANDARD_SIZE,
+ G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
+ job->cancellable,
+ &error);
+ if (enumerator)
+ {
+ error = NULL;
+ while ((info = g_file_enumerator_next_file (enumerator, job->cancellable, &error)) != NULL)
+ {
+ count_file (info, job, source_info, dir_info);
+
+ if (g_file_info_get_file_type (info) == G_FILE_TYPE_DIRECTORY)
+ {
+ subdir = g_file_get_child (dir,
+ g_file_info_get_name (info));
+
+ subdirs = g_list_prepend (subdirs, subdir);
+ }
+
+ g_object_unref (info);
+ }
+ g_file_enumerator_close (enumerator, job->cancellable, NULL);
+ g_object_unref (enumerator);
+
+ if (error && IS_IO_ERROR (error, CANCELLED))
+ {
+ g_error_free (error);
+ }
+ else if (error)
+ {
+ g_autofree gchar *basename = NULL;
+
+ primary = get_scan_primary (source_info->op);
+ details = NULL;
+ basename = get_basename (dir);
+
+ if (IS_IO_ERROR (error, PERMISSION_DENIED))
+ {
+ secondary = g_strdup_printf (_("Files in the folder “%s” cannot be handled "
+ "because you do not have permissions to see them."),
+ basename);
+ }
+ else
+ {
+ secondary = g_strdup_printf (_("There was an error getting information about the "
+ "files in the folder “%s”."), basename);
+ details = error->message;
+ }
+
+ response = run_warning (job,
+ primary,
+ secondary,
+ details,
+ FALSE,
+ CANCEL, RETRY, SKIP,
+ NULL);
+
+ g_error_free (error);
+
+ if (response == 0 || response == GTK_RESPONSE_DELETE_EVENT)
+ {
+ abort_job (job);
+ skip_subdirs = TRUE;
+ }
+ else if (response == 1)
+ {
+ g_clear_list (&subdirs, g_object_unref);
+ *source_info = saved_info;
+ goto retry;
+ }
+ else if (response == 2)
+ {
+ skip_readdir_error (job, dir);
+ }
+ else
+ {
+ g_assert_not_reached ();
+ }
+ }
+ }
+ else if (job->skip_all_error)
+ {
+ g_error_free (error);
+ skip_file (job, dir);
+ skip_subdirs = TRUE;
+ }
+ else if (IS_IO_ERROR (error, CANCELLED))
+ {
+ g_error_free (error);
+ }
+ else
+ {
+ g_autofree gchar *basename = NULL;
+
+ primary = get_scan_primary (source_info->op);
+ details = NULL;
+ basename = get_basename (dir);
+ if (IS_IO_ERROR (error, PERMISSION_DENIED))
+ {
+ secondary = g_strdup_printf (_("The folder “%s” cannot be handled because you "
+ "do not have permissions to read it."),
+ basename);
+ }
+ else
+ {
+ secondary = g_strdup_printf (_("There was an error reading the folder “%s”."),
+ basename);
+ details = error->message;
+ }
+ /* set show_all to TRUE here, as we don't know how many
+ * files we'll end up processing yet.
+ */
+ response = run_warning (job,
+ primary,
+ secondary,
+ details,
+ TRUE,
+ CANCEL, SKIP_ALL, SKIP, RETRY,
+ NULL);
+
+ g_error_free (error);
+
+ if (response == 0 || response == GTK_RESPONSE_DELETE_EVENT)
+ {
+ abort_job (job);
+ skip_subdirs = TRUE;
+ }
+ else if (response == 1 || response == 2)
+ {
+ if (response == 1)
+ {
+ job->skip_all_error = TRUE;
+ }
+ skip_file (job, dir);
+ skip_subdirs = TRUE;
+ }
+ else if (response == 3)
+ {
+ goto retry;
+ }
+ else
+ {
+ g_assert_not_reached ();
+ }
+ }
+
+ if (!skip_subdirs)
+ {
+ while (subdirs != NULL)
+ {
+ GList *l = subdirs;
+ subdirs = g_list_remove_link (subdirs, l);
+
+ /* Push to head, since we want depth-first */
+ g_queue_push_head_link (dirs, l);
+ }
+ }
+}
+
+static void
+scan_file (GFile *file,
+ SourceInfo *source_info,
+ CommonJob *job)
+{
+ GFileInfo *info;
+ GError *error;
+ GQueue *dirs;
+ GFile *dir;
+ char *primary;
+ char *secondary;
+ char *details;
+ int response;
+
+ dirs = g_queue_new ();
+
+retry:
+ error = NULL;
+ info = g_file_query_info (file,
+ G_FILE_ATTRIBUTE_STANDARD_TYPE ","
+ G_FILE_ATTRIBUTE_STANDARD_SIZE,
+ G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
+ job->cancellable,
+ &error);
+
+ if (info)
+ {
+ count_file (info, job, source_info, NULL);
+
+ /* trashing operation doesn't recurse */
+ if (g_file_info_get_file_type (info) == G_FILE_TYPE_DIRECTORY &&
+ source_info->op != OP_KIND_TRASH)
+ {
+ g_queue_push_head (dirs, g_object_ref (file));
+ }
+ g_object_unref (info);
+ }
+ else if (job->skip_all_error)
+ {
+ g_error_free (error);
+ skip_file (job, file);
+ }
+ else if (IS_IO_ERROR (error, CANCELLED))
+ {
+ g_error_free (error);
+ }
+ else
+ {
+ g_autofree gchar *basename = NULL;
+
+ primary = get_scan_primary (source_info->op);
+ details = NULL;
+ basename = get_basename (file);
+
+ if (IS_IO_ERROR (error, PERMISSION_DENIED))
+ {
+ secondary = g_strdup_printf (_("The file “%s” cannot be handled because you do not have "
+ "permissions to read it."), basename);
+ }
+ else
+ {
+ secondary = g_strdup_printf (_("There was an error getting information about “%s”."),
+ basename);
+ details = error->message;
+ }
+ /* set show_all to TRUE here, as we don't know how many
+ * files we'll end up processing yet.
+ */
+ response = run_warning (job,
+ primary,
+ secondary,
+ details,
+ TRUE,
+ CANCEL, SKIP_ALL, SKIP, RETRY,
+ NULL);
+
+ g_error_free (error);
+
+ if (response == 0 || response == GTK_RESPONSE_DELETE_EVENT)
+ {
+ abort_job (job);
+ }
+ else if (response == 1 || response == 2)
+ {
+ if (response == 1)
+ {
+ job->skip_all_error = TRUE;
+ }
+ skip_file (job, file);
+ }
+ else if (response == 3)
+ {
+ goto retry;
+ }
+ else
+ {
+ g_assert_not_reached ();
+ }
+ }
+
+ while (!job_aborted (job) &&
+ (dir = g_queue_pop_head (dirs)) != NULL)
+ {
+ scan_dir (dir, source_info, job, dirs);
+ g_object_unref (dir);
+ }
+
+ /* Free all from queue if we exited early */
+ g_queue_foreach (dirs, (GFunc) g_object_unref, NULL);
+ g_queue_free (dirs);
+}
+
+static void
+scan_sources (GList *files,
+ SourceInfo *source_info,
+ CommonJob *job,
+ OpKind kind)
+{
+ GList *l;
+ GFile *file;
+
+ source_info->op = kind;
+ source_info->scanned_dirs_info = g_hash_table_new_full (g_file_hash,
+ (GEqualFunc) g_file_equal,
+ (GDestroyNotify) g_object_unref,
+ (GDestroyNotify) g_free);
+
+ report_preparing_count_progress (job, source_info);
+
+ for (l = files; l != NULL && !job_aborted (job); l = l->next)
+ {
+ file = l->data;
+
+ scan_file (file,
+ source_info,
+ job);
+ }
+
+ /* Make sure we report the final count */
+ report_preparing_count_progress (job, source_info);
+}
+
+static void
+verify_destination (CommonJob *job,
+ GFile *dest,
+ char **dest_fs_id,
+ goffset required_size)
+{
+ GFileInfo *info, *fsinfo;
+ GError *error;
+ const char *fs_type;
+ guint64 free_size;
+ guint64 size_difference;
+ char *primary, *secondary, *details;
+ int response;
+ GFileType file_type;
+ gboolean dest_is_symlink = FALSE;
+
+ if (dest_fs_id)
+ {
+ *dest_fs_id = NULL;
+ }
+
+retry:
+
+ error = NULL;
+ info = g_file_query_info (dest,
+ G_FILE_ATTRIBUTE_STANDARD_TYPE ","
+ G_FILE_ATTRIBUTE_ID_FILESYSTEM,
+ dest_is_symlink ? G_FILE_QUERY_INFO_NONE : G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
+ job->cancellable,
+ &error);
+
+ if (info == NULL)
+ {
+ g_autofree gchar *basename = NULL;
+
+ if (IS_IO_ERROR (error, CANCELLED))
+ {
+ g_error_free (error);
+ return;
+ }
+
+ basename = get_basename (dest);
+ primary = g_strdup_printf (_("Error while copying to “%s”."), basename);
+ details = NULL;
+
+ if (IS_IO_ERROR (error, PERMISSION_DENIED))
+ {
+ secondary = g_strdup (_("You do not have permissions to access the destination folder."));
+ }
+ else
+ {
+ secondary = g_strdup (_("There was an error getting information about the destination."));
+ details = error->message;
+ }
+
+ response = run_error (job,
+ primary,
+ secondary,
+ details,
+ FALSE,
+ CANCEL, RETRY,
+ NULL);
+
+ g_error_free (error);
+
+ if (response == 0 || response == GTK_RESPONSE_DELETE_EVENT)
+ {
+ abort_job (job);
+ }
+ else if (response == 1)
+ {
+ goto retry;
+ }
+ else
+ {
+ g_assert_not_reached ();
+ }
+
+ return;
+ }
+
+ file_type = g_file_info_get_file_type (info);
+ if (!dest_is_symlink && file_type == G_FILE_TYPE_SYMBOLIC_LINK)
+ {
+ /* Record that destination is a symlink and do real stat() once again */
+ dest_is_symlink = TRUE;
+ g_object_unref (info);
+ goto retry;
+ }
+
+ if (dest_fs_id)
+ {
+ *dest_fs_id =
+ g_strdup (g_file_info_get_attribute_string (info,
+ G_FILE_ATTRIBUTE_ID_FILESYSTEM));
+ }
+
+ g_object_unref (info);
+
+ if (file_type != G_FILE_TYPE_DIRECTORY)
+ {
+ g_autofree gchar *basename = NULL;
+
+ basename = get_basename (dest);
+ primary = g_strdup_printf (_("Error while copying to “%s”."), basename);
+ secondary = g_strdup (_("The destination is not a folder."));
+
+ run_error (job,
+ primary,
+ secondary,
+ NULL,
+ FALSE,
+ CANCEL,
+ NULL);
+
+ abort_job (job);
+ return;
+ }
+
+ if (dest_is_symlink)
+ {
+ /* We can't reliably statfs() destination if it's a symlink, thus not doing any further checks. */
+ return;
+ }
+
+ fsinfo = g_file_query_filesystem_info (dest,
+ G_FILE_ATTRIBUTE_FILESYSTEM_FREE ","
+ G_FILE_ATTRIBUTE_FILESYSTEM_READONLY ","
+ G_FILE_ATTRIBUTE_FILESYSTEM_TYPE,
+ job->cancellable,
+ NULL);
+
+ if (fsinfo == NULL)
+ {
+ /* All sorts of things can go wrong getting the fs info (like not supported)
+ * only check these things if the fs returns them
+ */
+ return;
+ }
+
+ /* ramfs reports a free size, but that size is always 0. If we're copying to ramfs,
+ * skip the free size check. */
+ fs_type = g_file_info_get_attribute_string (fsinfo,
+ G_FILE_ATTRIBUTE_FILESYSTEM_TYPE);
+
+ if (required_size > 0 &&
+ g_strcmp0 (fs_type, "ramfs") != 0 &&
+ g_file_info_has_attribute (fsinfo, G_FILE_ATTRIBUTE_FILESYSTEM_FREE))
+ {
+ free_size = g_file_info_get_attribute_uint64 (fsinfo,
+ G_FILE_ATTRIBUTE_FILESYSTEM_FREE);
+
+ if (free_size < required_size)
+ {
+ g_autofree gchar *basename = NULL;
+ g_autofree gchar *formatted_size = NULL;
+
+ basename = get_basename (dest);
+ size_difference = required_size - free_size;
+ primary = g_strdup_printf (_("Error while copying to “%s”."), basename);
+ secondary = g_strdup (_("There is not enough space on the destination."
+ " Try to remove files to make space."));
+
+ formatted_size = g_format_size (size_difference);
+ details = g_strdup_printf (_("%s more space is required to copy to the destination."),
+ formatted_size);
+
+ response = run_warning (job,
+ primary,
+ secondary,
+ details,
+ FALSE,
+ CANCEL,
+ COPY_FORCE,
+ RETRY,
+ NULL);
+
+ if (response == 0 || response == GTK_RESPONSE_DELETE_EVENT)
+ {
+ abort_job (job);
+ }
+ else if (response == 2)
+ {
+ goto retry;
+ }
+ else if (response == 1)
+ {
+ /* We are forced to copy - just fall through ... */
+ }
+ else
+ {
+ g_assert_not_reached ();
+ }
+ }
+ }
+
+ if (!job_aborted (job) &&
+ g_file_info_get_attribute_boolean (fsinfo,
+ G_FILE_ATTRIBUTE_FILESYSTEM_READONLY))
+ {
+ g_autofree gchar *basename = NULL;
+
+ basename = get_basename (dest);
+ primary = g_strdup_printf (_("Error while copying to “%s”."), basename);
+ secondary = g_strdup (_("The destination is read-only."));
+
+ run_error (job,
+ primary,
+ secondary,
+ NULL,
+ FALSE,
+ CANCEL,
+ NULL);
+
+ g_error_free (error);
+
+ abort_job (job);
+ }
+
+ g_object_unref (fsinfo);
+}
+
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wformat-nonliteral"
+static void
+report_copy_progress (CopyMoveJob *copy_job,
+ SourceInfo *source_info,
+ TransferInfo *transfer_info)
+{
+ int files_left;
+ goffset total_size;
+ double elapsed, transfer_rate;
+ int remaining_time;
+ guint64 now;
+ CommonJob *job;
+ gboolean is_move;
+ gchar *status;
+ char *details;
+ gchar *tmp;
+
+ job = (CommonJob *) copy_job;
+
+ is_move = copy_job->is_move;
+
+ now = g_get_monotonic_time ();
+
+ files_left = source_info->num_files - transfer_info->num_files;
+
+ /* Races and whatnot could cause this to be negative... */
+ if (files_left < 0)
+ {
+ files_left = 0;
+ }
+
+ /* If the number of files left is 0, we want to update the status without
+ * considering this time, since we want to change the status to completed
+ * and probably we won't get more calls to this function */
+ if (transfer_info->last_report_time != 0 &&
+ ABS ((gint64) (transfer_info->last_report_time - now)) < 100 * NSEC_PER_MICROSEC &&
+ files_left > 0)
+ {
+ return;
+ }
+ transfer_info->last_report_time = now;
+
+ if (files_left != transfer_info->last_reported_files_left ||
+ transfer_info->last_reported_files_left == 0)
+ {
+ /* Avoid changing this unless files_left changed since last time */
+ transfer_info->last_reported_files_left = files_left;
+
+ if (source_info->num_files == 1)
+ {
+ g_autofree gchar *basename_dest = NULL;
+
+ if (copy_job->destination != NULL)
+ {
+ if (is_move)
+ {
+ if (files_left > 0)
+ {
+ status = _("Moving “%s” to “%s”");
+ }
+ else
+ {
+ status = _("Moved “%s” to “%s”");
+ }
+ }
+ else
+ {
+ if (files_left > 0)
+ {
+ status = _("Copying “%s” to “%s”");
+ }
+ else
+ {
+ status = _("Copied “%s” to “%s”");
+ }
+ }
+
+ basename_dest = get_basename (G_FILE (copy_job->destination));
+
+ if (copy_job->fake_display_source != NULL)
+ {
+ g_autofree gchar *basename_fake_display_source = NULL;
+
+ basename_fake_display_source = get_basename (copy_job->fake_display_source);
+ tmp = g_strdup_printf (status,
+ basename_fake_display_source,
+ basename_dest);
+ }
+ else
+ {
+ g_autofree gchar *basename_data = NULL;
+
+ basename_data = get_basename (G_FILE (copy_job->files->data));
+ tmp = g_strdup_printf (status,
+ basename_data,
+ basename_dest);
+ }
+
+ nautilus_progress_info_take_status (job->progress,
+ tmp);
+ }
+ else
+ {
+ g_autofree gchar *basename = NULL;
+
+ if (files_left > 0)
+ {
+ status = _("Duplicating “%s”");
+ }
+ else
+ {
+ status = _("Duplicated “%s”");
+ }
+
+ basename = get_basename (G_FILE (copy_job->files->data));
+ nautilus_progress_info_take_status (job->progress,
+ g_strdup_printf (status,
+ basename));
+ }
+ }
+ else if (copy_job->files != NULL)
+ {
+ if (copy_job->destination != NULL)
+ {
+ if (files_left > 0)
+ {
+ g_autofree gchar *basename = NULL;
+
+ if (is_move)
+ {
+ status = ngettext ("Moving %'d file to “%s”",
+ "Moving %'d files to “%s”",
+ source_info->num_files);
+ }
+ else
+ {
+ status = ngettext ("Copying %'d file to “%s”",
+ "Copying %'d files to “%s”",
+ source_info->num_files);
+ }
+
+ basename = get_basename (G_FILE (copy_job->destination));
+ tmp = g_strdup_printf (status,
+ source_info->num_files,
+ basename);
+
+ nautilus_progress_info_take_status (job->progress,
+ tmp);
+ }
+ else
+ {
+ g_autofree gchar *basename = NULL;
+
+ if (is_move)
+ {
+ status = ngettext ("Moved %'d file to “%s”",
+ "Moved %'d files to “%s”",
+ source_info->num_files);
+ }
+ else
+ {
+ status = ngettext ("Copied %'d file to “%s”",
+ "Copied %'d files to “%s”",
+ source_info->num_files);
+ }
+
+ basename = get_basename (G_FILE (copy_job->destination));
+ tmp = g_strdup_printf (status,
+ source_info->num_files,
+ basename);
+
+ nautilus_progress_info_take_status (job->progress,
+ tmp);
+ }
+ }
+ else
+ {
+ GFile *parent;
+ g_autofree gchar *basename = NULL;
+
+ parent = g_file_get_parent (copy_job->files->data);
+ basename = get_basename (parent);
+ if (files_left > 0)
+ {
+ status = ngettext ("Duplicating %'d file in “%s”",
+ "Duplicating %'d files in “%s”",
+ source_info->num_files);
+ nautilus_progress_info_take_status (job->progress,
+ g_strdup_printf (status,
+ source_info->num_files,
+ basename));
+ }
+ else
+ {
+ status = ngettext ("Duplicated %'d file in “%s”",
+ "Duplicated %'d files in “%s”",
+ source_info->num_files);
+ nautilus_progress_info_take_status (job->progress,
+ g_strdup_printf (status,
+ source_info->num_files,
+ basename));
+ }
+ g_object_unref (parent);
+ }
+ }
+ }
+
+ total_size = MAX (source_info->num_bytes, transfer_info->num_bytes);
+
+ elapsed = g_timer_elapsed (job->time, NULL);
+ transfer_rate = 0;
+ remaining_time = INT_MAX;
+ if (elapsed > 0)
+ {
+ transfer_rate = transfer_info->num_bytes / elapsed;
+ if (transfer_rate > 0)
+ {
+ remaining_time = (total_size - transfer_info->num_bytes) / transfer_rate;
+ }
+ }
+
+ if (elapsed < SECONDS_NEEDED_FOR_RELIABLE_TRANSFER_RATE ||
+ transfer_rate == 0 ||
+ !transfer_info->partial_progress)
+ {
+ if (source_info->num_files == 1)
+ {
+ g_autofree gchar *formatted_size_num_bytes = NULL;
+ g_autofree gchar *formatted_size_total_size = NULL;
+
+ formatted_size_num_bytes = g_format_size (transfer_info->num_bytes);
+ formatted_size_total_size = g_format_size (total_size);
+ /* To translators: %s will expand to a size like "2 bytes" or "3 MB", so something like "4 kb / 4 MB" */
+ details = g_strdup_printf (_("%s / %s"),
+ formatted_size_num_bytes,
+ formatted_size_total_size);
+ }
+ else
+ {
+ if (files_left > 0)
+ {
+ /* To translators: %'d is the number of files completed for the operation,
+ * so it will be something like 2/14. */
+ details = g_strdup_printf (_("%'d / %'d"),
+ transfer_info->num_files + 1,
+ source_info->num_files);
+ }
+ else
+ {
+ /* To translators: %'d is the number of files completed for the operation,
+ * so it will be something like 2/14. */
+ details = g_strdup_printf (_("%'d / %'d"),
+ transfer_info->num_files,
+ source_info->num_files);
+ }
+ }
+ }
+ else
+ {
+ if (source_info->num_files == 1)
+ {
+ if (files_left > 0)
+ {
+ g_autofree gchar *formatted_time = NULL;
+ g_autofree gchar *formatted_size_num_bytes = NULL;
+ g_autofree gchar *formatted_size_total_size = NULL;
+ g_autofree gchar *formatted_size_transfer_rate = NULL;
+
+ formatted_time = get_formatted_time (remaining_time);
+ formatted_size_num_bytes = g_format_size (transfer_info->num_bytes);
+ formatted_size_total_size = g_format_size (total_size);
+ formatted_size_transfer_rate = g_format_size ((goffset) transfer_rate);
+ /* To translators: %s will expand to a size like "2 bytes" or "3 MB", %s to a time duration like
+ * "2 minutes". So the whole thing will be something like "2 kb / 4 MB -- 2 hours left (4kb/sec)"
+ *
+ * The singular/plural form will be used depending on the remaining time (i.e. the %s argument).
+ */
+ details = g_strdup_printf (ngettext ("%s / %s \xE2\x80\x94 %s left (%s/sec)",
+ "%s / %s \xE2\x80\x94 %s left (%s/sec)",
+ seconds_count_format_time_units (remaining_time)),
+ formatted_size_num_bytes,
+ formatted_size_total_size,
+ formatted_time,
+ formatted_size_transfer_rate);
+ }
+ else
+ {
+ g_autofree gchar *formatted_size_num_bytes = NULL;
+ g_autofree gchar *formatted_size_total_size = NULL;
+
+ formatted_size_num_bytes = g_format_size (transfer_info->num_bytes);
+ formatted_size_total_size = g_format_size (total_size);
+ /* To translators: %s will expand to a size like "2 bytes" or "3 MB". */
+ details = g_strdup_printf (_("%s / %s"),
+ formatted_size_num_bytes,
+ formatted_size_total_size);
+ }
+ }
+ else
+ {
+ if (files_left > 0)
+ {
+ g_autofree gchar *formatted_time = NULL;
+ g_autofree gchar *formatted_size = NULL;
+ formatted_time = get_formatted_time (remaining_time);
+ formatted_size = g_format_size ((goffset) transfer_rate);
+ /* To translators: %s will expand to a time duration like "2 minutes".
+ * So the whole thing will be something like "1 / 5 -- 2 hours left (4kb/sec)"
+ *
+ * The singular/plural form will be used depending on the remaining time (i.e. the %s argument).
+ */
+ details = g_strdup_printf (ngettext ("%'d / %'d \xE2\x80\x94 %s left (%s/sec)",
+ "%'d / %'d \xE2\x80\x94 %s left (%s/sec)",
+ seconds_count_format_time_units (remaining_time)),
+ transfer_info->num_files + 1, source_info->num_files,
+ formatted_time,
+ formatted_size);
+ }
+ else
+ {
+ /* To translators: %'d is the number of files completed for the operation,
+ * so it will be something like 2/14. */
+ details = g_strdup_printf (_("%'d / %'d"),
+ transfer_info->num_files,
+ source_info->num_files);
+ }
+ }
+ }
+ nautilus_progress_info_take_details (job->progress, details);
+
+ if (elapsed > SECONDS_NEEDED_FOR_APROXIMATE_TRANSFER_RATE)
+ {
+ nautilus_progress_info_set_remaining_time (job->progress,
+ remaining_time);
+ nautilus_progress_info_set_elapsed_time (job->progress,
+ elapsed);
+ }
+
+ nautilus_progress_info_set_progress (job->progress, transfer_info->num_bytes, total_size);
+}
+#pragma GCC diagnostic pop
+
+#define FAT_FORBIDDEN_CHARACTERS "/:*?\"<>\\|"
+
+static gboolean
+fat_str_replace (char *str,
+ char replacement)
+{
+ gboolean success;
+ int i;
+
+ success = FALSE;
+ for (i = 0; str[i] != '\0'; i++)
+ {
+ if (strchr (FAT_FORBIDDEN_CHARACTERS, str[i]) ||
+ str[i] < 32)
+ {
+ success = TRUE;
+ str[i] = replacement;
+ }
+ }
+
+ return success;
+}
+
+static gboolean
+make_file_name_valid_for_dest_fs (char *filename,
+ const char *dest_fs_type)
+{
+ if (dest_fs_type != NULL && filename != NULL)
+ {
+ if (/* The fuseblk filesystem type could be of any type
+ * in theory, but in practice is usually NTFS or exFAT.
+ * This assumption is a pragmatic way to solve
+ * https://gitlab.gnome.org/GNOME/nautilus/-/issues/1343 */
+ !strcmp (dest_fs_type, "fuse") ||
+ !strcmp (dest_fs_type, "ntfs") ||
+ /* msdos is returned for fat filesystems */
+ !strcmp (dest_fs_type, "msdos") ||
+ !strcmp (dest_fs_type, "exfat"))
+ {
+ gboolean ret;
+ int i, old_len;
+
+ ret = fat_str_replace (filename, '_');
+
+ old_len = strlen (filename);
+ for (i = 0; i < old_len; i++)
+ {
+ if (filename[i] != ' ')
+ {
+ g_strchomp (filename);
+ ret |= (old_len != strlen (filename));
+ break;
+ }
+ }
+
+ return ret;
+ }
+ }
+
+ return FALSE;
+}
+
+static GFile *
+get_unique_target_file (GFile *src,
+ GFile *dest_dir,
+ gboolean same_fs,
+ const char *dest_fs_type,
+ int count)
+{
+ const char *editname, *end;
+ char *basename, *new_name;
+ GFileInfo *info;
+ GFile *dest;
+ int max_length;
+ NautilusFile *file;
+ gboolean ignore_extension;
+
+ max_length = nautilus_get_max_child_name_length_for_location (dest_dir);
+
+ file = nautilus_file_get (src);
+ ignore_extension = nautilus_file_is_directory (file);
+ nautilus_file_unref (file);
+
+ dest = NULL;
+ info = g_file_query_info (src,
+ G_FILE_ATTRIBUTE_STANDARD_EDIT_NAME,
+ 0, NULL, NULL);
+ if (info != NULL)
+ {
+ editname = g_file_info_get_attribute_string (info, G_FILE_ATTRIBUTE_STANDARD_EDIT_NAME);
+
+ if (editname != NULL)
+ {
+ new_name = get_duplicate_name (editname, count, max_length, ignore_extension);
+ make_file_name_valid_for_dest_fs (new_name, dest_fs_type);
+ dest = g_file_get_child_for_display_name (dest_dir, new_name, NULL);
+ g_free (new_name);
+ }
+
+ g_object_unref (info);
+ }
+
+ if (dest == NULL)
+ {
+ basename = g_file_get_basename (src);
+ g_assert (basename == NULL);
+
+ if (g_utf8_validate (basename, -1, NULL))
+ {
+ new_name = get_duplicate_name (basename, count, max_length, ignore_extension);
+ make_file_name_valid_for_dest_fs (new_name, dest_fs_type);
+ dest = g_file_get_child_for_display_name (dest_dir, new_name, NULL);
+ g_free (new_name);
+ }
+
+ if (dest == NULL)
+ {
+ end = strrchr (basename, '.');
+ if (end != NULL)
+ {
+ count += atoi (end + 1);
+ }
+ new_name = g_strdup_printf ("%s.%d", basename, count);
+ make_file_name_valid_for_dest_fs (new_name, dest_fs_type);
+ dest = g_file_get_child (dest_dir, new_name);
+ g_free (new_name);
+ }
+
+ g_free (basename);
+ }
+
+ return dest;
+}
+
+static GFile *
+get_target_file_for_link (GFile *src,
+ GFile *dest_dir,
+ const char *dest_fs_type,
+ int count)
+{
+ const char *editname;
+ char *basename, *new_name;
+ GFileInfo *info;
+ GFile *dest;
+ int max_length;
+
+ max_length = nautilus_get_max_child_name_length_for_location (dest_dir);
+
+ dest = NULL;
+ info = g_file_query_info (src,
+ G_FILE_ATTRIBUTE_STANDARD_EDIT_NAME,
+ 0, NULL, NULL);
+ if (info != NULL)
+ {
+ editname = g_file_info_get_attribute_string (info, G_FILE_ATTRIBUTE_STANDARD_EDIT_NAME);
+
+ if (editname != NULL)
+ {
+ new_name = get_link_name (editname, count, max_length);
+ make_file_name_valid_for_dest_fs (new_name, dest_fs_type);
+ dest = g_file_get_child_for_display_name (dest_dir, new_name, NULL);
+ g_free (new_name);
+ }
+
+ g_object_unref (info);
+ }
+
+ if (dest == NULL)
+ {
+ basename = g_file_get_basename (src);
+ make_file_name_valid_for_dest_fs (basename, dest_fs_type);
+
+ if (g_utf8_validate (basename, -1, NULL))
+ {
+ new_name = get_link_name (basename, count, max_length);
+ make_file_name_valid_for_dest_fs (new_name, dest_fs_type);
+ dest = g_file_get_child_for_display_name (dest_dir, new_name, NULL);
+ g_free (new_name);
+ }
+
+ if (dest == NULL)
+ {
+ if (count == 1)
+ {
+ new_name = g_strdup_printf ("%s.lnk", basename);
+ }
+ else
+ {
+ new_name = g_strdup_printf ("%s.lnk%d", basename, count);
+ }
+ make_file_name_valid_for_dest_fs (new_name, dest_fs_type);
+ dest = g_file_get_child (dest_dir, new_name);
+ g_free (new_name);
+ }
+
+ g_free (basename);
+ }
+
+ return dest;
+}
+
+static GFile *
+get_target_file_with_custom_name (GFile *src,
+ GFile *dest_dir,
+ const char *dest_fs_type,
+ gboolean same_fs,
+ const gchar *custom_name)
+{
+ char *basename;
+ GFile *dest;
+ GFileInfo *info;
+ char *copyname;
+
+ dest = NULL;
+
+ if (custom_name != NULL)
+ {
+ copyname = g_strdup (custom_name);
+ make_file_name_valid_for_dest_fs (copyname, dest_fs_type);
+ dest = g_file_get_child_for_display_name (dest_dir, copyname, NULL);
+
+ g_free (copyname);
+ }
+
+ if (dest == NULL && !same_fs)
+ {
+ info = g_file_query_info (src,
+ G_FILE_ATTRIBUTE_STANDARD_COPY_NAME ","
+ G_FILE_ATTRIBUTE_TRASH_ORIG_PATH,
+ 0, NULL, NULL);
+
+ if (info)
+ {
+ copyname = NULL;
+
+ /* if file is being restored from trash make sure it uses its original name */
+ if (g_file_has_uri_scheme (src, "trash"))
+ {
+ copyname = g_path_get_basename (g_file_info_get_attribute_byte_string (info, G_FILE_ATTRIBUTE_TRASH_ORIG_PATH));
+ }
+
+ if (copyname == NULL)
+ {
+ copyname = g_strdup (g_file_info_get_attribute_string (info, G_FILE_ATTRIBUTE_STANDARD_COPY_NAME));
+ }
+
+ if (copyname)
+ {
+ make_file_name_valid_for_dest_fs (copyname, dest_fs_type);
+ dest = g_file_get_child_for_display_name (dest_dir, copyname, NULL);
+ g_free (copyname);
+ }
+
+ g_object_unref (info);
+ }
+ }
+
+ if (dest == NULL)
+ {
+ basename = g_file_get_basename (src);
+ make_file_name_valid_for_dest_fs (basename, dest_fs_type);
+ dest = g_file_get_child (dest_dir, basename);
+ g_free (basename);
+ }
+
+ return dest;
+}
+
+static GFile *
+get_target_file (GFile *src,
+ GFile *dest_dir,
+ const char *dest_fs_type,
+ gboolean same_fs)
+{
+ return get_target_file_with_custom_name (src, dest_dir, dest_fs_type, same_fs, NULL);
+}
+
+static gboolean
+has_fs_id (GFile *file,
+ const char *fs_id)
+{
+ const char *id;
+ GFileInfo *info;
+ gboolean res;
+
+ res = FALSE;
+ info = g_file_query_info (file,
+ G_FILE_ATTRIBUTE_ID_FILESYSTEM,
+ G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
+ NULL, NULL);
+
+ if (info)
+ {
+ id = g_file_info_get_attribute_string (info, G_FILE_ATTRIBUTE_ID_FILESYSTEM);
+
+ if (id && strcmp (id, fs_id) == 0)
+ {
+ res = TRUE;
+ }
+
+ g_object_unref (info);
+ }
+
+ return res;
+}
+
+static gboolean
+is_dir (GFile *file)
+{
+ GFileType file_type;
+
+ file_type = g_file_query_file_type (file,
+ G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
+ NULL);
+
+ return file_type == G_FILE_TYPE_DIRECTORY;
+}
+
+static GFile *
+map_possibly_volatile_file_to_real (GFile *volatile_file,
+ GCancellable *cancellable,
+ GError **error)
+{
+ GFile *real_file = NULL;
+ GFileInfo *info = NULL;
+
+ info = g_file_query_info (volatile_file,
+ G_FILE_ATTRIBUTE_STANDARD_IS_SYMLINK ","
+ G_FILE_ATTRIBUTE_STANDARD_IS_VOLATILE ","
+ G_FILE_ATTRIBUTE_STANDARD_SYMLINK_TARGET,
+ G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
+ cancellable,
+ error);
+ if (info == NULL)
+ {
+ return NULL;
+ }
+ else
+ {
+ gboolean is_volatile;
+
+ is_volatile = g_file_info_get_attribute_boolean (info,
+ G_FILE_ATTRIBUTE_STANDARD_IS_VOLATILE);
+ if (is_volatile)
+ {
+ const gchar *target;
+
+ target = g_file_info_get_symlink_target (info);
+ real_file = g_file_resolve_relative_path (volatile_file, target);
+ }
+ }
+
+ g_object_unref (info);
+
+ if (real_file == NULL)
+ {
+ real_file = g_object_ref (volatile_file);
+ }
+
+ return real_file;
+}
+
+static GFile *
+map_possibly_volatile_file_to_real_on_write (GFile *volatile_file,
+ GFileOutputStream *stream,
+ GCancellable *cancellable,
+ GError **error)
+{
+ GFile *real_file = NULL;
+ GFileInfo *info = NULL;
+
+ info = g_file_output_stream_query_info (stream,
+ G_FILE_ATTRIBUTE_STANDARD_IS_SYMLINK ","
+ G_FILE_ATTRIBUTE_STANDARD_IS_VOLATILE ","
+ G_FILE_ATTRIBUTE_STANDARD_SYMLINK_TARGET,
+ cancellable,
+ error);
+ if (info == NULL)
+ {
+ return NULL;
+ }
+ else
+ {
+ gboolean is_volatile;
+
+ is_volatile = g_file_info_get_attribute_boolean (info,
+ G_FILE_ATTRIBUTE_STANDARD_IS_VOLATILE);
+ if (is_volatile)
+ {
+ const gchar *target;
+
+ target = g_file_info_get_symlink_target (info);
+ real_file = g_file_resolve_relative_path (volatile_file, target);
+ }
+ }
+
+ g_object_unref (info);
+
+ if (real_file == NULL)
+ {
+ real_file = g_object_ref (volatile_file);
+ }
+
+ return real_file;
+}
+
+static void copy_move_file (CopyMoveJob *job,
+ GFile *src,
+ GFile *dest_dir,
+ gboolean same_fs,
+ gboolean unique_names,
+ char **dest_fs_type,
+ SourceInfo *source_info,
+ TransferInfo *transfer_info,
+ GHashTable *debuting_files,
+ gboolean overwrite,
+ gboolean *skipped_file,
+ gboolean readonly_source_fs);
+
+typedef enum
+{
+ CREATE_DEST_DIR_RETRY,
+ CREATE_DEST_DIR_FAILED,
+ CREATE_DEST_DIR_SUCCESS
+} CreateDestDirResult;
+
+static CreateDestDirResult
+create_dest_dir (CommonJob *job,
+ GFile *src,
+ GFile **dest,
+ gboolean same_fs,
+ char **dest_fs_type)
+{
+ GError *error;
+ GFile *new_dest, *dest_dir;
+ char *primary, *secondary, *details;
+ int response;
+ gboolean handled_invalid_filename;
+ gboolean res;
+
+ handled_invalid_filename = *dest_fs_type != NULL;
+
+retry:
+ /* First create the directory, then copy stuff to it before
+ * copying the attributes, because we need to be sure we can write to it */
+
+ error = NULL;
+ res = g_file_make_directory (*dest, job->cancellable, &error);
+
+ if (res)
+ {
+ GFile *real;
+
+ real = map_possibly_volatile_file_to_real (*dest, job->cancellable, &error);
+ if (real == NULL)
+ {
+ res = FALSE;
+ }
+ else
+ {
+ g_object_unref (*dest);
+ *dest = real;
+ }
+ }
+
+ if (!res)
+ {
+ g_autofree gchar *basename = NULL;
+
+ if (IS_IO_ERROR (error, CANCELLED))
+ {
+ g_error_free (error);
+ return CREATE_DEST_DIR_FAILED;
+ }
+ else if (IS_IO_ERROR (error, INVALID_FILENAME) &&
+ !handled_invalid_filename)
+ {
+ handled_invalid_filename = TRUE;
+
+ g_assert (*dest_fs_type == NULL);
+
+ dest_dir = g_file_get_parent (*dest);
+
+ if (dest_dir != NULL)
+ {
+ *dest_fs_type = query_fs_type (dest_dir, job->cancellable);
+
+ new_dest = get_target_file (src, dest_dir, *dest_fs_type, same_fs);
+ g_object_unref (dest_dir);
+
+ if (!g_file_equal (*dest, new_dest))
+ {
+ g_object_unref (*dest);
+ *dest = new_dest;
+ g_error_free (error);
+ return CREATE_DEST_DIR_RETRY;
+ }
+ else
+ {
+ g_object_unref (new_dest);
+ }
+ }
+ }
+
+ primary = g_strdup (_("Error while copying."));
+ details = NULL;
+ basename = get_basename (src);
+
+ if (IS_IO_ERROR (error, PERMISSION_DENIED))
+ {
+ secondary = g_strdup_printf (_("The folder “%s” cannot be copied because you do not "
+ "have permissions to create it in the destination."),
+ basename);
+ }
+ else
+ {
+ secondary = g_strdup_printf (_("There was an error creating the folder “%s”."),
+ basename);
+ details = error->message;
+ }
+
+ response = run_warning (job,
+ primary,
+ secondary,
+ details,
+ FALSE,
+ CANCEL, SKIP, RETRY,
+ NULL);
+
+ g_error_free (error);
+
+ if (response == 0 || response == GTK_RESPONSE_DELETE_EVENT)
+ {
+ abort_job (job);
+ }
+ else if (response == 1)
+ {
+ /* Skip: Do Nothing */
+ }
+ else if (response == 2)
+ {
+ goto retry;
+ }
+ else
+ {
+ g_assert_not_reached ();
+ }
+ return CREATE_DEST_DIR_FAILED;
+ }
+ nautilus_file_changes_queue_file_added (*dest);
+
+ if (job->undo_info != NULL)
+ {
+ nautilus_file_undo_info_ext_add_origin_target_pair (NAUTILUS_FILE_UNDO_INFO_EXT (job->undo_info),
+ src, *dest);
+ }
+
+ return CREATE_DEST_DIR_SUCCESS;
+}
+
+/* a return value of FALSE means retry, i.e.
+ * the destination has changed and the source
+ * is expected to re-try the preceding
+ * g_file_move() or g_file_copy() call with
+ * the new destination.
+ */
+static gboolean
+copy_move_directory (CopyMoveJob *copy_job,
+ GFile *src,
+ GFile **dest,
+ gboolean same_fs,
+ gboolean create_dest,
+ char **parent_dest_fs_type,
+ SourceInfo *source_info,
+ TransferInfo *transfer_info,
+ GHashTable *debuting_files,
+ gboolean *skipped_file,
+ gboolean readonly_source_fs)
+{
+ g_autoptr (GFileInfo) src_info = NULL;
+ GFileInfo *info;
+ GError *error;
+ GFile *src_file;
+ GFileEnumerator *enumerator;
+ char *primary, *secondary, *details;
+ char *dest_fs_type;
+ int response;
+ gboolean skip_error;
+ gboolean local_skipped_file;
+ CommonJob *job;
+ GFileCopyFlags flags;
+
+ job = (CommonJob *) copy_job;
+ *skipped_file = FALSE;
+
+ if (create_dest)
+ {
+ g_autofree char *attrs_to_read = NULL;
+
+ switch (create_dest_dir (job, src, dest, same_fs, parent_dest_fs_type))
+ {
+ case CREATE_DEST_DIR_RETRY:
+ {
+ /* next time copy_move_directory() is called,
+ * create_dest will be FALSE if a directory already
+ * exists under the new name (i.e. WOULD_RECURSE)
+ */
+ return FALSE;
+ }
+
+ case CREATE_DEST_DIR_FAILED:
+ {
+ *skipped_file = TRUE;
+ return TRUE;
+ }
+
+ case CREATE_DEST_DIR_SUCCESS:
+ default:
+ {
+ }
+ break;
+ }
+
+ if (debuting_files)
+ {
+ g_hash_table_replace (debuting_files, g_object_ref (*dest), GINT_TO_POINTER (TRUE));
+ }
+
+ flags = G_FILE_COPY_NOFOLLOW_SYMLINKS;
+ if (readonly_source_fs)
+ {
+ flags |= G_FILE_COPY_TARGET_DEFAULT_PERMS;
+ }
+ if (copy_job->is_move)
+ {
+ flags |= G_FILE_COPY_ALL_METADATA;
+ }
+
+ /* Ignore errors here. Failure to copy metadata is not a hard error */
+ attrs_to_read = g_file_build_attribute_list_for_copy (*dest, flags, job->cancellable, NULL);
+ if (attrs_to_read != NULL)
+ {
+ src_info = g_file_query_info (src, attrs_to_read, G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS, job->cancellable, NULL);
+ }
+ }
+
+ local_skipped_file = FALSE;
+ dest_fs_type = NULL;
+
+ skip_error = should_skip_readdir_error (job, src);
+retry:
+ error = NULL;
+ enumerator = g_file_enumerate_children (src,
+ G_FILE_ATTRIBUTE_STANDARD_NAME,
+ G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
+ job->cancellable,
+ &error);
+ if (enumerator)
+ {
+ error = NULL;
+
+ while (!job_aborted (job) &&
+ (info = g_file_enumerator_next_file (enumerator, job->cancellable, skip_error ? NULL : &error)) != NULL)
+ {
+ src_file = g_file_get_child (src,
+ g_file_info_get_name (info));
+ copy_move_file (copy_job, src_file, *dest, same_fs, FALSE, &dest_fs_type,
+ source_info, transfer_info, NULL, FALSE, &local_skipped_file,
+ readonly_source_fs);
+
+ if (local_skipped_file)
+ {
+ source_info_remove_file_from_count (src_file, job, source_info);
+ report_copy_progress (copy_job, source_info, transfer_info);
+ }
+
+ g_object_unref (src_file);
+ g_object_unref (info);
+ }
+ g_file_enumerator_close (enumerator, job->cancellable, NULL);
+ g_object_unref (enumerator);
+
+ if (error && IS_IO_ERROR (error, CANCELLED))
+ {
+ g_error_free (error);
+ }
+ else if (error)
+ {
+ g_autofree gchar *basename = NULL;
+
+ if (copy_job->is_move)
+ {
+ primary = g_strdup (_("Error while moving."));
+ }
+ else
+ {
+ primary = g_strdup (_("Error while copying."));
+ }
+ details = NULL;
+ basename = get_basename (src);
+
+ if (IS_IO_ERROR (error, PERMISSION_DENIED))
+ {
+ secondary = g_strdup_printf (_("Files in the folder “%s” cannot be copied because you do "
+ "not have permissions to see them."), basename);
+ }
+ else
+ {
+ secondary = g_strdup_printf (_("There was an error getting information about "
+ "the files in the folder “%s”."),
+ basename);
+ details = error->message;
+ }
+
+ response = run_warning (job,
+ primary,
+ secondary,
+ details,
+ FALSE,
+ CANCEL, _("_Skip files"),
+ NULL);
+
+ g_error_free (error);
+
+ if (response == 0 || response == GTK_RESPONSE_DELETE_EVENT)
+ {
+ abort_job (job);
+ }
+ else if (response == 1)
+ {
+ /* Skip: Do Nothing */
+ local_skipped_file = TRUE;
+ }
+ else
+ {
+ g_assert_not_reached ();
+ }
+ }
+
+ /* Count the copied directory as a file */
+ transfer_info->num_files++;
+
+ info = g_file_query_info (src,
+ G_FILE_ATTRIBUTE_STANDARD_SIZE,
+ G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
+ job->cancellable,
+ NULL);
+
+ g_warn_if_fail (info != NULL);
+
+ if (info != NULL)
+ {
+ transfer_info->num_bytes += g_file_info_get_size (info);
+
+ g_object_unref (info);
+ }
+
+ report_copy_progress (copy_job, source_info, transfer_info);
+
+ if (debuting_files)
+ {
+ g_hash_table_replace (debuting_files, g_object_ref (*dest), GINT_TO_POINTER (create_dest));
+ }
+ }
+ else if (IS_IO_ERROR (error, CANCELLED))
+ {
+ g_error_free (error);
+ }
+ else
+ {
+ g_autofree gchar *basename = NULL;
+
+ if (copy_job->is_move)
+ {
+ primary = g_strdup (_("Error while moving."));
+ }
+ else
+ {
+ primary = g_strdup (_("Error while copying."));
+ }
+ details = NULL;
+ basename = get_basename (src);
+
+ if (IS_IO_ERROR (error, PERMISSION_DENIED))
+ {
+ secondary = g_strdup_printf (_("The folder “%s” cannot be copied because you do not have "
+ "permissions to read it."), basename);
+ }
+ else
+ {
+ secondary = g_strdup_printf (_("There was an error reading the folder “%s”."),
+ basename);
+
+ details = error->message;
+ }
+
+ response = run_warning (job,
+ primary,
+ secondary,
+ details,
+ FALSE,
+ CANCEL, SKIP, RETRY,
+ NULL);
+
+ g_error_free (error);
+
+ if (response == 0 || response == GTK_RESPONSE_DELETE_EVENT)
+ {
+ abort_job (job);
+ }
+ else if (response == 1)
+ {
+ /* Skip: Do Nothing */
+ *skipped_file = TRUE;
+ }
+ else if (response == 2)
+ {
+ goto retry;
+ }
+ else
+ {
+ g_assert_not_reached ();
+ }
+ }
+
+ if (src_info != NULL)
+ {
+ /* Ignore errors here. Failure to copy metadata is not a hard error */
+ g_file_set_attributes_from_info (*dest,
+ src_info,
+ G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
+ job->cancellable,
+ NULL);
+ }
+
+ if (!job_aborted (job) && copy_job->is_move &&
+ /* Don't delete source if there was a skipped file */
+ !*skipped_file &&
+ !local_skipped_file)
+ {
+ if (!g_file_delete (src, job->cancellable, &error))
+ {
+ g_autofree gchar *basename = NULL;
+
+ if (job->skip_all_error)
+ {
+ *skipped_file = TRUE;
+ goto skip;
+ }
+ basename = get_basename (src);
+ primary = g_strdup_printf (_("Error while moving “%s”."), basename);
+ secondary = g_strdup (_("Could not remove the source folder."));
+ details = error->message;
+
+ response = run_cancel_or_skip_warning (job,
+ primary,
+ secondary,
+ details,
+ source_info->num_files,
+ source_info->num_files - transfer_info->num_files);
+
+ if (response == 0 || response == GTK_RESPONSE_DELETE_EVENT)
+ {
+ abort_job (job);
+ }
+ else if (response == 1) /* skip all */
+ {
+ job->skip_all_error = TRUE;
+ *skipped_file = TRUE;
+ }
+ else if (response == 2) /* skip */
+ {
+ *skipped_file = TRUE;
+ }
+ else
+ {
+ g_assert_not_reached ();
+ }
+
+skip:
+ g_error_free (error);
+ }
+ }
+
+ g_free (dest_fs_type);
+ return TRUE;
+}
+
+
+typedef struct
+{
+ CommonJob *job;
+ GFile *source;
+} DeleteExistingFileData;
+
+typedef struct
+{
+ CopyMoveJob *job;
+ goffset last_size;
+ SourceInfo *source_info;
+ TransferInfo *transfer_info;
+} ProgressData;
+
+static void
+copy_file_progress_callback (goffset current_num_bytes,
+ goffset total_num_bytes,
+ gpointer user_data)
+{
+ ProgressData *pdata;
+ goffset new_size;
+
+ pdata = user_data;
+
+ if (current_num_bytes != 0 &&
+ current_num_bytes != total_num_bytes)
+ {
+ pdata->transfer_info->partial_progress = TRUE;
+ }
+
+ new_size = current_num_bytes - pdata->last_size;
+
+ if (new_size > 0)
+ {
+ pdata->transfer_info->num_bytes += new_size;
+ pdata->last_size = current_num_bytes;
+ report_copy_progress (pdata->job,
+ pdata->source_info,
+ pdata->transfer_info);
+ }
+}
+
+static gboolean
+test_dir_is_parent (GFile *child,
+ GFile *root)
+{
+ GFile *f, *tmp;
+
+ f = g_file_dup (child);
+ while (f)
+ {
+ if (g_file_equal (f, root))
+ {
+ g_object_unref (f);
+ return TRUE;
+ }
+ tmp = f;
+ f = g_file_get_parent (f);
+ g_object_unref (tmp);
+ }
+ if (f)
+ {
+ g_object_unref (f);
+ }
+ return FALSE;
+}
+
+static char *
+query_fs_type (GFile *file,
+ GCancellable *cancellable)
+{
+ GFileInfo *fsinfo;
+ char *ret;
+
+ ret = NULL;
+
+ fsinfo = g_file_query_filesystem_info (file,
+ G_FILE_ATTRIBUTE_FILESYSTEM_TYPE,
+ cancellable,
+ NULL);
+ if (fsinfo != NULL)
+ {
+ ret = g_strdup (g_file_info_get_attribute_string (fsinfo, G_FILE_ATTRIBUTE_FILESYSTEM_TYPE));
+ g_object_unref (fsinfo);
+ }
+
+ if (ret == NULL)
+ {
+ /* ensure that we don't attempt to query
+ * the FS type for each file in a given
+ * directory, if it can't be queried. */
+ ret = g_strdup ("");
+ }
+
+ return ret;
+}
+
+static FileConflictResponse *
+handle_copy_move_conflict (CommonJob *job,
+ GFile *src,
+ GFile *dest,
+ GFile *dest_dir)
+{
+ FileConflictResponse *response;
+ g_autofree gchar *basename = NULL;
+ g_autoptr (GFile) suggested_file = NULL;
+ g_autofree gchar *suggestion = NULL;
+ gboolean should_start_inactive;
+
+ g_timer_stop (job->time);
+ nautilus_progress_info_pause (job->progress);
+
+ should_start_inactive = is_long_job (job);
+
+ basename = g_file_get_basename (dest);
+ suggested_file = nautilus_generate_unique_file_in_directory (dest_dir, basename);
+ suggestion = g_file_get_basename (suggested_file);
+
+ response = copy_move_conflict_ask_user_action (job->parent_window,
+ should_start_inactive,
+ src,
+ dest,
+ dest_dir,
+ suggestion);
+
+ nautilus_progress_info_resume (job->progress);
+ g_timer_continue (job->time);
+
+ return response;
+}
+
+static GFile *
+get_target_file_for_display_name (GFile *dir,
+ const gchar *name)
+{
+ GFile *dest;
+
+ dest = NULL;
+ dest = g_file_get_child_for_display_name (dir, name, NULL);
+
+ if (dest == NULL)
+ {
+ dest = g_file_get_child (dir, name);
+ }
+
+ return dest;
+}
+
+/* This is a workaround to resolve broken conflict dialog for google-drive
+ * locations. This is needed to provide POSIX-like behavior for google-drive
+ * filesystem, where each file has an unique identificator that is not tied to
+ * its display_name. See the following MR for more details:
+ * https://gitlab.gnome.org/GNOME/nautilus/merge_requests/514.
+ */
+static GFile *
+get_target_file_from_source_display_name (CopyMoveJob *copy_job,
+ GFile *src,
+ GFile *dir)
+{
+ CommonJob *job;
+ g_autoptr (GError) error = NULL;
+ g_autoptr (GFileInfo) info = NULL;
+ gchar *primary, *secondary;
+ GFile *dest = NULL;
+
+ job = (CommonJob *) copy_job;
+
+ info = g_file_query_info (src, G_FILE_ATTRIBUTE_STANDARD_DISPLAY_NAME, 0, NULL, &error);
+ if (info == NULL)
+ {
+ if (copy_job->is_move)
+ {
+ primary = g_strdup (_("Error while moving."));
+ }
+ else
+ {
+ primary = g_strdup (_("Error while copying."));
+ }
+ secondary = g_strdup (_("There was an error getting information about the source."));
+
+ run_error (job,
+ primary,
+ secondary,
+ error->message,
+ FALSE,
+ CANCEL,
+ NULL);
+
+ abort_job (job);
+ }
+ else
+ {
+ dest = get_target_file_for_display_name (dir, g_file_info_get_display_name (info));
+ }
+
+ return dest;
+}
+
+
+/* Debuting files is non-NULL only for toplevel items */
+static void
+copy_move_file (CopyMoveJob *copy_job,
+ GFile *src,
+ GFile *dest_dir,
+ gboolean same_fs,
+ gboolean unique_names,
+ char **dest_fs_type,
+ SourceInfo *source_info,
+ TransferInfo *transfer_info,
+ GHashTable *debuting_files,
+ gboolean overwrite,
+ gboolean *skipped_file,
+ gboolean readonly_source_fs)
+{
+ GFile *dest, *new_dest;
+ g_autofree gchar *dest_uri = NULL;
+ GError *error;
+ GFileCopyFlags flags;
+ char *primary, *secondary, *details;
+ ProgressData pdata;
+ gboolean would_recurse;
+ CommonJob *job;
+ gboolean res;
+ int unique_name_nr;
+ gboolean handled_invalid_filename;
+
+ job = (CommonJob *) copy_job;
+
+ *skipped_file = FALSE;
+
+ if (should_skip_file (job, src))
+ {
+ *skipped_file = TRUE;
+ return;
+ }
+
+ unique_name_nr = 1;
+
+ /* another file in the same directory might have handled the invalid
+ * filename condition for us
+ */
+ handled_invalid_filename = *dest_fs_type != NULL;
+
+ if (unique_names)
+ {
+ dest = get_unique_target_file (src, dest_dir, same_fs, *dest_fs_type, unique_name_nr++);
+ }
+ else if (copy_job->target_name != NULL)
+ {
+ dest = get_target_file_with_custom_name (src, dest_dir, *dest_fs_type, same_fs,
+ copy_job->target_name);
+ }
+ else if (g_file_has_uri_scheme (src, "google-drive") &&
+ g_file_has_uri_scheme (dest_dir, "google-drive"))
+ {
+ dest = get_target_file_from_source_display_name (copy_job, src, dest_dir);
+ if (dest == NULL)
+ {
+ *skipped_file = TRUE;
+ return;
+ }
+ }
+ else
+ {
+ dest = get_target_file (src, dest_dir, *dest_fs_type, same_fs);
+ }
+
+ /* Don't allow recursive move/copy into itself.
+ * (We would get a file system error if we proceeded but it is nicer to
+ * detect and report it at this level) */
+ if (test_dir_is_parent (dest_dir, src))
+ {
+ int response;
+
+ if (job->skip_all_error)
+ {
+ goto out;
+ }
+
+ /* the run_warning() frees all strings passed in automatically */
+ primary = copy_job->is_move ? g_strdup (_("You cannot move a folder into itself."))
+ : g_strdup (_("You cannot copy a folder into itself."));
+ secondary = g_strdup (_("The destination folder is inside the source folder."));
+
+ response = run_cancel_or_skip_warning (job,
+ primary,
+ secondary,
+ NULL,
+ source_info->num_files,
+ source_info->num_files - transfer_info->num_files);
+
+ if (response == 0 || response == GTK_RESPONSE_DELETE_EVENT)
+ {
+ abort_job (job);
+ }
+ else if (response == 1) /* skip all */
+ {
+ job->skip_all_error = TRUE;
+ }
+ else if (response == 2) /* skip */
+ { /* do nothing */
+ }
+ else
+ {
+ g_assert_not_reached ();
+ }
+
+ goto out;
+ }
+
+ /* Don't allow copying over the source or one of the parents of the source.
+ */
+ if (test_dir_is_parent (src, dest))
+ {
+ int response;
+
+ if (job->skip_all_error)
+ {
+ goto out;
+ }
+
+ /* the run_warning() frees all strings passed in automatically */
+ primary = copy_job->is_move ? g_strdup (_("You cannot move a file over itself."))
+ : g_strdup (_("You cannot copy a file over itself."));
+ secondary = g_strdup (_("The source file would be overwritten by the destination."));
+
+ response = run_cancel_or_skip_warning (job,
+ primary,
+ secondary,
+ NULL,
+ source_info->num_files,
+ source_info->num_files - transfer_info->num_files);
+
+ if (response == 0 || response == GTK_RESPONSE_DELETE_EVENT)
+ {
+ abort_job (job);
+ }
+ else if (response == 1) /* skip all */
+ {
+ job->skip_all_error = TRUE;
+ }
+ else if (response == 2) /* skip */
+ { /* do nothing */
+ }
+ else
+ {
+ g_assert_not_reached ();
+ }
+
+ goto out;
+ }
+
+
+retry:
+
+ error = NULL;
+ flags = G_FILE_COPY_NOFOLLOW_SYMLINKS;
+ if (overwrite)
+ {
+ flags |= G_FILE_COPY_OVERWRITE;
+ }
+ if (readonly_source_fs)
+ {
+ flags |= G_FILE_COPY_TARGET_DEFAULT_PERMS;
+ }
+
+ pdata.job = copy_job;
+ pdata.last_size = 0;
+ pdata.source_info = source_info;
+ pdata.transfer_info = transfer_info;
+
+ if (copy_job->is_move)
+ {
+ res = g_file_move (src, dest,
+ flags,
+ job->cancellable,
+ copy_file_progress_callback,
+ &pdata,
+ &error);
+ }
+ else
+ {
+ res = g_file_copy (src, dest,
+ flags,
+ job->cancellable,
+ copy_file_progress_callback,
+ &pdata,
+ &error);
+ }
+
+ if (res)
+ {
+ GFile *real;
+
+ real = map_possibly_volatile_file_to_real (dest, job->cancellable, &error);
+ if (real == NULL)
+ {
+ res = FALSE;
+ }
+ else
+ {
+ g_object_unref (dest);
+ dest = real;
+ }
+ }
+
+ if (res)
+ {
+ transfer_info->num_files++;
+ report_copy_progress (copy_job, source_info, transfer_info);
+
+ if (debuting_files)
+ {
+ dest_uri = g_file_get_uri (dest);
+
+ g_hash_table_replace (debuting_files, g_object_ref (dest), GINT_TO_POINTER (!overwrite));
+ }
+ if (copy_job->is_move)
+ {
+ nautilus_file_changes_queue_file_moved (src, dest);
+ }
+ else
+ {
+ nautilus_file_changes_queue_file_added (dest);
+ }
+
+ if (job->undo_info != NULL)
+ {
+ nautilus_file_undo_info_ext_add_origin_target_pair (NAUTILUS_FILE_UNDO_INFO_EXT (job->undo_info),
+ src, dest);
+ }
+
+ g_object_unref (dest);
+ return;
+ }
+
+ if (!handled_invalid_filename &&
+ IS_IO_ERROR (error, INVALID_FILENAME))
+ {
+ handled_invalid_filename = TRUE;
+
+ g_assert (*dest_fs_type == NULL);
+ *dest_fs_type = query_fs_type (dest_dir, job->cancellable);
+
+ if (unique_names)
+ {
+ new_dest = get_unique_target_file (src, dest_dir, same_fs, *dest_fs_type, unique_name_nr);
+ }
+ else
+ {
+ new_dest = get_target_file (src, dest_dir, *dest_fs_type, same_fs);
+ }
+
+ if (!g_file_equal (dest, new_dest))
+ {
+ g_object_unref (dest);
+ dest = new_dest;
+
+ g_error_free (error);
+ goto retry;
+ }
+ else
+ {
+ g_object_unref (new_dest);
+ }
+ }
+
+ /* Conflict */
+ if (!overwrite &&
+ IS_IO_ERROR (error, EXISTS))
+ {
+ gboolean source_is_directory;
+ gboolean destination_is_directory;
+ gboolean is_merge;
+ FileConflictResponse *response;
+
+ source_is_directory = is_dir (src);
+ destination_is_directory = is_dir (dest);
+
+ g_error_free (error);
+
+ if (unique_names)
+ {
+ g_object_unref (dest);
+ dest = get_unique_target_file (src, dest_dir, same_fs, *dest_fs_type, unique_name_nr++);
+ goto retry;
+ }
+
+ is_merge = FALSE;
+
+ if (source_is_directory && destination_is_directory)
+ {
+ is_merge = TRUE;
+ }
+ else if (!source_is_directory && destination_is_directory)
+ {
+ /* Any sane backend will fail with G_IO_ERROR_IS_DIRECTORY. */
+ overwrite = TRUE;
+ goto retry;
+ }
+
+ if ((is_merge && job->merge_all) ||
+ (!is_merge && job->replace_all))
+ {
+ overwrite = TRUE;
+ goto retry;
+ }
+
+ if (job->skip_all_conflict)
+ {
+ goto out;
+ }
+
+ response = handle_copy_move_conflict (job, src, dest, dest_dir);
+
+ if (response->id == GTK_RESPONSE_CANCEL ||
+ response->id == GTK_RESPONSE_DELETE_EVENT)
+ {
+ file_conflict_response_free (response);
+ abort_job (job);
+ }
+ else if (response->id == CONFLICT_RESPONSE_SKIP)
+ {
+ if (response->apply_to_all)
+ {
+ job->skip_all_conflict = TRUE;
+ }
+ file_conflict_response_free (response);
+ }
+ else if (response->id == CONFLICT_RESPONSE_REPLACE) /* merge/replace */
+ {
+ if (response->apply_to_all)
+ {
+ if (is_merge)
+ {
+ job->merge_all = TRUE;
+ }
+ else
+ {
+ job->replace_all = TRUE;
+ }
+ }
+ overwrite = TRUE;
+ file_conflict_response_free (response);
+ goto retry;
+ }
+ else if (response->id == CONFLICT_RESPONSE_RENAME)
+ {
+ g_object_unref (dest);
+ dest = get_target_file_for_display_name (dest_dir,
+ response->new_name);
+ file_conflict_response_free (response);
+ goto retry;
+ }
+ else
+ {
+ g_assert_not_reached ();
+ }
+ }
+ /* Needs to recurse */
+ else if (IS_IO_ERROR (error, WOULD_RECURSE) ||
+ IS_IO_ERROR (error, WOULD_MERGE))
+ {
+ gboolean is_merge;
+
+ is_merge = error->code == G_IO_ERROR_WOULD_MERGE;
+ would_recurse = error->code == G_IO_ERROR_WOULD_RECURSE;
+ g_error_free (error);
+
+ if (overwrite && would_recurse)
+ {
+ error = NULL;
+
+ /* Copying a dir onto file, first remove the file */
+ if (!g_file_delete (dest, job->cancellable, &error) &&
+ !IS_IO_ERROR (error, NOT_FOUND))
+ {
+ g_autofree gchar *basename = NULL;
+ g_autofree gchar *filename = NULL;
+ int response;
+
+ if (job->skip_all_error)
+ {
+ g_error_free (error);
+ goto out;
+ }
+
+ basename = get_basename (src);
+ if (copy_job->is_move)
+ {
+ primary = g_strdup_printf (_("Error while moving “%s”."), basename);
+ }
+ else
+ {
+ primary = g_strdup_printf (_("Error while copying “%s”."), basename);
+ }
+ filename = get_truncated_parse_name (dest_dir);
+ secondary = g_strdup_printf (_("Could not remove the already existing file "
+ "with the same name in %s."),
+ filename);
+ details = error->message;
+
+ /* setting TRUE on show_all here, as we could have
+ * another error on the same file later.
+ */
+ response = run_warning (job,
+ primary,
+ secondary,
+ details,
+ TRUE,
+ CANCEL, SKIP_ALL, SKIP,
+ NULL);
+
+ g_error_free (error);
+
+ if (response == 0 || response == GTK_RESPONSE_DELETE_EVENT)
+ {
+ abort_job (job);
+ }
+ else if (response == 1) /* skip all */
+ {
+ job->skip_all_error = TRUE;
+ }
+ else if (response == 2) /* skip */
+ { /* do nothing */
+ }
+ else
+ {
+ g_assert_not_reached ();
+ }
+ goto out;
+ }
+ if (error)
+ {
+ g_error_free (error);
+ error = NULL;
+ }
+ nautilus_file_changes_queue_file_removed (dest);
+ }
+
+ if (is_merge)
+ {
+ /* On merge we now write in the target directory, which may not
+ * be in the same directory as the source, even if the parent is
+ * (if the merged directory is a mountpoint). This could cause
+ * problems as we then don't transcode filenames.
+ * We just set same_fs to FALSE which is safe but a bit slower. */
+ same_fs = FALSE;
+ }
+
+ if (!copy_move_directory (copy_job, src, &dest, same_fs,
+ would_recurse, dest_fs_type,
+ source_info, transfer_info,
+ debuting_files, skipped_file,
+ readonly_source_fs))
+ {
+ /* destination changed, since it was an invalid file name */
+ g_assert (*dest_fs_type != NULL);
+ handled_invalid_filename = TRUE;
+ goto retry;
+ }
+
+ g_object_unref (dest);
+ return;
+ }
+ else if (IS_IO_ERROR (error, CANCELLED))
+ {
+ g_error_free (error);
+ }
+ /* Other error */
+ else
+ {
+ g_autofree gchar *basename = NULL;
+ g_autofree gchar *filename = NULL;
+ int response;
+
+ if (job->skip_all_error)
+ {
+ g_error_free (error);
+ goto out;
+ }
+ basename = get_basename (src);
+ primary = g_strdup_printf (_("Error while copying “%s”."), basename);
+ filename = get_truncated_parse_name (dest_dir);
+ secondary = g_strdup_printf (_("There was an error copying the file into %s."),
+ filename);
+ details = error->message;
+
+ response = run_cancel_or_skip_warning (job,
+ primary,
+ secondary,
+ details,
+ source_info->num_files,
+ source_info->num_files - transfer_info->num_files);
+
+ g_error_free (error);
+
+ if (response == 0 || response == GTK_RESPONSE_DELETE_EVENT)
+ {
+ abort_job (job);
+ }
+ else if (response == 1) /* skip all */
+ {
+ job->skip_all_error = TRUE;
+ }
+ else if (response == 2) /* skip */
+ { /* do nothing */
+ }
+ else
+ {
+ g_assert_not_reached ();
+ }
+ }
+out:
+ *skipped_file = TRUE; /* Or aborted, but same-same */
+ g_object_unref (dest);
+}
+
+static void
+copy_files (CopyMoveJob *job,
+ const char *dest_fs_id,
+ SourceInfo *source_info,
+ TransferInfo *transfer_info)
+{
+ CommonJob *common;
+ GList *l;
+ GFile *src;
+ gboolean same_fs;
+ int i;
+ gboolean skipped_file;
+ gboolean unique_names;
+ GFile *dest;
+ GFile *source_dir;
+ char *dest_fs_type;
+ GFileInfo *inf;
+ gboolean readonly_source_fs;
+
+ dest_fs_type = NULL;
+ readonly_source_fs = FALSE;
+
+ common = &job->common;
+
+ report_copy_progress (job, source_info, transfer_info);
+
+ /* Query the source dir, not the file because if it's a symlink we'll follow it */
+ source_dir = g_file_get_parent ((GFile *) job->files->data);
+ if (source_dir)
+ {
+ inf = g_file_query_filesystem_info (source_dir, "filesystem::readonly", NULL, NULL);
+ if (inf != NULL)
+ {
+ readonly_source_fs = g_file_info_get_attribute_boolean (inf, "filesystem::readonly");
+ g_object_unref (inf);
+ }
+ g_object_unref (source_dir);
+ }
+
+ unique_names = (job->destination == NULL);
+ i = 0;
+ for (l = job->files;
+ l != NULL && !job_aborted (common);
+ l = l->next)
+ {
+ src = l->data;
+
+ same_fs = FALSE;
+ if (dest_fs_id)
+ {
+ same_fs = has_fs_id (src, dest_fs_id);
+ }
+
+ if (job->destination)
+ {
+ dest = g_object_ref (job->destination);
+ }
+ else
+ {
+ dest = g_file_get_parent (src);
+ }
+ if (dest)
+ {
+ skipped_file = FALSE;
+ copy_move_file (job, src, dest,
+ same_fs, unique_names,
+ &dest_fs_type,
+ source_info, transfer_info,
+ job->debuting_files,
+ FALSE, &skipped_file,
+ readonly_source_fs);
+ g_object_unref (dest);
+
+ if (skipped_file)
+ {
+ source_info_remove_file_from_count (src, common, source_info);
+ report_copy_progress (job, source_info, transfer_info);
+ }
+ }
+ i++;
+ }
+
+ g_free (dest_fs_type);
+}
+
+static void
+copy_task_done (GObject *source_object,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ CopyMoveJob *job;
+
+ job = user_data;
+ if (job->done_callback)
+ {
+ job->done_callback (job->debuting_files,
+ !job_aborted ((CommonJob *) job),
+ job->done_callback_data);
+ }
+
+ g_list_free_full (job->files, g_object_unref);
+ if (job->destination)
+ {
+ g_object_unref (job->destination);
+ }
+ g_hash_table_unref (job->debuting_files);
+ g_free (job->target_name);
+
+ g_clear_object (&job->fake_display_source);
+
+ finalize_common ((CommonJob *) job);
+
+ nautilus_file_changes_consume_changes (TRUE);
+}
+
+static CopyMoveJob *
+copy_job_setup (GList *files,
+ GFile *target_dir,
+ GtkWindow *parent_window,
+ NautilusFileOperationsDBusData *dbus_data,
+ NautilusCopyCallback done_callback,
+ gpointer done_callback_data)
+{
+ CopyMoveJob *job;
+
+ job = op_job_new (CopyMoveJob, parent_window, dbus_data);
+ job->done_callback = done_callback;
+ job->done_callback_data = done_callback_data;
+ job->files = g_list_copy_deep (files, (GCopyFunc) g_object_ref, NULL);
+ job->destination = g_object_ref (target_dir);
+ /* Need to indicate the destination for the operation notification open
+ * button. */
+ nautilus_progress_info_set_destination (((CommonJob *) job)->progress, target_dir);
+ job->debuting_files = g_hash_table_new_full (g_file_hash, (GEqualFunc) g_file_equal, g_object_unref, NULL);
+
+ return job;
+}
+
+static void
+nautilus_file_operations_copy (GTask *task,
+ gpointer source_object,
+ gpointer task_data,
+ GCancellable *cancellable)
+{
+ CopyMoveJob *job;
+ CommonJob *common;
+ g_auto (SourceInfo) source_info = SOURCE_INFO_INIT;
+ TransferInfo transfer_info;
+ g_autofree char *dest_fs_id = NULL;
+ GFile *dest;
+
+ job = task_data;
+ common = &job->common;
+
+ if (g_strcmp0 (g_getenv ("RUNNING_TESTS"), "TRUE"))
+ {
+ inhibit_power_manager ((CommonJob *) job, _("Copying Files"));
+ }
+
+ if (!nautilus_file_undo_manager_is_operating ())
+ {
+ g_autoptr (GFile) src_dir = NULL;
+
+ src_dir = g_file_get_parent (job->files->data);
+ /* In the case of duplicate, the undo_info is already set, so we don't want to
+ * overwrite it wrongfully.
+ */
+ if (job->common.undo_info == NULL)
+ {
+ job->common.undo_info = nautilus_file_undo_info_ext_new (NAUTILUS_FILE_UNDO_OP_COPY,
+ g_list_length (job->files),
+ src_dir, job->destination);
+ }
+ }
+
+ nautilus_progress_info_start (job->common.progress);
+
+ scan_sources (job->files,
+ &source_info,
+ common,
+ OP_KIND_COPY);
+ if (job_aborted (common))
+ {
+ return;
+ }
+
+ if (job->destination)
+ {
+ dest = g_object_ref (job->destination);
+ }
+ else
+ {
+ /* Duplication, no dest,
+ * use source for free size, etc
+ */
+ dest = g_file_get_parent (job->files->data);
+ }
+
+ verify_destination (&job->common,
+ dest,
+ &dest_fs_id,
+ source_info.num_bytes);
+ g_object_unref (dest);
+ if (job_aborted (common))
+ {
+ return;
+ }
+
+ g_timer_start (job->common.time);
+
+ memset (&transfer_info, 0, sizeof (transfer_info));
+ copy_files (job,
+ dest_fs_id,
+ &source_info, &transfer_info);
+}
+
+void
+nautilus_file_operations_copy_sync (GList *files,
+ GFile *target_dir)
+{
+ GTask *task;
+ CopyMoveJob *job;
+
+ job = copy_job_setup (files,
+ target_dir,
+ NULL,
+ NULL,
+ NULL,
+ NULL);
+
+ task = g_task_new (NULL, job->common.cancellable, NULL, job);
+ g_task_set_task_data (task, job, NULL);
+ g_task_run_in_thread_sync (task, nautilus_file_operations_copy);
+ g_object_unref (task);
+ /* Since g_task_run_in_thread_sync doesn't work with callbacks (in this case not reaching
+ * copy_task_done) we need to set up the undo information ourselves.
+ */
+ copy_task_done (NULL, NULL, job);
+}
+
+void
+nautilus_file_operations_copy_async (GList *files,
+ GFile *target_dir,
+ GtkWindow *parent_window,
+ NautilusFileOperationsDBusData *dbus_data,
+ NautilusCopyCallback done_callback,
+ gpointer done_callback_data)
+{
+ GTask *task;
+ CopyMoveJob *job;
+
+ job = copy_job_setup (files,
+ target_dir,
+ parent_window,
+ dbus_data,
+ done_callback,
+ done_callback_data);
+
+ task = g_task_new (NULL, job->common.cancellable, copy_task_done, job);
+ g_task_set_task_data (task, job, NULL);
+ g_task_run_in_thread (task, nautilus_file_operations_copy);
+ g_object_unref (task);
+}
+
+static void
+report_preparing_move_progress (CopyMoveJob *move_job,
+ int total,
+ int left)
+{
+ CommonJob *job;
+ g_autofree gchar *basename = NULL;
+
+ job = (CommonJob *) move_job;
+ basename = get_basename (move_job->destination);
+
+ nautilus_progress_info_take_status (job->progress,
+ g_strdup_printf (_("Preparing to move to “%s”"),
+ basename));
+
+ nautilus_progress_info_take_details (job->progress,
+ g_strdup_printf (ngettext ("Preparing to move %'d file",
+ "Preparing to move %'d files",
+ left),
+ left));
+
+ nautilus_progress_info_pulse_progress (job->progress);
+}
+
+typedef struct
+{
+ GFile *file;
+ gboolean overwrite;
+} MoveFileCopyFallback;
+
+static MoveFileCopyFallback *
+move_copy_file_callback_new (GFile *file,
+ gboolean overwrite)
+{
+ MoveFileCopyFallback *fallback;
+
+ fallback = g_new (MoveFileCopyFallback, 1);
+ fallback->file = file;
+ fallback->overwrite = overwrite;
+
+ return fallback;
+}
+
+static GList *
+get_files_from_fallbacks (GList *fallbacks)
+{
+ MoveFileCopyFallback *fallback;
+ GList *res, *l;
+
+ res = NULL;
+ for (l = fallbacks; l != NULL; l = l->next)
+ {
+ fallback = l->data;
+ res = g_list_prepend (res, fallback->file);
+ }
+ return g_list_reverse (res);
+}
+
+static void
+move_file_prepare (CopyMoveJob *move_job,
+ GFile *src,
+ GFile *dest_dir,
+ gboolean same_fs,
+ char **dest_fs_type,
+ GHashTable *debuting_files,
+ GList **fallback_files,
+ int files_left)
+{
+ GFile *dest, *new_dest;
+ g_autofree gchar *dest_uri = NULL;
+ GError *error;
+ CommonJob *job;
+ gboolean overwrite;
+ char *primary, *secondary, *details;
+ GFileCopyFlags flags;
+ MoveFileCopyFallback *fallback;
+ gboolean handled_invalid_filename;
+
+ overwrite = FALSE;
+ handled_invalid_filename = *dest_fs_type != NULL;
+
+ job = (CommonJob *) move_job;
+
+ if (g_file_has_uri_scheme (src, "google-drive") &&
+ g_file_has_uri_scheme (dest_dir, "google-drive"))
+ {
+ dest = get_target_file_from_source_display_name (move_job, src, dest_dir);
+ if (dest == NULL)
+ {
+ return;
+ }
+ }
+ else
+ {
+ dest = get_target_file (src, dest_dir, *dest_fs_type, same_fs);
+ }
+
+
+ /* Don't allow recursive move/copy into itself.
+ * (We would get a file system error if we proceeded but it is nicer to
+ * detect and report it at this level) */
+ if (test_dir_is_parent (dest_dir, src))
+ {
+ int response;
+
+ if (job->skip_all_error)
+ {
+ goto out;
+ }
+
+ /* the run_warning() frees all strings passed in automatically */
+ primary = move_job->is_move ? g_strdup (_("You cannot move a folder into itself."))
+ : g_strdup (_("You cannot copy a folder into itself."));
+ secondary = g_strdup (_("The destination folder is inside the source folder."));
+
+ response = run_warning (job,
+ primary,
+ secondary,
+ NULL,
+ files_left > 1,
+ CANCEL, SKIP_ALL, SKIP,
+ NULL);
+
+ if (response == 0 || response == GTK_RESPONSE_DELETE_EVENT)
+ {
+ abort_job (job);
+ }
+ else if (response == 1) /* skip all */
+ {
+ job->skip_all_error = TRUE;
+ }
+ else if (response == 2) /* skip */
+ { /* do nothing */
+ }
+ else
+ {
+ g_assert_not_reached ();
+ }
+
+ goto out;
+ }
+
+ /* Don't allow moving over the source or one of the parents of the source.
+ */
+ if (test_dir_is_parent (src, dest))
+ {
+ int response;
+
+ if (job->skip_all_error)
+ {
+ goto out;
+ }
+
+ /* the run_warning() frees all strings passed in automatically */
+ primary = move_job->is_move ? g_strdup (_("You cannot move a file over itself."))
+ : g_strdup (_("You cannot copy a file over itself."));
+ secondary = g_strdup (_("The source file would be overwritten by the destination."));
+
+ response = run_warning (job,
+ primary,
+ secondary,
+ NULL,
+ files_left > 1,
+ CANCEL, SKIP_ALL, SKIP,
+ NULL);
+
+ if (response == 0 || response == GTK_RESPONSE_DELETE_EVENT)
+ {
+ abort_job (job);
+ }
+ else if (response == 1) /* skip all */
+ {
+ job->skip_all_error = TRUE;
+ }
+ else if (response == 2) /* skip */
+ {
+ /* do nothing */
+ }
+ else
+ {
+ g_assert_not_reached ();
+ }
+
+ goto out;
+ }
+
+
+retry:
+
+ flags = G_FILE_COPY_NOFOLLOW_SYMLINKS | G_FILE_COPY_NO_FALLBACK_FOR_MOVE;
+ if (overwrite)
+ {
+ flags |= G_FILE_COPY_OVERWRITE;
+ }
+
+ error = NULL;
+ if (g_file_move (src, dest,
+ flags,
+ job->cancellable,
+ NULL,
+ NULL,
+ &error))
+ {
+ if (debuting_files)
+ {
+ g_hash_table_replace (debuting_files, g_object_ref (dest), GINT_TO_POINTER (TRUE));
+ }
+
+ nautilus_file_changes_queue_file_moved (src, dest);
+
+ dest_uri = g_file_get_uri (dest);
+
+ if (job->undo_info != NULL)
+ {
+ nautilus_file_undo_info_ext_add_origin_target_pair (NAUTILUS_FILE_UNDO_INFO_EXT (job->undo_info),
+ src, dest);
+ }
+
+ return;
+ }
+
+ if (IS_IO_ERROR (error, INVALID_FILENAME) &&
+ !handled_invalid_filename)
+ {
+ g_error_free (error);
+
+ handled_invalid_filename = TRUE;
+
+ g_assert (*dest_fs_type == NULL);
+ *dest_fs_type = query_fs_type (dest_dir, job->cancellable);
+
+ new_dest = get_target_file (src, dest_dir, *dest_fs_type, same_fs);
+ if (!g_file_equal (dest, new_dest))
+ {
+ g_object_unref (dest);
+ dest = new_dest;
+ goto retry;
+ }
+ else
+ {
+ g_object_unref (new_dest);
+ }
+ }
+ /* Conflict */
+ else if (!overwrite &&
+ IS_IO_ERROR (error, EXISTS))
+ {
+ gboolean source_is_directory;
+ gboolean destination_is_directory;
+ gboolean is_merge;
+ FileConflictResponse *response;
+
+ source_is_directory = is_dir (src);
+ destination_is_directory = is_dir (dest);
+
+ g_error_free (error);
+
+ is_merge = FALSE;
+ if (source_is_directory && destination_is_directory)
+ {
+ is_merge = TRUE;
+ }
+ else if (!source_is_directory && destination_is_directory)
+ {
+ /* Any sane backend will fail with G_IO_ERROR_IS_DIRECTORY. */
+ overwrite = TRUE;
+ goto retry;
+ }
+
+ if ((is_merge && job->merge_all) ||
+ (!is_merge && job->replace_all))
+ {
+ overwrite = TRUE;
+ goto retry;
+ }
+
+ if (job->skip_all_conflict)
+ {
+ goto out;
+ }
+
+ response = handle_copy_move_conflict (job, src, dest, dest_dir);
+
+ if (response->id == GTK_RESPONSE_CANCEL ||
+ response->id == GTK_RESPONSE_DELETE_EVENT)
+ {
+ file_conflict_response_free (response);
+ abort_job (job);
+ }
+ else if (response->id == CONFLICT_RESPONSE_SKIP)
+ {
+ if (response->apply_to_all)
+ {
+ job->skip_all_conflict = TRUE;
+ }
+ file_conflict_response_free (response);
+ }
+ else if (response->id == CONFLICT_RESPONSE_REPLACE) /* merge/replace */
+ {
+ if (response->apply_to_all)
+ {
+ if (is_merge)
+ {
+ job->merge_all = TRUE;
+ }
+ else
+ {
+ job->replace_all = TRUE;
+ }
+ }
+ overwrite = TRUE;
+ file_conflict_response_free (response);
+ goto retry;
+ }
+ else if (response->id == CONFLICT_RESPONSE_RENAME)
+ {
+ g_object_unref (dest);
+ dest = get_target_file_for_display_name (dest_dir,
+ response->new_name);
+ file_conflict_response_free (response);
+ goto retry;
+ }
+ else
+ {
+ g_assert_not_reached ();
+ }
+ }
+ else if (IS_IO_ERROR (error, WOULD_RECURSE) ||
+ IS_IO_ERROR (error, WOULD_MERGE) ||
+ IS_IO_ERROR (error, NOT_SUPPORTED))
+ {
+ g_error_free (error);
+
+ fallback = move_copy_file_callback_new (src,
+ overwrite);
+ *fallback_files = g_list_prepend (*fallback_files, fallback);
+ }
+ else if (IS_IO_ERROR (error, CANCELLED))
+ {
+ g_error_free (error);
+ }
+ /* Other error */
+ else
+ {
+ g_autofree gchar *basename = NULL;
+ g_autofree gchar *filename = NULL;
+ int response;
+
+ if (job->skip_all_error)
+ {
+ g_error_free (error);
+ goto out;
+ }
+ basename = get_basename (src);
+ primary = g_strdup_printf (_("Error while moving “%s”."), basename);
+ filename = get_truncated_parse_name (dest_dir);
+ secondary = g_strdup_printf (_("There was an error moving the file into %s."),
+ filename);
+
+ details = error->message;
+
+ response = run_warning (job,
+ primary,
+ secondary,
+ details,
+ files_left > 1,
+ CANCEL, SKIP_ALL, SKIP,
+ NULL);
+
+ g_error_free (error);
+
+ if (response == 0 || response == GTK_RESPONSE_DELETE_EVENT)
+ {
+ abort_job (job);
+ }
+ else if (response == 1) /* skip all */
+ {
+ job->skip_all_error = TRUE;
+ }
+ else if (response == 2) /* skip */
+ { /* do nothing */
+ }
+ else
+ {
+ g_assert_not_reached ();
+ }
+ }
+
+out:
+ g_object_unref (dest);
+}
+
+static void
+move_files_prepare (CopyMoveJob *job,
+ const char *dest_fs_id,
+ char **dest_fs_type,
+ GList **fallbacks)
+{
+ CommonJob *common;
+ GList *l;
+ GFile *src;
+ gboolean same_fs;
+ int i;
+ int total, left;
+
+ common = &job->common;
+
+ total = left = g_list_length (job->files);
+
+ report_preparing_move_progress (job, total, left);
+
+ i = 0;
+ for (l = job->files;
+ l != NULL && !job_aborted (common);
+ l = l->next)
+ {
+ src = l->data;
+
+ same_fs = FALSE;
+ if (dest_fs_id)
+ {
+ same_fs = has_fs_id (src, dest_fs_id);
+ }
+
+ move_file_prepare (job, src, job->destination,
+ same_fs, dest_fs_type,
+ job->debuting_files,
+ fallbacks,
+ left);
+ report_preparing_move_progress (job, total, --left);
+ i++;
+ }
+
+ *fallbacks = g_list_reverse (*fallbacks);
+}
+
+static void
+move_files (CopyMoveJob *job,
+ GList *fallbacks,
+ const char *dest_fs_id,
+ char **dest_fs_type,
+ SourceInfo *source_info,
+ TransferInfo *transfer_info)
+{
+ CommonJob *common;
+ GList *l;
+ GFile *src;
+ gboolean same_fs;
+ int i;
+ gboolean skipped_file;
+ MoveFileCopyFallback *fallback;
+ common = &job->common;
+
+ report_copy_progress (job, source_info, transfer_info);
+
+ i = 0;
+ for (l = fallbacks;
+ l != NULL && !job_aborted (common);
+ l = l->next)
+ {
+ fallback = l->data;
+ src = fallback->file;
+
+ same_fs = FALSE;
+ if (dest_fs_id)
+ {
+ same_fs = has_fs_id (src, dest_fs_id);
+ }
+
+ /* Set overwrite to true, as the user has
+ * selected overwrite on all toplevel items */
+ skipped_file = FALSE;
+ copy_move_file (job, src, job->destination,
+ same_fs, FALSE, dest_fs_type,
+ source_info, transfer_info,
+ job->debuting_files,
+ fallback->overwrite, &skipped_file, FALSE);
+ i++;
+
+ if (skipped_file)
+ {
+ source_info_remove_file_from_count (src, common, source_info);
+ report_copy_progress (job, source_info, transfer_info);
+ }
+ }
+}
+
+
+static void
+move_task_done (GObject *source_object,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ CopyMoveJob *job;
+
+ job = user_data;
+ if (job->done_callback)
+ {
+ job->done_callback (job->debuting_files,
+ !job_aborted ((CommonJob *) job),
+ job->done_callback_data);
+ }
+
+ g_list_free_full (job->files, g_object_unref);
+ g_object_unref (job->destination);
+ g_hash_table_unref (job->debuting_files);
+
+ finalize_common ((CommonJob *) job);
+
+ nautilus_file_changes_consume_changes (TRUE);
+}
+
+static CopyMoveJob *
+move_job_setup (GList *files,
+ GFile *target_dir,
+ GtkWindow *parent_window,
+ NautilusFileOperationsDBusData *dbus_data,
+ NautilusCopyCallback done_callback,
+ gpointer done_callback_data)
+{
+ CopyMoveJob *job;
+
+ job = op_job_new (CopyMoveJob, parent_window, dbus_data);
+ job->is_move = TRUE;
+ job->done_callback = done_callback;
+ job->done_callback_data = done_callback_data;
+ job->files = g_list_copy_deep (files, (GCopyFunc) g_object_ref, NULL);
+ job->destination = g_object_ref (target_dir);
+
+ /* Need to indicate the destination for the operation notification open
+ * button. */
+ nautilus_progress_info_set_destination (((CommonJob *) job)->progress, job->destination);
+ job->debuting_files = g_hash_table_new_full (g_file_hash, (GEqualFunc) g_file_equal, g_object_unref, NULL);
+
+ return job;
+}
+
+void
+nautilus_file_operations_move_sync (GList *files,
+ GFile *target_dir)
+{
+ GTask *task;
+ CopyMoveJob *job;
+
+ job = move_job_setup (files, target_dir, NULL, NULL, NULL, NULL);
+ task = g_task_new (NULL, job->common.cancellable, NULL, job);
+ g_task_set_task_data (task, job, NULL);
+ g_task_run_in_thread_sync (task, nautilus_file_operations_move);
+ g_object_unref (task);
+ /* Since g_task_run_in_thread_sync doesn't work with callbacks (in this case not reaching
+ * move_task_done) we need to set up the undo information ourselves.
+ */
+ move_task_done (NULL, NULL, job);
+}
+
+void
+nautilus_file_operations_move_async (GList *files,
+ GFile *target_dir,
+ GtkWindow *parent_window,
+ NautilusFileOperationsDBusData *dbus_data,
+ NautilusCopyCallback done_callback,
+ gpointer done_callback_data)
+{
+ GTask *task;
+ CopyMoveJob *job;
+
+ job = move_job_setup (files,
+ target_dir,
+ parent_window,
+ dbus_data,
+ done_callback,
+ done_callback_data);
+
+ task = g_task_new (NULL, job->common.cancellable, move_task_done, job);
+ g_task_set_task_data (task, job, NULL);
+ g_task_run_in_thread (task, nautilus_file_operations_move);
+ g_object_unref (task);
+}
+
+static void
+nautilus_file_operations_move (GTask *task,
+ gpointer source_object,
+ gpointer task_data,
+ GCancellable *cancellable)
+{
+ CopyMoveJob *job;
+ CommonJob *common;
+ GList *fallbacks;
+ g_auto (SourceInfo) source_info = SOURCE_INFO_INIT;
+ TransferInfo transfer_info;
+ g_autofree char *dest_fs_id = NULL;
+ g_autofree char *dest_fs_type = NULL;
+ GList *fallback_files;
+
+ job = task_data;
+
+ /* Since we never initiate the app (in the case of
+ * testing), we can't inhibit its power manager.
+ * This would terminate the testing. So we avoid
+ * doing this by checking if the RUNNING_TESTS
+ * environment variable is set to "TRUE".
+ */
+ if (g_strcmp0 (g_getenv ("RUNNING_TESTS"), "TRUE"))
+ {
+ inhibit_power_manager ((CommonJob *) job, _("Moving Files"));
+ }
+
+ if (!nautilus_file_undo_manager_is_operating ())
+ {
+ g_autoptr (GFile) src_dir = NULL;
+
+ src_dir = g_file_get_parent ((job->files)->data);
+
+ if (g_file_has_uri_scheme (g_list_first (job->files)->data, "trash"))
+ {
+ job->common.undo_info = nautilus_file_undo_info_ext_new (NAUTILUS_FILE_UNDO_OP_RESTORE_FROM_TRASH,
+ g_list_length (job->files),
+ src_dir, job->destination);
+ }
+ else
+ {
+ job->common.undo_info = nautilus_file_undo_info_ext_new (NAUTILUS_FILE_UNDO_OP_MOVE,
+ g_list_length (job->files),
+ src_dir, job->destination);
+ }
+ }
+
+ common = &job->common;
+
+ nautilus_progress_info_start (job->common.progress);
+
+ fallbacks = NULL;
+
+ verify_destination (&job->common,
+ job->destination,
+ &dest_fs_id,
+ -1);
+ if (job_aborted (common))
+ {
+ goto aborted;
+ }
+
+ /* This moves all files that we can do without copy + delete */
+ move_files_prepare (job, dest_fs_id, &dest_fs_type, &fallbacks);
+ if (job_aborted (common))
+ {
+ goto aborted;
+ }
+
+ if (fallbacks == NULL)
+ {
+ gint total;
+
+ total = g_list_length (job->files);
+
+ memset (&source_info, 0, sizeof (source_info));
+ source_info.num_files = total;
+ memset (&transfer_info, 0, sizeof (transfer_info));
+ transfer_info.num_files = total;
+ report_copy_progress (job, &source_info, &transfer_info);
+
+ return;
+ }
+
+ /* The rest we need to do deep copy + delete behind on,
+ * so scan for size */
+
+ fallback_files = get_files_from_fallbacks (fallbacks);
+ scan_sources (fallback_files,
+ &source_info,
+ common,
+ OP_KIND_MOVE);
+
+ g_list_free (fallback_files);
+
+ if (job_aborted (common))
+ {
+ goto aborted;
+ }
+
+ verify_destination (&job->common,
+ job->destination,
+ NULL,
+ source_info.num_bytes);
+ if (job_aborted (common))
+ {
+ goto aborted;
+ }
+
+ memset (&transfer_info, 0, sizeof (transfer_info));
+ move_files (job,
+ fallbacks,
+ dest_fs_id, &dest_fs_type,
+ &source_info, &transfer_info);
+
+aborted:
+ g_list_free_full (fallbacks, g_free);
+}
+
+static void
+report_preparing_link_progress (CopyMoveJob *link_job,
+ int total,
+ int left)
+{
+ CommonJob *job;
+ g_autofree gchar *basename = NULL;
+
+ job = (CommonJob *) link_job;
+ basename = get_basename (link_job->destination);
+ nautilus_progress_info_take_status (job->progress,
+ g_strdup_printf (_("Creating links in “%s”"),
+ basename));
+
+ nautilus_progress_info_take_details (job->progress,
+ g_strdup_printf (ngettext ("Making link to %'d file",
+ "Making links to %'d files",
+ left),
+ left));
+
+ nautilus_progress_info_set_progress (job->progress, left, total);
+}
+
+static char *
+get_abs_path_for_symlink (GFile *file,
+ GFile *destination)
+{
+ GFile *root, *parent;
+ char *relative, *abs;
+
+ if (g_file_is_native (file) || g_file_is_native (destination))
+ {
+ return g_file_get_path (file);
+ }
+
+ root = g_object_ref (file);
+ while ((parent = g_file_get_parent (root)) != NULL)
+ {
+ g_object_unref (root);
+ root = parent;
+ }
+
+ relative = g_file_get_relative_path (root, file);
+ g_object_unref (root);
+ abs = g_strconcat ("/", relative, NULL);
+ g_free (relative);
+ return abs;
+}
+
+
+static void
+link_file (CopyMoveJob *job,
+ GFile *src,
+ GFile *dest_dir,
+ char **dest_fs_type,
+ GHashTable *debuting_files,
+ int files_left)
+{
+ GFile *src_dir;
+ GFile *new_dest;
+ g_autoptr (GFile) dest = NULL;
+ g_autofree gchar *dest_uri = NULL;
+ int count;
+ char *path;
+ gboolean not_local;
+ GError *error;
+ CommonJob *common;
+ char *primary, *secondary, *details;
+ int response;
+ gboolean handled_invalid_filename;
+
+ common = (CommonJob *) job;
+
+ count = 0;
+
+ src_dir = g_file_get_parent (src);
+ if (g_file_equal (src_dir, dest_dir))
+ {
+ count = 1;
+ }
+ g_object_unref (src_dir);
+
+ handled_invalid_filename = *dest_fs_type != NULL;
+
+ dest = get_target_file_for_link (src, dest_dir, *dest_fs_type, count);
+
+retry:
+ error = NULL;
+ not_local = FALSE;
+
+ path = get_abs_path_for_symlink (src, dest);
+ if (path == NULL)
+ {
+ not_local = TRUE;
+ }
+ else if (g_file_make_symbolic_link (dest,
+ path,
+ common->cancellable,
+ &error))
+ {
+ if (common->undo_info != NULL)
+ {
+ nautilus_file_undo_info_ext_add_origin_target_pair (NAUTILUS_FILE_UNDO_INFO_EXT (common->undo_info),
+ src, dest);
+ }
+
+ g_free (path);
+ if (debuting_files)
+ {
+ g_hash_table_replace (debuting_files, g_object_ref (dest), GINT_TO_POINTER (TRUE));
+ }
+
+ nautilus_file_changes_queue_file_added (dest);
+ dest_uri = g_file_get_uri (dest);
+
+ return;
+ }
+ g_free (path);
+
+ if (error != NULL &&
+ IS_IO_ERROR (error, INVALID_FILENAME) &&
+ !handled_invalid_filename)
+ {
+ handled_invalid_filename = TRUE;
+
+ g_assert (*dest_fs_type == NULL);
+ *dest_fs_type = query_fs_type (dest_dir, common->cancellable);
+
+ new_dest = get_target_file_for_link (src, dest_dir, *dest_fs_type, count);
+
+ if (!g_file_equal (dest, new_dest))
+ {
+ g_object_unref (dest);
+ dest = new_dest;
+ g_error_free (error);
+
+ goto retry;
+ }
+ else
+ {
+ g_object_unref (new_dest);
+ }
+ }
+ /* Conflict */
+ if (error != NULL && IS_IO_ERROR (error, EXISTS))
+ {
+ g_object_unref (dest);
+ dest = get_target_file_for_link (src, dest_dir, *dest_fs_type, count++);
+ g_error_free (error);
+ goto retry;
+ }
+ else if (error != NULL && IS_IO_ERROR (error, CANCELLED))
+ {
+ g_error_free (error);
+ }
+ /* Other error */
+ else if (error != NULL)
+ {
+ g_autofree gchar *basename = NULL;
+
+ if (common->skip_all_error)
+ {
+ return;
+ }
+ basename = get_basename (src);
+ primary = g_strdup_printf (_("Error while creating link to %s."),
+ basename);
+ if (not_local)
+ {
+ secondary = g_strdup (_("Symbolic links only supported for local files"));
+ details = NULL;
+ }
+ else if (IS_IO_ERROR (error, NOT_SUPPORTED))
+ {
+ secondary = g_strdup (_("The target doesn’t support symbolic links."));
+ details = NULL;
+ }
+ else
+ {
+ g_autofree gchar *filename = NULL;
+
+ filename = get_truncated_parse_name (dest_dir);
+ secondary = g_strdup_printf (_("There was an error creating the symlink in %s."),
+ filename);
+ details = error->message;
+ }
+
+ response = run_warning (common,
+ primary,
+ secondary,
+ details,
+ files_left > 1,
+ CANCEL, SKIP_ALL, SKIP,
+ NULL);
+
+ if (error)
+ {
+ g_error_free (error);
+ }
+
+ if (response == 0 || response == GTK_RESPONSE_DELETE_EVENT)
+ {
+ abort_job (common);
+ }
+ else if (response == 1) /* skip all */
+ {
+ common->skip_all_error = TRUE;
+ }
+ else if (response == 2) /* skip */
+ { /* do nothing */
+ }
+ else
+ {
+ g_assert_not_reached ();
+ }
+ }
+}
+
+static void
+link_task_done (GObject *source_object,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ CopyMoveJob *job;
+
+ job = user_data;
+ if (job->done_callback)
+ {
+ job->done_callback (job->debuting_files,
+ !job_aborted ((CommonJob *) job),
+ job->done_callback_data);
+ }
+
+ g_list_free_full (job->files, g_object_unref);
+ g_object_unref (job->destination);
+ g_hash_table_unref (job->debuting_files);
+
+ finalize_common ((CommonJob *) job);
+
+ nautilus_file_changes_consume_changes (TRUE);
+}
+
+static void
+link_task_thread_func (GTask *task,
+ gpointer source_object,
+ gpointer task_data,
+ GCancellable *cancellable)
+{
+ CopyMoveJob *job;
+ CommonJob *common;
+ GFile *src;
+ g_autofree char *dest_fs_type = NULL;
+ int total, left;
+ int i;
+ GList *l;
+
+ job = task_data;
+ common = &job->common;
+
+ nautilus_progress_info_start (job->common.progress);
+
+ verify_destination (&job->common,
+ job->destination,
+ NULL,
+ -1);
+ if (job_aborted (common))
+ {
+ return;
+ }
+
+ total = left = g_list_length (job->files);
+
+ report_preparing_link_progress (job, total, left);
+
+ i = 0;
+ for (l = job->files;
+ l != NULL && !job_aborted (common);
+ l = l->next)
+ {
+ src = l->data;
+
+ link_file (job, src, job->destination,
+ &dest_fs_type, job->debuting_files,
+ left);
+ report_preparing_link_progress (job, total, --left);
+ i++;
+ }
+}
+
+void
+nautilus_file_operations_link (GList *files,
+ GFile *target_dir,
+ GtkWindow *parent_window,
+ NautilusFileOperationsDBusData *dbus_data,
+ NautilusCopyCallback done_callback,
+ gpointer done_callback_data)
+{
+ g_autoptr (GTask) task = NULL;
+ CopyMoveJob *job;
+
+ job = op_job_new (CopyMoveJob, parent_window, dbus_data);
+ job->done_callback = done_callback;
+ job->done_callback_data = done_callback_data;
+ job->files = g_list_copy_deep (files, (GCopyFunc) g_object_ref, NULL);
+ job->destination = g_object_ref (target_dir);
+ /* Need to indicate the destination for the operation notification open
+ * button. */
+ nautilus_progress_info_set_destination (((CommonJob *) job)->progress, target_dir);
+ job->debuting_files = g_hash_table_new_full (g_file_hash, (GEqualFunc) g_file_equal, g_object_unref, NULL);
+
+ if (!nautilus_file_undo_manager_is_operating ())
+ {
+ g_autoptr (GFile) src_dir = NULL;
+
+ src_dir = g_file_get_parent (files->data);
+ job->common.undo_info = nautilus_file_undo_info_ext_new (NAUTILUS_FILE_UNDO_OP_CREATE_LINK,
+ g_list_length (files),
+ src_dir, target_dir);
+ }
+
+ task = g_task_new (NULL, job->common.cancellable, link_task_done, job);
+ g_task_set_task_data (task, job, NULL);
+ g_task_run_in_thread (task, link_task_thread_func);
+}
+
+
+void
+nautilus_file_operations_duplicate (GList *files,
+ GtkWindow *parent_window,
+ NautilusFileOperationsDBusData *dbus_data,
+ NautilusCopyCallback done_callback,
+ gpointer done_callback_data)
+{
+ g_autoptr (GTask) task = NULL;
+ CopyMoveJob *job;
+ g_autoptr (GFile) parent = NULL;
+
+ job = op_job_new (CopyMoveJob, parent_window, dbus_data);
+ job->done_callback = done_callback;
+ job->done_callback_data = done_callback_data;
+ job->files = g_list_copy_deep (files, (GCopyFunc) g_object_ref, NULL);
+ job->destination = NULL;
+ /* Duplicate files doesn't have a destination, since is the same as source.
+ * For that set as destination the source parent folder */
+ parent = g_file_get_parent (files->data);
+ /* Need to indicate the destination for the operation notification open
+ * button. */
+ nautilus_progress_info_set_destination (((CommonJob *) job)->progress, parent);
+ job->debuting_files = g_hash_table_new_full (g_file_hash, (GEqualFunc) g_file_equal, g_object_unref, NULL);
+
+ if (!nautilus_file_undo_manager_is_operating ())
+ {
+ g_autoptr (GFile) src_dir = NULL;
+
+ src_dir = g_file_get_parent (files->data);
+ job->common.undo_info =
+ nautilus_file_undo_info_ext_new (NAUTILUS_FILE_UNDO_OP_DUPLICATE,
+ g_list_length (files),
+ src_dir, src_dir);
+ }
+
+ task = g_task_new (NULL, job->common.cancellable, copy_task_done, job);
+ g_task_set_task_data (task, job, NULL);
+ g_task_run_in_thread (task, nautilus_file_operations_copy);
+}
+
+static void
+set_permissions_task_done (GObject *source_object,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ SetPermissionsJob *job;
+
+ job = user_data;
+
+ g_object_unref (job->file);
+
+ if (job->done_callback)
+ {
+ job->done_callback (!job_aborted ((CommonJob *) job),
+ job->done_callback_data);
+ }
+
+ finalize_common ((CommonJob *) job);
+}
+
+static void
+set_permissions_file (SetPermissionsJob *job,
+ GFile *file,
+ GFileInfo *info);
+
+static void
+set_permissions_contained_files (SetPermissionsJob *job,
+ GFile *file)
+{
+ CommonJob *common;
+ GFileEnumerator *enumerator;
+
+ common = (CommonJob *) job;
+
+ enumerator = g_file_enumerate_children (file,
+ G_FILE_ATTRIBUTE_STANDARD_NAME ","
+ G_FILE_ATTRIBUTE_STANDARD_TYPE ","
+ G_FILE_ATTRIBUTE_UNIX_MODE,
+ G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
+ common->cancellable,
+ NULL);
+ if (enumerator)
+ {
+ GFileInfo *child_info;
+
+ while (!job_aborted (common) &&
+ (child_info = g_file_enumerator_next_file (enumerator, common->cancellable, NULL)) != NULL)
+ {
+ GFile *child;
+
+ child = g_file_get_child (file,
+ g_file_info_get_name (child_info));
+ set_permissions_file (job, child, child_info);
+ g_object_unref (child);
+ g_object_unref (child_info);
+ }
+ g_file_enumerator_close (enumerator, common->cancellable, NULL);
+ g_object_unref (enumerator);
+ }
+}
+
+static void
+set_permissions_file (SetPermissionsJob *job,
+ GFile *file,
+ GFileInfo *info)
+{
+ CommonJob *common;
+ gboolean free_info;
+ guint32 current;
+ guint32 value;
+ guint32 mask;
+
+ common = (CommonJob *) job;
+
+ nautilus_progress_info_pulse_progress (common->progress);
+
+ free_info = FALSE;
+ if (info == NULL)
+ {
+ free_info = TRUE;
+ info = g_file_query_info (file,
+ G_FILE_ATTRIBUTE_STANDARD_TYPE ","
+ G_FILE_ATTRIBUTE_UNIX_MODE,
+ G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
+ common->cancellable,
+ NULL);
+ /* Ignore errors */
+ if (info == NULL)
+ {
+ return;
+ }
+ }
+
+ if (g_file_info_get_file_type (info) == G_FILE_TYPE_DIRECTORY)
+ {
+ value = job->dir_permissions;
+ mask = job->dir_mask;
+ }
+ else
+ {
+ value = job->file_permissions;
+ mask = job->file_mask;
+ }
+
+
+ if (!job_aborted (common) &&
+ g_file_info_has_attribute (info, G_FILE_ATTRIBUTE_UNIX_MODE))
+ {
+ current = g_file_info_get_attribute_uint32 (info, G_FILE_ATTRIBUTE_UNIX_MODE);
+
+ if (common->undo_info != NULL)
+ {
+ nautilus_file_undo_info_rec_permissions_add_file (NAUTILUS_FILE_UNDO_INFO_REC_PERMISSIONS (common->undo_info),
+ file, current);
+ }
+
+ current = (current & ~mask) | value;
+
+ g_file_set_attribute_uint32 (file, G_FILE_ATTRIBUTE_UNIX_MODE,
+ current, G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
+ common->cancellable, NULL);
+ }
+
+ if (!job_aborted (common) &&
+ g_file_info_get_file_type (info) == G_FILE_TYPE_DIRECTORY)
+ {
+ set_permissions_contained_files (job, file);
+ }
+ if (free_info)
+ {
+ g_object_unref (info);
+ }
+}
+
+static void
+set_permissions_thread_func (GTask *task,
+ gpointer source_object,
+ gpointer task_data,
+ GCancellable *cancellable)
+{
+ SetPermissionsJob *job = task_data;
+ CommonJob *common;
+
+ common = (CommonJob *) job;
+
+ nautilus_progress_info_set_status (common->progress,
+ _("Setting permissions"));
+
+ nautilus_progress_info_start (job->common.progress);
+ set_permissions_contained_files (job, job->file);
+}
+
+void
+nautilus_file_set_permissions_recursive (const char *directory,
+ guint32 file_permissions,
+ guint32 file_mask,
+ guint32 dir_permissions,
+ guint32 dir_mask,
+ NautilusOpCallback callback,
+ gpointer callback_data)
+{
+ g_autoptr (GTask) task = NULL;
+ SetPermissionsJob *job;
+
+ job = op_job_new (SetPermissionsJob, NULL, NULL);
+ job->file = g_file_new_for_uri (directory);
+ job->file_permissions = file_permissions;
+ job->file_mask = file_mask;
+ job->dir_permissions = dir_permissions;
+ job->dir_mask = dir_mask;
+ job->done_callback = callback;
+ job->done_callback_data = callback_data;
+
+ if (!nautilus_file_undo_manager_is_operating ())
+ {
+ job->common.undo_info =
+ nautilus_file_undo_info_rec_permissions_new (job->file,
+ file_permissions, file_mask,
+ dir_permissions, dir_mask);
+ }
+
+ task = g_task_new (NULL, NULL, set_permissions_task_done, job);
+ g_task_set_task_data (task, job, NULL);
+ g_task_run_in_thread (task, set_permissions_thread_func);
+}
+
+static GList *
+location_list_from_uri_list (const GList *uris)
+{
+ const GList *l;
+ GList *files;
+ GFile *f;
+
+ files = NULL;
+ for (l = uris; l != NULL; l = l->next)
+ {
+ f = g_file_new_for_uri (l->data);
+ files = g_list_prepend (files, f);
+ }
+
+ return g_list_reverse (files);
+}
+
+typedef struct
+{
+ NautilusCopyCallback real_callback;
+ gpointer real_data;
+} MoveTrashCBData;
+
+static void
+callback_for_move_to_trash (GHashTable *debuting_uris,
+ gboolean user_cancelled,
+ MoveTrashCBData *data)
+{
+ if (data->real_callback)
+ {
+ data->real_callback (debuting_uris, !user_cancelled, data->real_data);
+ }
+ g_slice_free (MoveTrashCBData, data);
+}
+
+void
+nautilus_file_operations_copy_move (const GList *item_uris,
+ const char *target_dir,
+ GdkDragAction copy_action,
+ GtkWidget *parent_view,
+ NautilusFileOperationsDBusData *dbus_data,
+ NautilusCopyCallback done_callback,
+ gpointer done_callback_data)
+{
+ GList *locations;
+ GList *p;
+ GFile *dest, *src_dir;
+ GtkWindow *parent_window;
+ gboolean target_is_mapping;
+ gboolean have_nonmapping_source;
+
+ dest = NULL;
+ target_is_mapping = FALSE;
+ have_nonmapping_source = FALSE;
+
+ if (target_dir)
+ {
+ dest = g_file_new_for_uri (target_dir);
+ if (g_file_has_uri_scheme (dest, "burn"))
+ {
+ target_is_mapping = TRUE;
+ }
+ }
+
+ locations = location_list_from_uri_list (item_uris);
+
+ for (p = locations; p != NULL; p = p->next)
+ {
+ if (!g_file_has_uri_scheme ((GFile * ) p->data, "burn"))
+ {
+ have_nonmapping_source = TRUE;
+ }
+ }
+
+ if (target_is_mapping && have_nonmapping_source && copy_action == GDK_ACTION_MOVE)
+ {
+ /* never move to "burn:///", but fall back to copy.
+ * This is a workaround, because otherwise the source files would be removed.
+ */
+ copy_action = GDK_ACTION_COPY;
+ }
+
+ parent_window = NULL;
+ if (parent_view)
+ {
+ parent_window = (GtkWindow *) gtk_widget_get_ancestor (parent_view, GTK_TYPE_WINDOW);
+ }
+
+ if (g_file_has_uri_scheme (dest, "starred"))
+ {
+ g_autolist (NautilusFile) source_file_list = NULL;
+
+ for (GList *l = locations; l != NULL; l = l->next)
+ {
+ source_file_list = g_list_prepend (source_file_list, nautilus_file_get (l->data));
+ }
+
+ source_file_list = g_list_reverse (source_file_list);
+ nautilus_tag_manager_star_files (nautilus_tag_manager_get (),
+ G_OBJECT (parent_view),
+ source_file_list, NULL, NULL);
+ }
+ else if (copy_action == GDK_ACTION_COPY)
+ {
+ src_dir = g_file_get_parent (locations->data);
+ if (target_dir == NULL ||
+ (src_dir != NULL &&
+ g_file_equal (src_dir, dest)))
+ {
+ nautilus_file_operations_duplicate (locations,
+ parent_window,
+ dbus_data,
+ done_callback, done_callback_data);
+ }
+ else
+ {
+ nautilus_file_operations_copy_async (locations,
+ dest,
+ parent_window,
+ dbus_data,
+ done_callback, done_callback_data);
+ }
+ if (src_dir)
+ {
+ g_object_unref (src_dir);
+ }
+ }
+ else if (copy_action == GDK_ACTION_MOVE)
+ {
+ if (g_file_has_uri_scheme (dest, "trash"))
+ {
+ MoveTrashCBData *cb_data;
+
+ cb_data = g_slice_new0 (MoveTrashCBData);
+ cb_data->real_callback = done_callback;
+ cb_data->real_data = done_callback_data;
+
+ nautilus_file_operations_trash_or_delete_async (locations,
+ parent_window,
+ dbus_data,
+ (NautilusDeleteCallback) callback_for_move_to_trash,
+ cb_data);
+ }
+ else
+ {
+ nautilus_file_operations_move_async (locations,
+ dest,
+ parent_window,
+ dbus_data,
+ done_callback, done_callback_data);
+ }
+ }
+ else
+ {
+ nautilus_file_operations_link (locations,
+ dest,
+ parent_window,
+ dbus_data,
+ done_callback, done_callback_data);
+ }
+
+ g_list_free_full (locations, g_object_unref);
+ if (dest)
+ {
+ g_object_unref (dest);
+ }
+}
+
+static void
+create_task_done (GObject *source_object,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ CreateJob *job;
+
+ job = user_data;
+ if (job->done_callback)
+ {
+ job->done_callback (job->created_file,
+ !job_aborted ((CommonJob *) job),
+ job->done_callback_data);
+ }
+
+ g_object_unref (job->dest_dir);
+ if (job->src)
+ {
+ g_object_unref (job->src);
+ }
+ g_free (job->src_data);
+ g_free (job->filename);
+ if (job->created_file)
+ {
+ g_object_unref (job->created_file);
+ }
+
+ finalize_common ((CommonJob *) job);
+
+ nautilus_file_changes_consume_changes (TRUE);
+}
+
+static void
+create_task_thread_func (GTask *task,
+ gpointer source_object,
+ gpointer task_data,
+ GCancellable *cancellable)
+{
+ CreateJob *job;
+ CommonJob *common;
+ int count;
+ g_autoptr (GFile) dest = NULL;
+ g_autofree gchar *dest_uri = NULL;
+ g_autofree char *filename = NULL;
+ char *filename_base;
+ g_autofree char *dest_fs_type = NULL;
+ GError *error;
+ gboolean res;
+ gboolean filename_is_utf8;
+ char *primary, *secondary, *details;
+ int response;
+ char *data;
+ int length;
+ GFileOutputStream *out;
+ gboolean handled_invalid_filename;
+ int max_length, offset;
+
+ job = task_data;
+ common = &job->common;
+
+ nautilus_progress_info_start (job->common.progress);
+
+ handled_invalid_filename = FALSE;
+
+ max_length = nautilus_get_max_child_name_length_for_location (job->dest_dir);
+
+ verify_destination (common,
+ job->dest_dir,
+ NULL, -1);
+ if (job_aborted (common))
+ {
+ return;
+ }
+
+ filename = g_strdup (job->filename);
+ filename_is_utf8 = FALSE;
+ if (filename)
+ {
+ filename_is_utf8 = g_utf8_validate (filename, -1, NULL);
+ }
+ if (filename == NULL)
+ {
+ if (job->make_dir)
+ {
+ /* localizers: the initial name of a new folder */
+ filename = g_strdup (_("Untitled Folder"));
+ filename_is_utf8 = TRUE; /* Pass in utf8 */
+ }
+ else
+ {
+ if (job->src != NULL)
+ {
+ g_autofree char *basename = NULL;
+
+ basename = g_file_get_basename (job->src);
+ filename = g_strdup_printf ("%s", basename);
+ }
+ if (filename == NULL)
+ {
+ /* localizers: the initial name of a new empty document */
+ filename = g_strdup (_("Untitled Document"));
+ filename_is_utf8 = TRUE; /* Pass in utf8 */
+ }
+ }
+ }
+
+ make_file_name_valid_for_dest_fs (filename, dest_fs_type);
+ if (filename_is_utf8)
+ {
+ dest = g_file_get_child_for_display_name (job->dest_dir, filename, NULL);
+ }
+ if (dest == NULL)
+ {
+ dest = g_file_get_child (job->dest_dir, filename);
+ }
+ count = 1;
+
+retry:
+
+ error = NULL;
+ if (job->make_dir)
+ {
+ res = g_file_make_directory (dest,
+ common->cancellable,
+ &error);
+
+ if (res)
+ {
+ GFile *real;
+
+ real = map_possibly_volatile_file_to_real (dest, common->cancellable, &error);
+ if (real == NULL)
+ {
+ res = FALSE;
+ }
+ else
+ {
+ g_object_unref (dest);
+ dest = real;
+ }
+ }
+
+ if (res && common->undo_info != NULL)
+ {
+ nautilus_file_undo_info_create_set_data (NAUTILUS_FILE_UNDO_INFO_CREATE (common->undo_info),
+ dest, NULL, 0);
+ }
+ }
+ else
+ {
+ if (job->src)
+ {
+ res = g_file_copy (job->src,
+ dest,
+ G_FILE_COPY_TARGET_DEFAULT_PERMS,
+ common->cancellable,
+ NULL, NULL,
+ &error);
+
+ if (res)
+ {
+ GFile *real;
+
+ real = map_possibly_volatile_file_to_real (dest, common->cancellable, &error);
+ if (real == NULL)
+ {
+ res = FALSE;
+ }
+ else
+ {
+ g_object_unref (dest);
+ dest = real;
+ }
+ }
+
+ if (res && common->undo_info != NULL)
+ {
+ g_autofree gchar *uri = NULL;
+
+ uri = g_file_get_uri (job->src);
+ nautilus_file_undo_info_create_set_data (NAUTILUS_FILE_UNDO_INFO_CREATE (common->undo_info),
+ dest, uri, 0);
+ }
+ }
+ else
+ {
+ data = "";
+ length = 0;
+ if (job->src_data)
+ {
+ data = job->src_data;
+ length = job->length;
+ }
+
+ out = g_file_create (dest,
+ G_FILE_CREATE_NONE,
+ common->cancellable,
+ &error);
+ if (out)
+ {
+ GFile *real;
+
+ real = map_possibly_volatile_file_to_real_on_write (dest,
+ out,
+ common->cancellable,
+ &error);
+ if (real == NULL)
+ {
+ res = FALSE;
+ g_object_unref (out);
+ }
+ else
+ {
+ g_object_unref (dest);
+ dest = real;
+
+ res = g_output_stream_write_all (G_OUTPUT_STREAM (out),
+ data, length,
+ NULL,
+ common->cancellable,
+ &error);
+ if (res)
+ {
+ res = g_output_stream_close (G_OUTPUT_STREAM (out),
+ common->cancellable,
+ &error);
+
+ if (res && common->undo_info != NULL)
+ {
+ nautilus_file_undo_info_create_set_data (NAUTILUS_FILE_UNDO_INFO_CREATE (common->undo_info),
+ dest, data, length);
+ }
+ }
+
+ /* This will close if the write failed and we didn't close */
+ g_object_unref (out);
+ }
+ }
+ else
+ {
+ res = FALSE;
+ }
+ }
+ }
+
+ if (res)
+ {
+ job->created_file = g_object_ref (dest);
+ nautilus_file_changes_queue_file_added (dest);
+ dest_uri = g_file_get_uri (dest);
+ gtk_recent_manager_add_item (gtk_recent_manager_get_default (), dest_uri);
+ }
+ else
+ {
+ g_assert (error != NULL);
+
+ if (IS_IO_ERROR (error, INVALID_FILENAME) &&
+ !handled_invalid_filename)
+ {
+ g_autofree gchar *new_filename = NULL;
+
+ handled_invalid_filename = TRUE;
+
+ g_assert (dest_fs_type == NULL);
+ dest_fs_type = query_fs_type (job->dest_dir, common->cancellable);
+
+ if (count == 1)
+ {
+ new_filename = g_strdup (filename);
+ }
+ else
+ {
+ g_autofree char *filename2 = NULL;
+ g_autofree char *suffix = NULL;
+
+ filename_base = filename;
+ if (job->src != NULL)
+ {
+ g_autoptr (NautilusFile) file = NULL;
+ file = nautilus_file_get (job->src);
+ if (!nautilus_file_is_directory (file))
+ {
+ filename_base = eel_filename_strip_extension (filename);
+ }
+ }
+
+ offset = strlen (filename_base);
+ suffix = g_strdup (filename + offset);
+
+ filename2 = g_strdup_printf ("%s %d%s", filename_base, count, suffix);
+
+ new_filename = NULL;
+ if (max_length > 0 && strlen (filename2) > max_length)
+ {
+ new_filename = shorten_utf8_string (filename2, strlen (filename2) - max_length);
+ }
+
+ if (new_filename == NULL)
+ {
+ new_filename = g_strdup (filename2);
+ }
+ }
+
+ if (make_file_name_valid_for_dest_fs (new_filename, dest_fs_type))
+ {
+ g_object_unref (dest);
+
+ if (filename_is_utf8)
+ {
+ dest = g_file_get_child_for_display_name (job->dest_dir, new_filename, NULL);
+ }
+ if (dest == NULL)
+ {
+ dest = g_file_get_child (job->dest_dir, new_filename);
+ }
+
+ g_error_free (error);
+ goto retry;
+ }
+ }
+
+ if (IS_IO_ERROR (error, EXISTS))
+ {
+ g_autofree char *suffix = NULL;
+ g_autofree gchar *filename2 = NULL;
+
+ g_clear_object (&dest);
+
+ filename_base = filename;
+ if (job->src != NULL)
+ {
+ g_autoptr (NautilusFile) file = NULL;
+
+ file = nautilus_file_get (job->src);
+ if (!nautilus_file_is_directory (file))
+ {
+ filename_base = eel_filename_strip_extension (filename);
+ }
+ }
+
+
+ offset = strlen (filename_base);
+ suffix = g_strdup (filename + offset);
+
+ filename2 = g_strdup_printf ("%s %d%s", filename_base, ++count, suffix);
+
+ if (max_length > 0 && strlen (filename2) > max_length)
+ {
+ g_autofree char *new_filename = NULL;
+
+ new_filename = shorten_utf8_string (filename2, strlen (filename2) - max_length);
+ if (new_filename != NULL)
+ {
+ g_free (filename2);
+ filename2 = new_filename;
+ }
+ }
+
+ make_file_name_valid_for_dest_fs (filename2, dest_fs_type);
+ if (filename_is_utf8)
+ {
+ dest = g_file_get_child_for_display_name (job->dest_dir, filename2, NULL);
+ }
+ if (dest == NULL)
+ {
+ dest = g_file_get_child (job->dest_dir, filename2);
+ }
+ g_error_free (error);
+ goto retry;
+ }
+ else if (IS_IO_ERROR (error, CANCELLED))
+ {
+ g_error_free (error);
+ }
+ /* Other error */
+ else
+ {
+ g_autofree gchar *basename = NULL;
+ g_autofree gchar *parse_name = NULL;
+
+ basename = get_basename (dest);
+ if (job->make_dir)
+ {
+ primary = g_strdup_printf (_("Error while creating directory %s."),
+ basename);
+ }
+ else
+ {
+ primary = g_strdup_printf (_("Error while creating file %s."),
+ basename);
+ }
+ parse_name = get_truncated_parse_name (job->dest_dir);
+ secondary = g_strdup_printf (_("There was an error creating the directory in %s."),
+ parse_name);
+
+ details = error->message;
+
+ response = run_warning (common,
+ primary,
+ secondary,
+ details,
+ FALSE,
+ CANCEL, SKIP,
+ NULL);
+
+ g_error_free (error);
+
+ if (response == 0 || response == GTK_RESPONSE_DELETE_EVENT)
+ {
+ abort_job (common);
+ }
+ else if (response == 1) /* skip */
+ { /* do nothing */
+ }
+ else
+ {
+ g_assert_not_reached ();
+ }
+ }
+ }
+}
+
+void
+nautilus_file_operations_new_folder (GtkWidget *parent_view,
+ NautilusFileOperationsDBusData *dbus_data,
+ const char *parent_dir,
+ const char *folder_name,
+ NautilusCreateCallback done_callback,
+ gpointer done_callback_data)
+{
+ g_autoptr (GTask) task = NULL;
+ CreateJob *job;
+ GtkWindow *parent_window;
+
+ parent_window = NULL;
+ if (parent_view)
+ {
+ parent_window = (GtkWindow *) gtk_widget_get_ancestor (parent_view, GTK_TYPE_WINDOW);
+ }
+
+ job = op_job_new (CreateJob, parent_window, dbus_data);
+ job->done_callback = done_callback;
+ job->done_callback_data = done_callback_data;
+ job->dest_dir = g_file_new_for_uri (parent_dir);
+ job->filename = g_strdup (folder_name);
+ job->make_dir = TRUE;
+
+ if (!nautilus_file_undo_manager_is_operating ())
+ {
+ job->common.undo_info = nautilus_file_undo_info_create_new (NAUTILUS_FILE_UNDO_OP_CREATE_FOLDER);
+ }
+
+ task = g_task_new (NULL, job->common.cancellable, create_task_done, job);
+ g_task_set_task_data (task, job, NULL);
+ g_task_run_in_thread (task, create_task_thread_func);
+}
+
+void
+nautilus_file_operations_new_file_from_template (GtkWidget *parent_view,
+ const char *parent_dir,
+ const char *target_filename,
+ const char *template_uri,
+ NautilusCreateCallback done_callback,
+ gpointer done_callback_data)
+{
+ g_autoptr (GTask) task = NULL;
+ CreateJob *job;
+ GtkWindow *parent_window;
+
+ parent_window = NULL;
+ if (parent_view)
+ {
+ parent_window = (GtkWindow *) gtk_widget_get_ancestor (parent_view, GTK_TYPE_WINDOW);
+ }
+
+ job = op_job_new (CreateJob, parent_window, NULL);
+ job->done_callback = done_callback;
+ job->done_callback_data = done_callback_data;
+ job->dest_dir = g_file_new_for_uri (parent_dir);
+ job->filename = g_strdup (target_filename);
+
+ if (template_uri)
+ {
+ job->src = g_file_new_for_uri (template_uri);
+ }
+
+ if (!nautilus_file_undo_manager_is_operating ())
+ {
+ job->common.undo_info = nautilus_file_undo_info_create_new (NAUTILUS_FILE_UNDO_OP_CREATE_FILE_FROM_TEMPLATE);
+ }
+
+ task = g_task_new (NULL, job->common.cancellable, create_task_done, job);
+ g_task_set_task_data (task, job, NULL);
+ g_task_run_in_thread (task, create_task_thread_func);
+}
+
+void
+nautilus_file_operations_new_file (GtkWidget *parent_view,
+ const char *parent_dir,
+ const char *target_filename,
+ const char *initial_contents,
+ int length,
+ NautilusCreateCallback done_callback,
+ gpointer done_callback_data)
+{
+ g_autoptr (GTask) task = NULL;
+ CreateJob *job;
+ GtkWindow *parent_window;
+
+ parent_window = NULL;
+ if (parent_view)
+ {
+ parent_window = (GtkWindow *) gtk_widget_get_ancestor (parent_view, GTK_TYPE_WINDOW);
+ }
+
+ job = op_job_new (CreateJob, parent_window, NULL);
+ job->done_callback = done_callback;
+ job->done_callback_data = done_callback_data;
+ job->dest_dir = g_file_new_for_uri (parent_dir);
+ job->src_data = g_memdup (initial_contents, length);
+ job->length = length;
+ job->filename = g_strdup (target_filename);
+
+ if (!nautilus_file_undo_manager_is_operating ())
+ {
+ job->common.undo_info = nautilus_file_undo_info_create_new (NAUTILUS_FILE_UNDO_OP_CREATE_EMPTY_FILE);
+ }
+
+ task = g_task_new (NULL, job->common.cancellable, create_task_done, job);
+ g_task_set_task_data (task, job, NULL);
+ g_task_run_in_thread (task, create_task_thread_func);
+}
+
+
+
+static void
+delete_trash_file (CommonJob *job,
+ GFile *file,
+ gboolean del_file,
+ gboolean del_children)
+{
+ GFileInfo *info;
+ GFile *child;
+ GFileEnumerator *enumerator;
+
+ if (job_aborted (job))
+ {
+ return;
+ }
+
+ if (del_children)
+ {
+ gboolean should_recurse;
+
+ /* The g_file_delete operation works differently for locations provided
+ * by the trash backend as it prevents modifications of trashed items
+ * For that reason, it is enough to call g_file_delete on top-level
+ * items only.
+ */
+ should_recurse = !g_file_has_uri_scheme (file, "trash");
+
+ enumerator = g_file_enumerate_children (file,
+ G_FILE_ATTRIBUTE_STANDARD_NAME ","
+ G_FILE_ATTRIBUTE_STANDARD_TYPE,
+ G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
+ job->cancellable,
+ NULL);
+ if (enumerator)
+ {
+ while (!job_aborted (job) &&
+ (info = g_file_enumerator_next_file (enumerator, job->cancellable, NULL)) != NULL)
+ {
+ gboolean is_dir;
+
+ child = g_file_get_child (file,
+ g_file_info_get_name (info));
+ is_dir = (g_file_info_get_file_type (info) == G_FILE_TYPE_DIRECTORY);
+
+ delete_trash_file (job, child, TRUE, should_recurse && is_dir);
+ g_object_unref (child);
+ g_object_unref (info);
+ }
+ g_file_enumerator_close (enumerator, job->cancellable, NULL);
+ g_object_unref (enumerator);
+ }
+ }
+
+ if (!job_aborted (job) && del_file)
+ {
+ g_file_delete (file, job->cancellable, NULL);
+ }
+}
+
+static void
+empty_trash_task_done (GObject *source_object,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ EmptyTrashJob *job;
+
+ job = user_data;
+
+ g_list_free_full (job->trash_dirs, g_object_unref);
+
+ if (job->done_callback)
+ {
+ job->done_callback (!job_aborted ((CommonJob *) job),
+ job->done_callback_data);
+ }
+
+ finalize_common ((CommonJob *) job);
+}
+
+static void
+empty_trash_thread_func (GTask *task,
+ gpointer source_object,
+ gpointer task_data,
+ GCancellable *cancellable)
+{
+ EmptyTrashJob *job = task_data;
+ CommonJob *common;
+ GList *l;
+ gboolean confirmed;
+
+ common = (CommonJob *) job;
+
+ nautilus_progress_info_start (job->common.progress);
+
+ if (job->should_confirm)
+ {
+ confirmed = confirm_empty_trash (common);
+ }
+ else
+ {
+ confirmed = TRUE;
+ }
+ if (confirmed)
+ {
+ for (l = job->trash_dirs;
+ l != NULL && !job_aborted (common);
+ l = l->next)
+ {
+ delete_trash_file (common, l->data, FALSE, TRUE);
+ }
+ }
+}
+
+void
+nautilus_file_operations_empty_trash (GtkWidget *parent_view,
+ gboolean ask_confirmation,
+ NautilusFileOperationsDBusData *dbus_data)
+{
+ g_autoptr (GTask) task = NULL;
+ EmptyTrashJob *job;
+ GtkWindow *parent_window;
+
+ parent_window = NULL;
+ if (parent_view)
+ {
+ parent_window = (GtkWindow *) gtk_widget_get_ancestor (parent_view, GTK_TYPE_WINDOW);
+ }
+
+ job = op_job_new (EmptyTrashJob, parent_window, dbus_data);
+ job->trash_dirs = g_list_prepend (job->trash_dirs,
+ g_file_new_for_uri ("trash:"));
+ job->should_confirm = ask_confirmation;
+
+ inhibit_power_manager ((CommonJob *) job, _("Emptying Trash"));
+
+ task = g_task_new (NULL, NULL, empty_trash_task_done, job);
+ g_task_set_task_data (task, job, NULL);
+ g_task_run_in_thread (task, empty_trash_thread_func);
+}
+
+static void
+extract_task_done (GObject *source_object,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ ExtractJob *extract_job;
+
+ extract_job = user_data;
+
+ if (extract_job->done_callback)
+ {
+ extract_job->done_callback (extract_job->output_files,
+ extract_job->done_callback_data);
+ }
+
+ g_list_free_full (extract_job->source_files, g_object_unref);
+ g_list_free_full (extract_job->output_files, g_object_unref);
+ g_object_unref (extract_job->destination_directory);
+
+ finalize_common ((CommonJob *) extract_job);
+
+ nautilus_file_changes_consume_changes (TRUE);
+}
+
+static GFile *
+extract_job_on_decide_destination (AutoarExtractor *extractor,
+ GFile *destination,
+ GList *files,
+ gpointer user_data)
+{
+ ExtractJob *extract_job = user_data;
+ GFile *decided_destination;
+ g_autofree char *basename = NULL;
+
+ nautilus_progress_info_set_details (extract_job->common.progress,
+ _("Verifying destination"));
+
+ basename = g_file_get_basename (destination);
+ decided_destination = nautilus_generate_unique_file_in_directory (extract_job->destination_directory,
+ basename);
+
+ if (job_aborted ((CommonJob *) extract_job))
+ {
+ g_object_unref (decided_destination);
+ return NULL;
+ }
+
+ /* The extract_job->destination_decided variable signalizes whether the
+ * extract_job->output_files list already contains the final location as
+ * its first link. There is no way to get this over the AutoarExtractor
+ * API currently.
+ */
+ extract_job->output_files = g_list_prepend (extract_job->output_files,
+ decided_destination);
+ extract_job->destination_decided = TRUE;
+
+ return g_object_ref (decided_destination);
+}
+
+static void
+extract_job_on_progress (AutoarExtractor *extractor,
+ guint64 archive_current_decompressed_size,
+ guint archive_current_decompressed_files,
+ gpointer user_data)
+{
+ ExtractJob *extract_job = user_data;
+ CommonJob *common = user_data;
+ GFile *source_file;
+ char *details;
+ double elapsed;
+ double transfer_rate;
+ int remaining_time;
+ guint64 archive_total_decompressed_size;
+ gdouble archive_weight;
+ gdouble archive_decompress_progress;
+ guint64 job_completed_size;
+ gdouble job_progress;
+ g_autofree gchar *basename = NULL;
+ g_autofree gchar *formatted_size_job_completed_size = NULL;
+ g_autofree gchar *formatted_size_total_compressed_size = NULL;
+
+ source_file = autoar_extractor_get_source_file (extractor);
+
+ basename = get_basename (source_file);
+ nautilus_progress_info_take_status (common->progress,
+ g_strdup_printf (_("Extracting “%s”"),
+ basename));
+
+ archive_total_decompressed_size = autoar_extractor_get_total_size (extractor);
+
+ archive_decompress_progress = (gdouble) archive_current_decompressed_size /
+ (gdouble) archive_total_decompressed_size;
+
+ archive_weight = 0;
+ if (extract_job->total_compressed_size)
+ {
+ archive_weight = (gdouble) extract_job->archive_compressed_size /
+ (gdouble) extract_job->total_compressed_size;
+ }
+
+ job_progress = archive_decompress_progress * archive_weight + extract_job->base_progress;
+
+ elapsed = g_timer_elapsed (common->time, NULL);
+
+ transfer_rate = 0;
+ remaining_time = -1;
+
+ job_completed_size = job_progress * extract_job->total_compressed_size;
+
+ if (elapsed > 0)
+ {
+ transfer_rate = job_completed_size / elapsed;
+ }
+ if (transfer_rate > 0)
+ {
+ remaining_time = (extract_job->total_compressed_size - job_completed_size) /
+ transfer_rate;
+ }
+
+ formatted_size_job_completed_size = g_format_size (job_completed_size);
+ formatted_size_total_compressed_size = g_format_size (extract_job->total_compressed_size);
+ if (elapsed < SECONDS_NEEDED_FOR_RELIABLE_TRANSFER_RATE ||
+ transfer_rate == 0)
+ {
+ /* To translators: %s will expand to a size like "2 bytes" or
+ * "3 MB", so something like "4 kb / 4 MB"
+ */
+ details = g_strdup_printf (_("%s / %s"), formatted_size_job_completed_size,
+ formatted_size_total_compressed_size);
+ }
+ else
+ {
+ g_autofree gchar *formatted_time = NULL;
+ g_autofree gchar *formatted_size_transfer_rate = NULL;
+
+ formatted_time = get_formatted_time (remaining_time);
+ formatted_size_transfer_rate = g_format_size ((goffset) transfer_rate);
+ /* To translators: %s will expand to a size like "2 bytes" or
+ * "3 MB", %s to a time duration like "2 minutes". So the whole
+ * thing will be something like
+ * "2 kb / 4 MB -- 2 hours left (4kb/sec)"
+ *
+ * The singular/plural form will be used depending on the
+ * remaining time (i.e. the %s argument).
+ */
+ details = g_strdup_printf (ngettext ("%s / %s \xE2\x80\x94 %s left (%s/sec)",
+ "%s / %s \xE2\x80\x94 %s left (%s/sec)",
+ seconds_count_format_time_units (remaining_time)),
+ formatted_size_job_completed_size,
+ formatted_size_total_compressed_size,
+ formatted_time,
+ formatted_size_transfer_rate);
+ }
+
+ nautilus_progress_info_take_details (common->progress, details);
+
+ if (elapsed > SECONDS_NEEDED_FOR_APROXIMATE_TRANSFER_RATE)
+ {
+ nautilus_progress_info_set_remaining_time (common->progress,
+ remaining_time);
+ nautilus_progress_info_set_elapsed_time (common->progress,
+ elapsed);
+ }
+
+ nautilus_progress_info_set_progress (common->progress, job_progress, 1);
+}
+
+static void
+extract_job_on_error (AutoarExtractor *extractor,
+ GError *error,
+ gpointer user_data)
+{
+ ExtractJob *extract_job = user_data;
+ GFile *source_file;
+ GFile *destination;
+ gint response_id;
+ gint remaining_files;
+ g_autofree gchar *basename = NULL;
+
+ source_file = autoar_extractor_get_source_file (extractor);
+
+ if (IS_IO_ERROR (error, NOT_SUPPORTED))
+ {
+ handle_unsupported_compressed_file (extract_job->common.parent_window,
+ source_file);
+
+ return;
+ }
+
+ extract_job->extraction_failed = TRUE;
+
+ /* It is safe to use extract_job->output_files->data only when the
+ * extract_job->destination_decided variable was set, see comment in the
+ * extract_job_on_decide_destination function.
+ */
+ if (extract_job->destination_decided)
+ {
+ destination = extract_job->output_files->data;
+ delete_file_recursively (destination, NULL, NULL, NULL);
+ extract_job->output_files = g_list_delete_link (extract_job->output_files,
+ extract_job->output_files);
+ g_object_unref (destination);
+ }
+
+ if (extract_job->common.skip_all_error)
+ {
+ return;
+ }
+
+ basename = get_basename (source_file);
+ nautilus_progress_info_take_status (extract_job->common.progress,
+ g_strdup_printf (_("Error extracting “%s”"),
+ basename));
+
+ remaining_files = g_list_length (g_list_find_custom (extract_job->source_files,
+ source_file,
+ (GCompareFunc) g_file_equal)) - 1;
+ response_id = run_cancel_or_skip_warning ((CommonJob *) extract_job,
+ g_strdup_printf (_("There was an error while extracting “%s”."),
+ basename),
+ g_strdup (error->message),
+ NULL,
+ extract_job->total_files,
+ remaining_files);
+
+ if (response_id == 0 || response_id == GTK_RESPONSE_DELETE_EVENT)
+ {
+ abort_job ((CommonJob *) extract_job);
+ }
+ else if (response_id == 1)
+ {
+ extract_job->common.skip_all_error = TRUE;
+ }
+}
+
+static void
+extract_job_on_completed (AutoarExtractor *extractor,
+ gpointer user_data)
+{
+ ExtractJob *extract_job = user_data;
+ GFile *output_file;
+
+ output_file = G_FILE (extract_job->output_files->data);
+
+ nautilus_file_changes_queue_file_added (output_file);
+}
+
+static gchar *
+extract_job_on_request_passphrase (AutoarExtractor *extractor,
+ gpointer user_data)
+{
+ ExtractJob *extract_job = user_data;
+ GtkWindow *parent_window;
+ GFile *source_file;
+ g_autofree gchar *basename = NULL;
+ gchar *passphrase;
+
+ parent_window = extract_job->common.parent_window;
+ source_file = autoar_extractor_get_source_file (extractor);
+ basename = get_basename (source_file);
+
+ passphrase = extract_ask_passphrase (parent_window, basename);
+ if (passphrase == NULL)
+ {
+ abort_job ((CommonJob *) extract_job);
+ }
+
+ return passphrase;
+}
+
+static void
+extract_job_on_scanned (AutoarExtractor *extractor,
+ guint total_files,
+ gpointer user_data)
+{
+ guint64 total_size;
+ ExtractJob *extract_job;
+ GFile *source_file;
+ g_autofree gchar *basename = NULL;
+ GFileInfo *fsinfo;
+ guint64 free_size;
+
+ extract_job = user_data;
+ total_size = autoar_extractor_get_total_size (extractor);
+ source_file = autoar_extractor_get_source_file (extractor);
+ basename = get_basename (source_file);
+
+ fsinfo = g_file_query_filesystem_info (source_file,
+ G_FILE_ATTRIBUTE_FILESYSTEM_FREE ","
+ G_FILE_ATTRIBUTE_FILESYSTEM_READONLY,
+ extract_job->common.cancellable,
+ NULL);
+ free_size = g_file_info_get_attribute_uint64 (fsinfo,
+ G_FILE_ATTRIBUTE_FILESYSTEM_FREE);
+
+ /* FIXME: G_MAXUINT64 is the value used by autoar when the file size cannot
+ * be determined. Ideally an API should be used instead.
+ */
+ if (total_size != G_MAXUINT64 && total_size > free_size)
+ {
+ nautilus_progress_info_take_status (extract_job->common.progress,
+ g_strdup_printf (_("Error extracting “%s”"),
+ basename));
+ run_error (&extract_job->common,
+ g_strdup_printf (_("Not enough free space to extract %s"), basename),
+ NULL,
+ NULL,
+ FALSE,
+ CANCEL,
+ NULL);
+
+ abort_job ((CommonJob *) extract_job);
+ }
+}
+
+static void
+report_extract_final_progress (ExtractJob *extract_job)
+{
+ char *status;
+ g_autofree gchar *basename_dest = NULL;
+ g_autofree gchar *formatted_size = NULL;
+
+ nautilus_progress_info_set_destination (extract_job->common.progress,
+ extract_job->destination_directory);
+ basename_dest = get_basename (extract_job->destination_directory);
+
+ /* The g_list_length function is used intentionally here instead of the
+ * extract_job->total_files variable to avoid printing wrong basename in
+ * the case of skipped files.
+ */
+ if (g_list_length (extract_job->source_files) == 1)
+ {
+ GFile *source_file;
+ g_autofree gchar *basename = NULL;
+
+ source_file = G_FILE (extract_job->source_files->data);
+ basename = get_basename (source_file);
+ status = g_strdup_printf (_("Extracted “%s” to “%s”"),
+ basename,
+ basename_dest);
+ }
+ else
+ {
+ status = g_strdup_printf (ngettext ("Extracted %'d file to “%s”",
+ "Extracted %'d files to “%s”",
+ extract_job->total_files),
+ extract_job->total_files,
+ basename_dest);
+ }
+
+ nautilus_progress_info_take_status (extract_job->common.progress,
+ status);
+ formatted_size = g_format_size (extract_job->total_compressed_size);
+ nautilus_progress_info_take_details (extract_job->common.progress,
+ g_strdup_printf (_("%s / %s"),
+ formatted_size,
+ formatted_size));
+
+ nautilus_progress_info_set_progress (extract_job->common.progress, 1, 1);
+}
+
+static void
+extract_task_thread_func (GTask *task,
+ gpointer source_object,
+ gpointer task_data,
+ GCancellable *cancellable)
+{
+ ExtractJob *extract_job = task_data;
+ GList *l;
+ g_autofree guint64 *archive_compressed_sizes = NULL;
+ gint i;
+
+ g_timer_start (extract_job->common.time);
+
+ nautilus_progress_info_start (extract_job->common.progress);
+
+ nautilus_progress_info_set_details (extract_job->common.progress,
+ _("Preparing to extract"));
+
+ extract_job->total_files = g_list_length (extract_job->source_files);
+
+ archive_compressed_sizes = g_malloc0_n (extract_job->total_files,
+ sizeof (guint64));
+ extract_job->total_compressed_size = 0;
+
+ for (l = extract_job->source_files, i = 0;
+ l != NULL && !job_aborted ((CommonJob *) extract_job);
+ l = l->next, i++)
+ {
+ GFile *source_file;
+ g_autoptr (GFileInfo) info = NULL;
+
+ source_file = G_FILE (l->data);
+ info = g_file_query_info (source_file,
+ G_FILE_ATTRIBUTE_STANDARD_SIZE,
+ G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
+ extract_job->common.cancellable,
+ NULL);
+
+ if (info)
+ {
+ archive_compressed_sizes[i] = g_file_info_get_size (info);
+ extract_job->total_compressed_size += archive_compressed_sizes[i];
+ }
+ }
+
+ extract_job->base_progress = 0;
+
+ for (l = extract_job->source_files, i = 0;
+ l != NULL && !job_aborted ((CommonJob *) extract_job);
+ l = l->next, i++)
+ {
+ g_autoptr (AutoarExtractor) extractor = NULL;
+
+ extractor = autoar_extractor_new (G_FILE (l->data),
+ extract_job->destination_directory);
+
+ autoar_extractor_set_notify_interval (extractor,
+ PROGRESS_NOTIFY_INTERVAL);
+ g_signal_connect (extractor, "scanned",
+ G_CALLBACK (extract_job_on_scanned),
+ extract_job);
+ g_signal_connect (extractor, "error",
+ G_CALLBACK (extract_job_on_error),
+ extract_job);
+ g_signal_connect (extractor, "decide-destination",
+ G_CALLBACK (extract_job_on_decide_destination),
+ extract_job);
+ g_signal_connect (extractor, "progress",
+ G_CALLBACK (extract_job_on_progress),
+ extract_job);
+ g_signal_connect (extractor, "completed",
+ G_CALLBACK (extract_job_on_completed),
+ extract_job);
+ g_signal_connect (extractor, "request-passphrase",
+ G_CALLBACK (extract_job_on_request_passphrase),
+ extract_job);
+
+ extract_job->archive_compressed_size = archive_compressed_sizes[i];
+ extract_job->destination_decided = FALSE;
+ extract_job->extraction_failed = FALSE;
+
+ autoar_extractor_start (extractor,
+ extract_job->common.cancellable);
+
+ g_signal_handlers_disconnect_by_data (extractor,
+ extract_job);
+
+ if (!extract_job->extraction_failed)
+ {
+ extract_job->base_progress += (gdouble) extract_job->archive_compressed_size /
+ (gdouble) extract_job->total_compressed_size;
+ }
+ else
+ {
+ extract_job->total_files--;
+ extract_job->base_progress *= extract_job->total_compressed_size;
+ extract_job->total_compressed_size -= extract_job->archive_compressed_size;
+ extract_job->base_progress /= extract_job->total_compressed_size;
+ }
+ }
+
+ if (!job_aborted ((CommonJob *) extract_job))
+ {
+ report_extract_final_progress (extract_job);
+ }
+
+ if (extract_job->common.undo_info)
+ {
+ if (extract_job->output_files)
+ {
+ NautilusFileUndoInfoExtract *undo_info;
+
+ undo_info = NAUTILUS_FILE_UNDO_INFO_EXTRACT (extract_job->common.undo_info);
+
+ nautilus_file_undo_info_extract_set_outputs (undo_info,
+ extract_job->output_files);
+ }
+ else
+ {
+ /* There is nothing to undo if there is no output */
+ g_clear_object (&extract_job->common.undo_info);
+ }
+ }
+}
+
+void
+nautilus_file_operations_extract_files (GList *files,
+ GFile *destination_directory,
+ GtkWindow *parent_window,
+ NautilusFileOperationsDBusData *dbus_data,
+ NautilusExtractCallback done_callback,
+ gpointer done_callback_data)
+{
+ ExtractJob *extract_job;
+ g_autoptr (GTask) task = NULL;
+
+ extract_job = op_job_new (ExtractJob, parent_window, dbus_data);
+ extract_job->source_files = g_list_copy_deep (files,
+ (GCopyFunc) g_object_ref,
+ NULL);
+ extract_job->destination_directory = g_object_ref (destination_directory);
+ extract_job->done_callback = done_callback;
+ extract_job->done_callback_data = done_callback_data;
+
+ inhibit_power_manager ((CommonJob *) extract_job, _("Extracting Files"));
+
+ if (!nautilus_file_undo_manager_is_operating ())
+ {
+ extract_job->common.undo_info = nautilus_file_undo_info_extract_new (files,
+ destination_directory);
+ }
+
+ task = g_task_new (NULL, extract_job->common.cancellable,
+ extract_task_done, extract_job);
+ g_task_set_task_data (task, extract_job, NULL);
+ g_task_run_in_thread (task, extract_task_thread_func);
+}
+
+static void
+compress_task_done (GObject *source_object,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ CompressJob *compress_job = user_data;
+
+ if (compress_job->done_callback)
+ {
+ compress_job->done_callback (compress_job->output_file,
+ compress_job->success,
+ compress_job->done_callback_data);
+ }
+
+ g_object_unref (compress_job->output_file);
+ g_list_free_full (compress_job->source_files, g_object_unref);
+ g_free (compress_job->passphrase);
+
+ finalize_common ((CommonJob *) compress_job);
+
+ nautilus_file_changes_consume_changes (TRUE);
+}
+
+static void
+compress_job_on_progress (AutoarCompressor *compressor,
+ guint64 completed_size,
+ guint completed_files,
+ gpointer user_data)
+{
+ CompressJob *compress_job = user_data;
+ CommonJob *common = user_data;
+ char *status;
+ char *details;
+ int files_left;
+ double elapsed;
+ double transfer_rate;
+ int remaining_time;
+ g_autofree gchar *basename_output_file = NULL;
+
+ files_left = compress_job->total_files - completed_files;
+ basename_output_file = get_basename (compress_job->output_file);
+ if (compress_job->total_files == 1)
+ {
+ g_autofree gchar *basename_data = NULL;
+
+ basename_data = get_basename (G_FILE (compress_job->source_files->data));
+ status = g_strdup_printf (_("Compressing “%s” into “%s”"),
+ basename_data,
+ basename_output_file);
+ }
+ else
+ {
+ status = g_strdup_printf (ngettext ("Compressing %'d file into “%s”",
+ "Compressing %'d files into “%s”",
+ compress_job->total_files),
+ compress_job->total_files,
+ basename_output_file);
+ }
+ nautilus_progress_info_take_status (common->progress, status);
+
+ elapsed = g_timer_elapsed (common->time, NULL);
+
+ transfer_rate = 0;
+ remaining_time = -1;
+
+ if (elapsed > 0)
+ {
+ if (completed_size > 0)
+ {
+ transfer_rate = completed_size / elapsed;
+ remaining_time = (compress_job->total_size - completed_size) / transfer_rate;
+ }
+ else if (completed_files > 0)
+ {
+ transfer_rate = completed_files / elapsed;
+ remaining_time = (compress_job->total_files - completed_files) / transfer_rate;
+ }
+ }
+
+ if (elapsed < SECONDS_NEEDED_FOR_RELIABLE_TRANSFER_RATE ||
+ transfer_rate == 0)
+ {
+ if (compress_job->total_files == 1)
+ {
+ g_autofree gchar *formatted_size_completed_size = NULL;
+ g_autofree gchar *formatted_size_total_size = NULL;
+
+ formatted_size_completed_size = g_format_size (completed_size);
+ formatted_size_total_size = g_format_size (compress_job->total_size);
+ /* To translators: %s will expand to a size like "2 bytes" or "3 MB", so something like "4 kb / 4 MB" */
+ details = g_strdup_printf (_("%s / %s"), formatted_size_completed_size,
+ formatted_size_total_size);
+ }
+ else
+ {
+ details = g_strdup_printf (_("%'d / %'d"),
+ files_left > 0 ? completed_files + 1 : completed_files,
+ compress_job->total_files);
+ }
+ }
+ else
+ {
+ if (compress_job->total_files == 1)
+ {
+ g_autofree gchar *formatted_size_completed_size = NULL;
+ g_autofree gchar *formatted_size_total_size = NULL;
+
+ formatted_size_completed_size = g_format_size (completed_size);
+ formatted_size_total_size = g_format_size (compress_job->total_size);
+
+ if (files_left > 0)
+ {
+ g_autofree gchar *formatted_time = NULL;
+ g_autofree gchar *formatted_size_transfer_rate = NULL;
+
+ formatted_time = get_formatted_time (remaining_time);
+ formatted_size_transfer_rate = g_format_size ((goffset) transfer_rate);
+ /* To translators: %s will expand to a size like "2 bytes" or "3 MB", %s to a time duration like
+ * "2 minutes". So the whole thing will be something like "2 kb / 4 MB -- 2 hours left (4kb/sec)"
+ *
+ * The singular/plural form will be used depending on the remaining time (i.e. the %s argument).
+ */
+ details = g_strdup_printf (ngettext ("%s / %s \xE2\x80\x94 %s left (%s/sec)",
+ "%s / %s \xE2\x80\x94 %s left (%s/sec)",
+ seconds_count_format_time_units (remaining_time)),
+ formatted_size_completed_size,
+ formatted_size_total_size,
+ formatted_time,
+ formatted_size_transfer_rate);
+ }
+ else
+ {
+ /* To translators: %s will expand to a size like "2 bytes" or "3 MB". */
+ details = g_strdup_printf (_("%s / %s"),
+ formatted_size_completed_size,
+ formatted_size_total_size);
+ }
+ }
+ else
+ {
+ if (files_left > 0)
+ {
+ g_autofree gchar *formatted_time = NULL;
+ g_autofree gchar *formatted_size = NULL;
+
+ formatted_time = get_formatted_time (remaining_time);
+ formatted_size = g_format_size ((goffset) transfer_rate);
+ /* To translators: %s will expand to a time duration like "2 minutes".
+ * So the whole thing will be something like "1 / 5 -- 2 hours left (4kb/sec)"
+ *
+ * The singular/plural form will be used depending on the remaining time (i.e. the %s argument).
+ */
+ details = g_strdup_printf (ngettext ("%'d / %'d \xE2\x80\x94 %s left (%s/sec)",
+ "%'d / %'d \xE2\x80\x94 %s left (%s/sec)",
+ seconds_count_format_time_units (remaining_time)),
+ completed_files + 1, compress_job->total_files,
+ formatted_time,
+ formatted_size);
+ }
+ else
+ {
+ /* To translators: %'d is the number of files completed for the operation,
+ * so it will be something like 2/14. */
+ details = g_strdup_printf (_("%'d / %'d"),
+ completed_files,
+ compress_job->total_files);
+ }
+ }
+ }
+
+ nautilus_progress_info_take_details (common->progress, details);
+
+ if (elapsed > SECONDS_NEEDED_FOR_APROXIMATE_TRANSFER_RATE)
+ {
+ nautilus_progress_info_set_remaining_time (common->progress,
+ remaining_time);
+ nautilus_progress_info_set_elapsed_time (common->progress,
+ elapsed);
+ }
+
+ nautilus_progress_info_set_progress (common->progress,
+ completed_size,
+ compress_job->total_size);
+}
+
+static void
+compress_job_on_error (AutoarCompressor *compressor,
+ GError *error,
+ gpointer user_data)
+{
+ CompressJob *compress_job = user_data;
+ char *status;
+ g_autofree gchar *basename_output_file = NULL;
+
+ basename_output_file = get_basename (compress_job->output_file);
+ if (compress_job->total_files == 1)
+ {
+ g_autofree gchar *basename_data = NULL;
+
+ basename_data = get_basename (G_FILE (compress_job->source_files->data));
+ status = g_strdup_printf (_("Error compressing “%s” into “%s”"),
+ basename_data,
+ basename_output_file);
+ }
+ else
+ {
+ status = g_strdup_printf (ngettext ("Error compressing %'d file into “%s”",
+ "Error compressing %'d files into “%s”",
+ compress_job->total_files),
+ compress_job->total_files,
+ basename_output_file);
+ }
+ nautilus_progress_info_take_status (compress_job->common.progress,
+ status);
+
+ run_error ((CommonJob *) compress_job,
+ g_strdup (_("There was an error while compressing files.")),
+ g_strdup (error->message),
+ NULL,
+ FALSE,
+ CANCEL,
+ NULL);
+
+ abort_job ((CommonJob *) compress_job);
+}
+
+static void
+compress_job_on_completed (AutoarCompressor *compressor,
+ gpointer user_data)
+{
+ CompressJob *compress_job = user_data;
+ g_autoptr (GFile) destination_directory = NULL;
+ char *status;
+ g_autofree gchar *basename_output_file = NULL;
+
+ basename_output_file = get_basename (compress_job->output_file);
+ if (compress_job->total_files == 1)
+ {
+ g_autofree gchar *basename_data = NULL;
+
+ basename_data = get_basename (G_FILE (compress_job->source_files->data));
+ status = g_strdup_printf (_("Compressed “%s” into “%s”"),
+ basename_data,
+ basename_output_file);
+ }
+ else
+ {
+ status = g_strdup_printf (ngettext ("Compressed %'d file into “%s”",
+ "Compressed %'d files into “%s”",
+ compress_job->total_files),
+ compress_job->total_files,
+ basename_output_file);
+ }
+
+ nautilus_progress_info_take_status (compress_job->common.progress,
+ status);
+
+ nautilus_file_changes_queue_file_added (compress_job->output_file);
+
+ destination_directory = g_file_get_parent (compress_job->output_file);
+ nautilus_progress_info_set_destination (compress_job->common.progress,
+ destination_directory);
+}
+
+static void
+compress_task_thread_func (GTask *task,
+ gpointer source_object,
+ gpointer task_data,
+ GCancellable *cancellable)
+{
+ CompressJob *compress_job = task_data;
+ g_auto (SourceInfo) source_info = SOURCE_INFO_INIT;
+ g_autoptr (AutoarCompressor) compressor = NULL;
+
+ g_timer_start (compress_job->common.time);
+
+ nautilus_progress_info_start (compress_job->common.progress);
+
+ scan_sources (compress_job->source_files,
+ &source_info,
+ (CommonJob *) compress_job,
+ OP_KIND_COMPRESS);
+
+ compress_job->total_files = source_info.num_files;
+ compress_job->total_size = source_info.num_bytes;
+
+ compressor = autoar_compressor_new (compress_job->source_files,
+ compress_job->output_file,
+ compress_job->format,
+ compress_job->filter,
+ FALSE);
+ if (compress_job->passphrase && compress_job->passphrase[0] != '\0')
+ {
+ autoar_compressor_set_passphrase (compressor, compress_job->passphrase);
+ }
+
+ autoar_compressor_set_output_is_dest (compressor, TRUE);
+
+ autoar_compressor_set_notify_interval (compressor,
+ PROGRESS_NOTIFY_INTERVAL);
+
+ g_signal_connect (compressor, "progress",
+ G_CALLBACK (compress_job_on_progress), compress_job);
+ g_signal_connect (compressor, "error",
+ G_CALLBACK (compress_job_on_error), compress_job);
+ g_signal_connect (compressor, "completed",
+ G_CALLBACK (compress_job_on_completed), compress_job);
+ autoar_compressor_start (compressor,
+ compress_job->common.cancellable);
+
+ compress_job->success = g_file_query_exists (compress_job->output_file,
+ NULL);
+
+ /* There is nothing to undo if the output was not created */
+ if (compress_job->common.undo_info != NULL && !compress_job->success)
+ {
+ g_clear_object (&compress_job->common.undo_info);
+ }
+}
+
+void
+nautilus_file_operations_compress (GList *files,
+ GFile *output,
+ AutoarFormat format,
+ AutoarFilter filter,
+ const gchar *passphrase,
+ GtkWindow *parent_window,
+ NautilusFileOperationsDBusData *dbus_data,
+ NautilusCreateCallback done_callback,
+ gpointer done_callback_data)
+{
+ g_autoptr (GTask) task = NULL;
+ CompressJob *compress_job;
+
+ compress_job = op_job_new (CompressJob, parent_window, dbus_data);
+ compress_job->source_files = g_list_copy_deep (files,
+ (GCopyFunc) g_object_ref,
+ NULL);
+ compress_job->output_file = g_object_ref (output);
+ compress_job->format = format;
+ compress_job->filter = filter;
+ compress_job->passphrase = g_strdup (passphrase);
+ compress_job->done_callback = done_callback;
+ compress_job->done_callback_data = done_callback_data;
+
+ inhibit_power_manager ((CommonJob *) compress_job, _("Compressing Files"));
+
+ if (!nautilus_file_undo_manager_is_operating ())
+ {
+ compress_job->common.undo_info = nautilus_file_undo_info_compress_new (files,
+ output,
+ format,
+ filter,
+ passphrase);
+ }
+
+ task = g_task_new (NULL, compress_job->common.cancellable,
+ compress_task_done, compress_job);
+ g_task_set_task_data (task, compress_job, NULL);
+ g_task_run_in_thread (task, compress_task_thread_func);
+}
+
+#if !defined (NAUTILUS_OMIT_SELF_CHECK)
+
+void
+nautilus_self_check_file_operations (void)
+{
+ setlocale (LC_MESSAGES, "C");
+
+
+ /* test the next duplicate name generator */
+ EEL_CHECK_STRING_RESULT (get_duplicate_name (" (copy)", 1, -1, FALSE), " (another copy)");
+ EEL_CHECK_STRING_RESULT (get_duplicate_name ("foo", 1, -1, FALSE), "foo (copy)");
+ EEL_CHECK_STRING_RESULT (get_duplicate_name (".bashrc", 1, -1, FALSE), ".bashrc (copy)");
+ EEL_CHECK_STRING_RESULT (get_duplicate_name (".foo.txt", 1, -1, FALSE), ".foo (copy).txt");
+ EEL_CHECK_STRING_RESULT (get_duplicate_name ("foo foo", 1, -1, FALSE), "foo foo (copy)");
+ EEL_CHECK_STRING_RESULT (get_duplicate_name ("foo.txt", 1, -1, FALSE), "foo (copy).txt");
+ EEL_CHECK_STRING_RESULT (get_duplicate_name ("foo foo.txt", 1, -1, FALSE), "foo foo (copy).txt");
+ EEL_CHECK_STRING_RESULT (get_duplicate_name ("foo foo.txt txt", 1, -1, FALSE), "foo foo (copy).txt txt");
+ EEL_CHECK_STRING_RESULT (get_duplicate_name ("foo...txt", 1, -1, FALSE), "foo.. (copy).txt");
+ EEL_CHECK_STRING_RESULT (get_duplicate_name ("foo...", 1, -1, FALSE), "foo... (copy)");
+ EEL_CHECK_STRING_RESULT (get_duplicate_name ("foo. (copy)", 1, -1, FALSE), "foo. (another copy)");
+ EEL_CHECK_STRING_RESULT (get_duplicate_name ("foo (copy)", 1, -1, FALSE), "foo (another copy)");
+ EEL_CHECK_STRING_RESULT (get_duplicate_name ("foo (copy).txt", 1, -1, FALSE), "foo (another copy).txt");
+ EEL_CHECK_STRING_RESULT (get_duplicate_name ("foo (another copy)", 1, -1, FALSE), "foo (3rd copy)");
+ EEL_CHECK_STRING_RESULT (get_duplicate_name ("foo (another copy).txt", 1, -1, FALSE), "foo (3rd copy).txt");
+ EEL_CHECK_STRING_RESULT (get_duplicate_name ("foo foo (another copy).txt", 1, -1, FALSE), "foo foo (3rd copy).txt");
+ EEL_CHECK_STRING_RESULT (get_duplicate_name ("foo (13th copy)", 1, -1, FALSE), "foo (14th copy)");
+ EEL_CHECK_STRING_RESULT (get_duplicate_name ("foo (13th copy).txt", 1, -1, FALSE), "foo (14th copy).txt");
+ EEL_CHECK_STRING_RESULT (get_duplicate_name ("foo (21st copy)", 1, -1, FALSE), "foo (22nd copy)");
+ EEL_CHECK_STRING_RESULT (get_duplicate_name ("foo (21st copy).txt", 1, -1, FALSE), "foo (22nd copy).txt");
+ EEL_CHECK_STRING_RESULT (get_duplicate_name ("foo (22nd copy)", 1, -1, FALSE), "foo (23rd copy)");
+ EEL_CHECK_STRING_RESULT (get_duplicate_name ("foo (22nd copy).txt", 1, -1, FALSE), "foo (23rd copy).txt");
+ EEL_CHECK_STRING_RESULT (get_duplicate_name ("foo (23rd copy)", 1, -1, FALSE), "foo (24th copy)");
+ EEL_CHECK_STRING_RESULT (get_duplicate_name ("foo (23rd copy).txt", 1, -1, FALSE), "foo (24th copy).txt");
+ EEL_CHECK_STRING_RESULT (get_duplicate_name ("foo (24th copy)", 1, -1, FALSE), "foo (25th copy)");
+ EEL_CHECK_STRING_RESULT (get_duplicate_name ("foo (24th copy).txt", 1, -1, FALSE), "foo (25th copy).txt");
+ EEL_CHECK_STRING_RESULT (get_duplicate_name ("foo foo (24th copy)", 1, -1, FALSE), "foo foo (25th copy)");
+ EEL_CHECK_STRING_RESULT (get_duplicate_name ("foo foo (24th copy).txt", 1, -1, FALSE), "foo foo (25th copy).txt");
+ EEL_CHECK_STRING_RESULT (get_duplicate_name ("foo foo (100000000000000th copy).txt", 1, -1, FALSE), "foo foo (copy).txt");
+ EEL_CHECK_STRING_RESULT (get_duplicate_name ("foo (10th copy)", 1, -1, FALSE), "foo (11th copy)");
+ EEL_CHECK_STRING_RESULT (get_duplicate_name ("foo (10th copy).txt", 1, -1, FALSE), "foo (11th copy).txt");
+ EEL_CHECK_STRING_RESULT (get_duplicate_name ("foo (11th copy)", 1, -1, FALSE), "foo (12th copy)");
+ EEL_CHECK_STRING_RESULT (get_duplicate_name ("foo (11th copy).txt", 1, -1, FALSE), "foo (12th copy).txt");
+ EEL_CHECK_STRING_RESULT (get_duplicate_name ("foo (12th copy)", 1, -1, FALSE), "foo (13th copy)");
+ EEL_CHECK_STRING_RESULT (get_duplicate_name ("foo (12th copy).txt", 1, -1, FALSE), "foo (13th copy).txt");
+ EEL_CHECK_STRING_RESULT (get_duplicate_name ("foo (110th copy)", 1, -1, FALSE), "foo (111th copy)");
+ EEL_CHECK_STRING_RESULT (get_duplicate_name ("foo (110th copy).txt", 1, -1, FALSE), "foo (111th copy).txt");
+ EEL_CHECK_STRING_RESULT (get_duplicate_name ("foo (122nd copy)", 1, -1, FALSE), "foo (123rd copy)");
+ EEL_CHECK_STRING_RESULT (get_duplicate_name ("foo (122nd copy).txt", 1, -1, FALSE), "foo (123rd copy).txt");
+ EEL_CHECK_STRING_RESULT (get_duplicate_name ("foo (123rd copy)", 1, -1, FALSE), "foo (124th copy)");
+ EEL_CHECK_STRING_RESULT (get_duplicate_name ("foo (123rd copy).txt", 1, -1, FALSE), "foo (124th copy).txt");
+ EEL_CHECK_STRING_RESULT (get_duplicate_name ("dir.with.dots", 1, -1, TRUE), "dir.with.dots (copy)");
+ EEL_CHECK_STRING_RESULT (get_duplicate_name ("dir (copy).dir", 1, -1, TRUE), "dir (another copy).dir");
+
+ setlocale (LC_MESSAGES, "");
+}
+
+#endif
diff --git a/src/nautilus-file-operations.h b/src/nautilus-file-operations.h
new file mode 100644
index 0000000..14d664f
--- /dev/null
+++ b/src/nautilus-file-operations.h
@@ -0,0 +1,166 @@
+
+/* nautilus-file-operations: execute file operations.
+
+ 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 <ettore@gnu.org>,
+ Pavel Cisler <pavel@eazel.com>
+*/
+
+#pragma once
+
+#include <gtk/gtk.h>
+#include <gio/gio.h>
+#include <gnome-autoar/gnome-autoar.h>
+
+#include "nautilus-file-operations-dbus-data.h"
+
+#define SECONDS_NEEDED_FOR_APROXIMATE_TRANSFER_RATE 1
+
+typedef void (* NautilusCopyCallback) (GHashTable *debuting_uris,
+ gboolean success,
+ gpointer callback_data);
+typedef void (* NautilusCreateCallback) (GFile *new_file,
+ gboolean success,
+ gpointer callback_data);
+typedef void (* NautilusOpCallback) (gboolean success,
+ gpointer callback_data);
+typedef void (* NautilusDeleteCallback) (GHashTable *debuting_uris,
+ gboolean user_cancel,
+ gpointer callback_data);
+typedef void (* NautilusMountCallback) (GVolume *volume,
+ gboolean success,
+ GObject *callback_data_object);
+typedef void (* NautilusUnmountCallback) (gpointer callback_data);
+typedef void (* NautilusExtractCallback) (GList *outputs,
+ gpointer callback_data);
+
+/* FIXME: int copy_action should be an enum */
+
+void nautilus_file_operations_copy_move (const GList *item_uris,
+ const char *target_dir_uri,
+ GdkDragAction copy_action,
+ GtkWidget *parent_view,
+ NautilusFileOperationsDBusData *dbus_data,
+ NautilusCopyCallback done_callback,
+ gpointer done_callback_data);
+void nautilus_file_operations_empty_trash (GtkWidget *parent_view,
+ gboolean ask_confirmation,
+ NautilusFileOperationsDBusData *dbus_data);
+void nautilus_file_operations_new_folder (GtkWidget *parent_view,
+ NautilusFileOperationsDBusData *dbus_data,
+ const char *parent_dir_uri,
+ const char *folder_name,
+ NautilusCreateCallback done_callback,
+ gpointer done_callback_data);
+void nautilus_file_operations_new_file (GtkWidget *parent_view,
+ const char *parent_dir,
+ const char *target_filename,
+ const char *initial_contents,
+ int length,
+ NautilusCreateCallback done_callback,
+ gpointer data);
+void nautilus_file_operations_new_file_from_template (GtkWidget *parent_view,
+ const char *parent_dir,
+ const char *target_filename,
+ const char *template_uri,
+ NautilusCreateCallback done_callback,
+ gpointer data);
+
+void nautilus_file_operations_trash_or_delete_sync (GList *files);
+void nautilus_file_operations_delete_sync (GList *files);
+void nautilus_file_operations_trash_or_delete_async (GList *files,
+ GtkWindow *parent_window,
+ NautilusFileOperationsDBusData *dbus_data,
+ NautilusDeleteCallback done_callback,
+ gpointer done_callback_data);
+void nautilus_file_operations_delete_async (GList *files,
+ GtkWindow *parent_window,
+ NautilusFileOperationsDBusData *dbus_data,
+ NautilusDeleteCallback done_callback,
+ gpointer done_callback_data);
+
+void nautilus_file_set_permissions_recursive (const char *directory,
+ guint32 file_permissions,
+ guint32 file_mask,
+ guint32 folder_permissions,
+ guint32 folder_mask,
+ NautilusOpCallback callback,
+ gpointer callback_data);
+
+void nautilus_file_operations_unmount_mount (GtkWindow *parent_window,
+ GMount *mount,
+ gboolean eject,
+ gboolean check_trash);
+void nautilus_file_operations_unmount_mount_full (GtkWindow *parent_window,
+ GMount *mount,
+ GMountOperation *mount_operation,
+ gboolean eject,
+ gboolean check_trash,
+ NautilusUnmountCallback callback,
+ gpointer callback_data);
+void nautilus_file_operations_mount_volume (GtkWindow *parent_window,
+ GVolume *volume);
+void nautilus_file_operations_mount_volume_full (GtkWindow *parent_window,
+ GVolume *volume,
+ NautilusMountCallback mount_callback,
+ GObject *mount_callback_data_object);
+
+void nautilus_file_operations_copy_async (GList *files,
+ GFile *target_dir,
+ GtkWindow *parent_window,
+ NautilusFileOperationsDBusData *dbus_data,
+ NautilusCopyCallback done_callback,
+ gpointer done_callback_data);
+void nautilus_file_operations_copy_sync (GList *files,
+ GFile *target_dir);
+
+void nautilus_file_operations_move_async (GList *files,
+ GFile *target_dir,
+ GtkWindow *parent_window,
+ NautilusFileOperationsDBusData *dbus_data,
+ NautilusCopyCallback done_callback,
+ gpointer done_callback_data);
+void nautilus_file_operations_move_sync (GList *files,
+ GFile *target_dir);
+
+void nautilus_file_operations_duplicate (GList *files,
+ GtkWindow *parent_window,
+ NautilusFileOperationsDBusData *dbus_data,
+ NautilusCopyCallback done_callback,
+ gpointer done_callback_data);
+void nautilus_file_operations_link (GList *files,
+ GFile *target_dir,
+ GtkWindow *parent_window,
+ NautilusFileOperationsDBusData *dbus_data,
+ NautilusCopyCallback done_callback,
+ gpointer done_callback_data);
+void nautilus_file_operations_extract_files (GList *files,
+ GFile *destination_directory,
+ GtkWindow *parent_window,
+ NautilusFileOperationsDBusData *dbus_data,
+ NautilusExtractCallback done_callback,
+ gpointer done_callback_data);
+void nautilus_file_operations_compress (GList *files,
+ GFile *output,
+ AutoarFormat format,
+ AutoarFilter filter,
+ const gchar *passphrase,
+ GtkWindow *parent_window,
+ NautilusFileOperationsDBusData *dbus_data,
+ NautilusCreateCallback done_callback,
+ gpointer done_callback_data);
diff --git a/src/nautilus-file-private.h b/src/nautilus-file-private.h
new file mode 100644
index 0000000..e575edb
--- /dev/null
+++ b/src/nautilus-file-private.h
@@ -0,0 +1,283 @@
+/*
+ nautilus-file-private.h:
+
+ Copyright (C) 1999, 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/>.
+
+ Author: Darin Adler <darin@bentspoon.com>
+*/
+
+#pragma once
+
+#include "nautilus-directory.h"
+#include "nautilus-file.h"
+#include "nautilus-monitor.h"
+#include "nautilus-file-undo-operations.h"
+#include <eel/eel-glib-extensions.h>
+
+#define NAUTILUS_FILE_DEFAULT_ATTRIBUTES \
+ "standard::*,access::*,mountable::*,time::*,unix::*,owner::*,selinux::*,thumbnail::*,id::filesystem,trash::orig-path,trash::deletion-date,metadata::*,recent::*"
+
+/* These are in the typical sort order. Known things come first, then
+ * things where we can't know, finally things where we don't yet know.
+ */
+typedef enum {
+ KNOWN,
+ UNKNOWABLE,
+ UNKNOWN
+} Knowledge;
+
+struct NautilusFileDetails
+{
+ NautilusDirectory *directory;
+
+ GRefString *name;
+
+ /* File info: */
+ GFileType type;
+
+ GRefString *display_name;
+ char *display_name_collation_key;
+ char *directory_name_collation_key;
+ GRefString *edit_name;
+
+ goffset size; /* -1 is unknown */
+
+ int sort_order;
+
+ guint32 permissions;
+ int uid; /* -1 is none */
+ int gid; /* -1 is none */
+
+ GRefString *owner;
+ GRefString *owner_real;
+ GRefString *group;
+
+ time_t atime; /* 0 is unknown */
+ time_t mtime; /* 0 is unknown */
+ time_t btime; /* 0 is unknown */
+
+ char *symlink_name;
+
+ GRefString *mime_type;
+
+ char *selinux_context;
+ char *description;
+
+ GError *get_info_error;
+
+ guint directory_count;
+
+ guint deep_directory_count;
+ guint deep_file_count;
+ guint deep_unreadable_count;
+ goffset deep_size;
+
+ GIcon *icon;
+
+ char *thumbnail_path;
+ GdkPixbuf *thumbnail;
+ time_t thumbnail_mtime;
+
+ GList *mime_list; /* If this is a directory, the list of MIME types in it. */
+
+ /* Info you might get from a link (.desktop, .directory or nautilus link) */
+ GIcon *custom_icon;
+ char *activation_uri;
+
+ /* used during DND, for checking whether source and destination are on
+ * the same file system.
+ */
+ GRefString *filesystem_id;
+
+ char *trash_orig_path;
+
+ /* The following is for file operations in progress. Since
+ * there are normally only a few of these, we can move them to
+ * a separate hash table or something if required to keep the
+ * file objects small.
+ */
+ GList *operations_in_progress;
+
+ /* NautilusInfoProviders that need to be run for this file */
+ GList *pending_info_providers;
+
+ /* Emblems provided by extensions */
+ GList *extension_emblems;
+ GList *pending_extension_emblems;
+
+ /* Attributes provided by extensions */
+ GHashTable *extension_attributes;
+ GHashTable *pending_extension_attributes;
+
+ GHashTable *metadata;
+
+ /* Mount for mountpoint or the references GMount for a "mountable" */
+ GMount *mount;
+
+ /* boolean fields: bitfield to save space, since there can be
+ many NautilusFile objects. */
+
+ eel_boolean_bit unconfirmed : 1;
+ eel_boolean_bit is_gone : 1;
+ /* Set when emitting files_added on the directory to make sure we
+ add a file, and only once */
+ eel_boolean_bit is_added : 1;
+ /* Set by the NautilusDirectory while it's loading the file
+ * list so the file knows not to do redundant I/O.
+ */
+ eel_boolean_bit loading_directory : 1;
+ eel_boolean_bit got_file_info : 1;
+ eel_boolean_bit get_info_failed : 1;
+ eel_boolean_bit file_info_is_up_to_date : 1;
+
+ eel_boolean_bit got_directory_count : 1;
+ eel_boolean_bit directory_count_failed : 1;
+ eel_boolean_bit directory_count_is_up_to_date : 1;
+
+ eel_boolean_bit deep_counts_status : 2; /* NautilusRequestStatus */
+ /* no deep_counts_are_up_to_date field; since we expose
+ intermediate values for this attribute, we do actually
+ forget it rather than invalidating. */
+
+ eel_boolean_bit got_mime_list : 1;
+ eel_boolean_bit mime_list_failed : 1;
+ eel_boolean_bit mime_list_is_up_to_date : 1;
+
+ eel_boolean_bit mount_is_up_to_date : 1;
+
+ eel_boolean_bit got_custom_display_name : 1;
+ eel_boolean_bit got_custom_activation_uri : 1;
+
+ eel_boolean_bit thumbnail_is_up_to_date : 1;
+ eel_boolean_bit thumbnailing_failed : 1;
+
+ eel_boolean_bit is_thumbnailing : 1;
+
+ eel_boolean_bit is_symlink : 1;
+ eel_boolean_bit is_mountpoint : 1;
+ eel_boolean_bit is_hidden : 1;
+
+ eel_boolean_bit has_permissions : 1;
+
+ eel_boolean_bit can_read : 1;
+ eel_boolean_bit can_write : 1;
+ eel_boolean_bit can_execute : 1;
+ eel_boolean_bit can_delete : 1;
+ eel_boolean_bit can_trash : 1;
+ eel_boolean_bit can_rename : 1;
+ eel_boolean_bit can_mount : 1;
+ eel_boolean_bit can_unmount : 1;
+ eel_boolean_bit can_eject : 1;
+ eel_boolean_bit can_start : 1;
+ eel_boolean_bit can_start_degraded : 1;
+ eel_boolean_bit can_stop : 1;
+ eel_boolean_bit start_stop_type : 3; /* GDriveStartStopType */
+ eel_boolean_bit can_poll_for_media : 1;
+ eel_boolean_bit is_media_check_automatic : 1;
+
+ eel_boolean_bit filesystem_readonly : 1;
+ eel_boolean_bit filesystem_use_preview : 2; /* GFilesystemPreviewType */
+ eel_boolean_bit filesystem_info_is_up_to_date : 1;
+ eel_boolean_bit filesystem_remote : 1;
+ GRefString *filesystem_type;
+
+ time_t trash_time; /* 0 is unknown */
+ time_t recency; /* 0 is unknown */
+
+ gdouble search_relevance;
+ gchar *fts_snippet;
+
+ guint64 free_space; /* (guint)-1 for unknown */
+ time_t free_space_read; /* The time free_space was updated, or 0 for never */
+};
+
+typedef struct {
+ NautilusFile *file;
+ GList *files;
+ gint renamed_files;
+ gint skipped_files;
+ GCancellable *cancellable;
+ NautilusFileOperationCallback callback;
+ gpointer callback_data;
+ gboolean is_rename;
+
+ gpointer data;
+ GDestroyNotify free_data;
+ NautilusFileUndoInfo *undo_info;
+} NautilusFileOperation;
+
+NautilusFile *nautilus_file_new_from_info (NautilusDirectory *directory,
+ GFileInfo *info);
+void nautilus_file_emit_changed (NautilusFile *file);
+void nautilus_file_mark_gone (NautilusFile *file);
+
+gboolean nautilus_file_get_date (NautilusFile *file,
+ NautilusDateType date_type,
+ time_t *date);
+void nautilus_file_updated_deep_count_in_progress (NautilusFile *file);
+
+
+void nautilus_file_clear_info (NautilusFile *file);
+/* Compare file's state with a fresh file info struct, return FALSE if
+ * no change, update file and return TRUE if the file info contains
+ * new state. */
+gboolean nautilus_file_update_info (NautilusFile *file,
+ GFileInfo *info);
+gboolean nautilus_file_update_name (NautilusFile *file,
+ const char *name);
+gboolean nautilus_file_update_metadata_from_info (NautilusFile *file,
+ GFileInfo *info);
+
+gboolean nautilus_file_update_name_and_directory (NautilusFile *file,
+ const char *name,
+ NautilusDirectory *directory);
+
+gboolean nautilus_file_set_display_name (NautilusFile *file,
+ const char *display_name,
+ const char *edit_name,
+ gboolean custom);
+NautilusDirectory *
+ nautilus_file_get_directory (NautilusFile *file);
+void nautilus_file_set_directory (NautilusFile *file,
+ NautilusDirectory *directory);
+void nautilus_file_set_mount (NautilusFile *file,
+ GMount *mount);
+
+/* Mark specified attributes for this file out of date without canceling current
+ * I/O or kicking off new I/O.
+ */
+void nautilus_file_invalidate_attributes_internal (NautilusFile *file,
+ NautilusFileAttributes file_attributes);
+NautilusFileAttributes nautilus_file_get_all_attributes (void);
+gboolean nautilus_file_is_self_owned (NautilusFile *file);
+void nautilus_file_invalidate_count_and_mime_list (NautilusFile *file);
+gboolean nautilus_file_rename_in_progress (NautilusFile *file);
+void nautilus_file_invalidate_extension_info_internal (NautilusFile *file);
+void nautilus_file_info_providers_done (NautilusFile *file);
+
+
+/* Thumbnailing: */
+void nautilus_file_set_is_thumbnailing (NautilusFile *file,
+ gboolean is_thumbnailing);
+
+NautilusFileOperation *nautilus_file_operation_new (NautilusFile *file,
+ NautilusFileOperationCallback callback,
+ gpointer callback_data);
+void nautilus_file_operation_free (NautilusFileOperation *op);
+void nautilus_file_operation_complete (NautilusFileOperation *op,
+ GFile *result_location,
+ GError *error);
+void nautilus_file_operation_cancel (NautilusFileOperation *op);
diff --git a/src/nautilus-file-queue.c b/src/nautilus-file-queue.c
new file mode 100644
index 0000000..026ded1
--- /dev/null
+++ b/src/nautilus-file-queue.c
@@ -0,0 +1,130 @@
+/*
+ * Copyright (C) 2001 Maciej Stachowiak
+ *
+ * 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/>.
+ *
+ * Author: Maciej Stachowiak <mjs@noisehavoc.org>
+ */
+
+#include <config.h>
+#include "nautilus-file-queue.h"
+
+#include <glib.h>
+
+struct NautilusFileQueue
+{
+ GList *head;
+ GList *tail;
+ GHashTable *item_to_link_map;
+};
+
+NautilusFileQueue *
+nautilus_file_queue_new (void)
+{
+ NautilusFileQueue *queue;
+
+ queue = g_new0 (NautilusFileQueue, 1);
+ queue->item_to_link_map = g_hash_table_new (g_direct_hash, g_direct_equal);
+
+ return queue;
+}
+
+void
+nautilus_file_queue_destroy (NautilusFileQueue *queue)
+{
+ g_hash_table_destroy (queue->item_to_link_map);
+ nautilus_file_list_free (queue->head);
+ g_free (queue);
+}
+
+void
+nautilus_file_queue_enqueue (NautilusFileQueue *queue,
+ NautilusFile *file)
+{
+ if (g_hash_table_lookup (queue->item_to_link_map, file) != NULL)
+ {
+ /* It's already on the queue. */
+ return;
+ }
+
+ if (queue->tail == NULL)
+ {
+ queue->head = g_list_append (NULL, file);
+ queue->tail = queue->head;
+ }
+ else
+ {
+ queue->tail = g_list_append (queue->tail, file);
+ queue->tail = queue->tail->next;
+ }
+
+ nautilus_file_ref (file);
+ g_hash_table_insert (queue->item_to_link_map, file, queue->tail);
+}
+
+NautilusFile *
+nautilus_file_queue_dequeue (NautilusFileQueue *queue)
+{
+ NautilusFile *file;
+
+ file = nautilus_file_queue_head (queue);
+ nautilus_file_queue_remove (queue, file);
+
+ return file;
+}
+
+
+void
+nautilus_file_queue_remove (NautilusFileQueue *queue,
+ NautilusFile *file)
+{
+ GList *link;
+
+ link = g_hash_table_lookup (queue->item_to_link_map, file);
+
+ if (link == NULL)
+ {
+ /* It's not on the queue */
+ return;
+ }
+
+ if (link == queue->tail)
+ {
+ /* Need to special-case removing the tail. */
+ queue->tail = queue->tail->prev;
+ }
+
+ queue->head = g_list_remove_link (queue->head, link);
+ g_list_free (link);
+ g_hash_table_remove (queue->item_to_link_map, file);
+
+ nautilus_file_unref (file);
+}
+
+NautilusFile *
+nautilus_file_queue_head (NautilusFileQueue *queue)
+{
+ if (queue->head == NULL)
+ {
+ return NULL;
+ }
+
+ return NAUTILUS_FILE (queue->head->data);
+}
+
+gboolean
+nautilus_file_queue_is_empty (NautilusFileQueue *queue)
+{
+ return (queue->head == NULL);
+}
diff --git a/src/nautilus-file-queue.h b/src/nautilus-file-queue.h
new file mode 100644
index 0000000..28c7b17
--- /dev/null
+++ b/src/nautilus-file-queue.h
@@ -0,0 +1,46 @@
+/*
+ Copyright (C) 2001 Maciej Stachowiak
+
+ 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/>.
+
+ Author: Maciej Stachowiak <mjs@noisehavoc.org>
+*/
+
+#pragma once
+
+#include "nautilus-file.h"
+
+typedef struct NautilusFileQueue NautilusFileQueue;
+
+NautilusFileQueue *nautilus_file_queue_new (void);
+void nautilus_file_queue_destroy (NautilusFileQueue *queue);
+
+/* Add a file to the tail of the queue, unless it's already in the queue */
+void nautilus_file_queue_enqueue (NautilusFileQueue *queue,
+ NautilusFile *file);
+
+/* Return the file at the head of the queue after removing it from the
+ * queue. This is dangerous unless you have another ref to the file,
+ * since it will unref it.
+ */
+NautilusFile * nautilus_file_queue_dequeue (NautilusFileQueue *queue);
+
+/* Remove a file from an arbitrary point in the queue in constant time. */
+void nautilus_file_queue_remove (NautilusFileQueue *queue,
+ NautilusFile *file);
+
+/* Get the file at the head of the queue without removing or unrefing it. */
+NautilusFile * nautilus_file_queue_head (NautilusFileQueue *queue);
+
+gboolean nautilus_file_queue_is_empty (NautilusFileQueue *queue);
diff --git a/src/nautilus-file-undo-manager.c b/src/nautilus-file-undo-manager.c
new file mode 100644
index 0000000..f2d1eeb
--- /dev/null
+++ b/src/nautilus-file-undo-manager.c
@@ -0,0 +1,270 @@
+/* nautilus-file-undo-manager.c - Manages the undo/redo stack
+ *
+ * Copyright (C) 2007-2011 Amos Brocco
+ * Copyright (C) 2010, 2012 Red Hat, Inc.
+ *
+ * This library 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 library 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 library; if not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Amos Brocco <amos.brocco@gmail.com>
+ * Cosimo Cecchi <cosimoc@redhat.com>
+ *
+ */
+
+#include <config.h>
+
+#include "nautilus-file-undo-manager.h"
+
+#include "nautilus-file-operations.h"
+#include "nautilus-file.h"
+#include "nautilus-trash-monitor.h"
+
+#include <glib/gi18n.h>
+
+#define DEBUG_FLAG NAUTILUS_DEBUG_UNDO
+#include "nautilus-debug.h"
+
+enum
+{
+ SIGNAL_UNDO_CHANGED,
+ NUM_SIGNALS,
+};
+
+static guint signals[NUM_SIGNALS] = { 0, };
+
+struct _NautilusFileUndoManager
+{
+ GObject parent_instance;
+ NautilusFileUndoInfo *info;
+ NautilusFileUndoManagerState state;
+ NautilusFileUndoManagerState last_state;
+
+ guint is_operating : 1;
+
+ gulong trash_signal_id;
+};
+
+G_DEFINE_TYPE (NautilusFileUndoManager, nautilus_file_undo_manager, G_TYPE_OBJECT)
+
+static NautilusFileUndoManager *undo_singleton = NULL;
+
+NautilusFileUndoManager *
+nautilus_file_undo_manager_new (void)
+{
+ if (undo_singleton != NULL)
+ {
+ return g_object_ref (undo_singleton);
+ }
+
+ undo_singleton = g_object_new (NAUTILUS_TYPE_FILE_UNDO_MANAGER, NULL);
+ g_object_add_weak_pointer (G_OBJECT (undo_singleton), (gpointer) & undo_singleton);
+
+ return undo_singleton;
+}
+
+static void
+file_undo_manager_clear (NautilusFileUndoManager *self)
+{
+ g_clear_object (&self->info);
+ self->state = NAUTILUS_FILE_UNDO_MANAGER_STATE_NONE;
+}
+
+static void
+trash_state_changed_cb (NautilusTrashMonitor *monitor,
+ gboolean is_empty,
+ gpointer user_data)
+{
+ NautilusFileUndoManager *self = user_data;
+
+ /* A trash operation cannot be undone if the trash is empty */
+ if (is_empty &&
+ self->state == NAUTILUS_FILE_UNDO_MANAGER_STATE_UNDO &&
+ NAUTILUS_IS_FILE_UNDO_INFO_TRASH (self->info))
+ {
+ file_undo_manager_clear (self);
+ g_signal_emit (self, signals[SIGNAL_UNDO_CHANGED], 0);
+ }
+}
+
+static void
+nautilus_file_undo_manager_init (NautilusFileUndoManager *self)
+{
+ self->trash_signal_id = g_signal_connect (nautilus_trash_monitor_get (),
+ "trash-state-changed",
+ G_CALLBACK (trash_state_changed_cb), self);
+}
+
+static void
+nautilus_file_undo_manager_finalize (GObject *object)
+{
+ NautilusFileUndoManager *self = NAUTILUS_FILE_UNDO_MANAGER (object);
+
+ g_clear_signal_handler (&self->trash_signal_id, nautilus_trash_monitor_get ());
+
+ file_undo_manager_clear (self);
+
+ G_OBJECT_CLASS (nautilus_file_undo_manager_parent_class)->finalize (object);
+}
+
+static void
+nautilus_file_undo_manager_class_init (NautilusFileUndoManagerClass *klass)
+{
+ GObjectClass *oclass;
+
+ oclass = G_OBJECT_CLASS (klass);
+
+ oclass->finalize = nautilus_file_undo_manager_finalize;
+
+ signals[SIGNAL_UNDO_CHANGED] =
+ g_signal_new ("undo-changed",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST,
+ 0, NULL, NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+}
+
+static void
+undo_info_apply_ready (GObject *source,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ NautilusFileUndoManager *self = user_data;
+ NautilusFileUndoInfo *info = NAUTILUS_FILE_UNDO_INFO (source);
+ gboolean success, user_cancel;
+
+ success = nautilus_file_undo_info_apply_finish (info, res, &user_cancel, NULL);
+
+ self->is_operating = FALSE;
+
+ /* just return in case we got another another operation set */
+ if ((self->info != NULL) &&
+ (self->info != info))
+ {
+ return;
+ }
+
+ if (success)
+ {
+ if (self->last_state == NAUTILUS_FILE_UNDO_MANAGER_STATE_UNDO)
+ {
+ self->state = NAUTILUS_FILE_UNDO_MANAGER_STATE_REDO;
+ }
+ else if (self->last_state == NAUTILUS_FILE_UNDO_MANAGER_STATE_REDO)
+ {
+ self->state = NAUTILUS_FILE_UNDO_MANAGER_STATE_UNDO;
+ }
+
+ self->info = g_object_ref (info);
+ }
+ else if (user_cancel)
+ {
+ self->state = self->last_state;
+ self->info = g_object_ref (info);
+ }
+ else
+ {
+ file_undo_manager_clear (self);
+ }
+
+ g_signal_emit (self, signals[SIGNAL_UNDO_CHANGED], 0);
+}
+
+static void
+do_undo_redo (NautilusFileUndoManager *self,
+ GtkWindow *parent_window,
+ NautilusFileOperationsDBusData *dbus_data)
+{
+ gboolean undo = self->state == NAUTILUS_FILE_UNDO_MANAGER_STATE_UNDO;
+
+ self->last_state = self->state;
+
+ self->is_operating = TRUE;
+ nautilus_file_undo_info_apply_async (self->info, undo, parent_window,
+ dbus_data,
+ undo_info_apply_ready, self);
+
+ /* clear actions while undoing */
+ file_undo_manager_clear (self);
+ g_signal_emit (self, signals[SIGNAL_UNDO_CHANGED], 0);
+}
+
+void
+nautilus_file_undo_manager_redo (GtkWindow *parent_window,
+ NautilusFileOperationsDBusData *dbus_data)
+{
+ if (undo_singleton->state != NAUTILUS_FILE_UNDO_MANAGER_STATE_REDO)
+ {
+ g_warning ("Called redo, but state is %s!", undo_singleton->state == 0 ?
+ "none" : "undo");
+ return;
+ }
+
+ do_undo_redo (undo_singleton, parent_window, dbus_data);
+}
+
+void
+nautilus_file_undo_manager_undo (GtkWindow *parent_window,
+ NautilusFileOperationsDBusData *dbus_data)
+{
+ if (undo_singleton->state != NAUTILUS_FILE_UNDO_MANAGER_STATE_UNDO)
+ {
+ g_warning ("Called undo, but state is %s!", undo_singleton->state == 0 ?
+ "none" : "redo");
+ return;
+ }
+
+ do_undo_redo (undo_singleton, parent_window, dbus_data);
+}
+
+void
+nautilus_file_undo_manager_set_action (NautilusFileUndoInfo *info)
+{
+ DEBUG ("Setting undo information %p", info);
+
+ file_undo_manager_clear (undo_singleton);
+
+ if (info != NULL)
+ {
+ undo_singleton->info = g_object_ref (info);
+ undo_singleton->state = NAUTILUS_FILE_UNDO_MANAGER_STATE_UNDO;
+ undo_singleton->last_state = NAUTILUS_FILE_UNDO_MANAGER_STATE_NONE;
+ }
+
+ g_signal_emit (undo_singleton, signals[SIGNAL_UNDO_CHANGED], 0);
+}
+
+NautilusFileUndoInfo *
+nautilus_file_undo_manager_get_action (void)
+{
+ return undo_singleton->info;
+}
+
+NautilusFileUndoManagerState
+nautilus_file_undo_manager_get_state (void)
+{
+ return undo_singleton->state;
+}
+
+
+gboolean
+nautilus_file_undo_manager_is_operating (void)
+{
+ return undo_singleton->is_operating;
+}
+
+NautilusFileUndoManager *
+nautilus_file_undo_manager_get (void)
+{
+ return undo_singleton;
+}
diff --git a/src/nautilus-file-undo-manager.h b/src/nautilus-file-undo-manager.h
new file mode 100644
index 0000000..7bf0309
--- /dev/null
+++ b/src/nautilus-file-undo-manager.h
@@ -0,0 +1,55 @@
+
+/* nautilus-file-undo-manager.h - Manages the undo/redo stack
+ *
+ * Copyright (C) 2007-2011 Amos Brocco
+ *
+ * This library 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 library 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 library; if not, see <http://www.gnu.org/licenses/>.
+ *
+ * Author: Amos Brocco <amos.brocco@gmail.com>
+ */
+
+#pragma once
+
+#include <glib.h>
+#include <glib-object.h>
+#include <gtk/gtk.h>
+#include <gio/gio.h>
+
+#include "nautilus-file-undo-operations.h"
+
+#define NAUTILUS_TYPE_FILE_UNDO_MANAGER\
+ (nautilus_file_undo_manager_get_type())
+
+G_DECLARE_FINAL_TYPE (NautilusFileUndoManager, nautilus_file_undo_manager, NAUTILUS, FILE_UNDO_MANAGER, GObject)
+
+typedef enum {
+ NAUTILUS_FILE_UNDO_MANAGER_STATE_NONE,
+ NAUTILUS_FILE_UNDO_MANAGER_STATE_UNDO,
+ NAUTILUS_FILE_UNDO_MANAGER_STATE_REDO
+} NautilusFileUndoManagerState;
+
+NautilusFileUndoManager *nautilus_file_undo_manager_new (void);
+NautilusFileUndoManager * nautilus_file_undo_manager_get (void);
+
+void nautilus_file_undo_manager_set_action (NautilusFileUndoInfo *info);
+NautilusFileUndoInfo *nautilus_file_undo_manager_get_action (void);
+
+NautilusFileUndoManagerState nautilus_file_undo_manager_get_state (void);
+
+void nautilus_file_undo_manager_undo (GtkWindow *parent_window,
+ NautilusFileOperationsDBusData *dbus_data);
+void nautilus_file_undo_manager_redo (GtkWindow *parent_window,
+ NautilusFileOperationsDBusData *dbus_data);
+
+gboolean nautilus_file_undo_manager_is_operating (void);
diff --git a/src/nautilus-file-undo-operations.c b/src/nautilus-file-undo-operations.c
new file mode 100644
index 0000000..962f7f4
--- /dev/null
+++ b/src/nautilus-file-undo-operations.c
@@ -0,0 +1,2639 @@
+/* nautilus-file-undo-operations.c - Manages undo/redo of file operations
+ *
+ * Copyright (C) 2007-2011 Amos Brocco
+ * Copyright (C) 2010, 2012 Red Hat, Inc.
+ *
+ * This library 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 library 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 library; if not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Amos Brocco <amos.brocco@gmail.com>
+ * Cosimo Cecchi <cosimoc@redhat.com>
+ *
+ */
+
+#include <stdlib.h>
+
+#include "nautilus-file-undo-operations.h"
+
+#include <glib/gi18n.h>
+
+#include "nautilus-file-operations.h"
+#include "nautilus-file.h"
+#include "nautilus-file-undo-manager.h"
+#include "nautilus-batch-rename-dialog.h"
+#include "nautilus-batch-rename-utilities.h"
+#include "nautilus-tag-manager.h"
+
+
+/* Since we use g_get_current_time for setting "orig_trash_time" in the undo
+ * info, there are situations where the difference between this value and the
+ * real deletion time can differ enough to make the rounding a difference of 1
+ * second, failing the equality check. To make sure we avoid this, and to be
+ * preventive, use 2 seconds epsilon.
+ */
+#define TRASH_TIME_EPSILON 2
+
+typedef struct
+{
+ NautilusFileUndoOp op_type;
+ guint count; /* Number of items */
+
+ GTask *apply_async_task;
+
+ gchar *undo_label;
+ gchar *redo_label;
+ gchar *undo_description;
+ gchar *redo_description;
+} NautilusFileUndoInfoPrivate;
+
+G_DEFINE_TYPE_WITH_PRIVATE (NautilusFileUndoInfo, nautilus_file_undo_info, G_TYPE_OBJECT)
+
+enum
+{
+ PROP_OP_TYPE = 1,
+ PROP_ITEM_COUNT,
+ N_PROPERTIES
+};
+
+static GParamSpec *properties[N_PROPERTIES] = { NULL, };
+
+/* description helpers */
+static void
+nautilus_file_undo_info_init (NautilusFileUndoInfo *self)
+{
+ NautilusFileUndoInfoPrivate *priv;
+
+ priv = nautilus_file_undo_info_get_instance_private (self);
+
+ priv->apply_async_task = NULL;
+}
+
+static void
+nautilus_file_undo_info_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ NautilusFileUndoInfo *self;
+ NautilusFileUndoInfoPrivate *priv;
+
+ self = NAUTILUS_FILE_UNDO_INFO (object);
+ priv = nautilus_file_undo_info_get_instance_private (self);
+
+ switch (property_id)
+ {
+ case PROP_OP_TYPE:
+ {
+ g_value_set_int (value, priv->op_type);
+ }
+ break;
+
+ case PROP_ITEM_COUNT:
+ {
+ g_value_set_int (value, priv->count);
+ }
+ break;
+
+ default:
+ {
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ }
+ break;
+ }
+}
+
+static void
+nautilus_file_undo_info_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ NautilusFileUndoInfo *self;
+ NautilusFileUndoInfoPrivate *priv;
+
+ self = NAUTILUS_FILE_UNDO_INFO (object);
+ priv = nautilus_file_undo_info_get_instance_private (self);
+
+ switch (property_id)
+ {
+ case PROP_OP_TYPE:
+ {
+ priv->op_type = g_value_get_int (value);
+ }
+ break;
+
+ case PROP_ITEM_COUNT:
+ {
+ priv->count = g_value_get_int (value);
+ }
+ break;
+
+ default:
+ {
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ }
+ break;
+ }
+}
+
+static void
+nautilus_file_redo_info_warn_redo (NautilusFileUndoInfo *self,
+ GtkWindow *parent_window,
+ NautilusFileOperationsDBusData *dbus_data)
+{
+ g_critical ("Object %p of type %s does not implement redo_func!!",
+ self, G_OBJECT_TYPE_NAME (self));
+}
+
+static void
+nautilus_file_undo_info_warn_undo (NautilusFileUndoInfo *self,
+ GtkWindow *parent_window,
+ NautilusFileOperationsDBusData *dbus_data)
+{
+ g_critical ("Object %p of type %s does not implement undo_func!!",
+ self, G_OBJECT_TYPE_NAME (self));
+}
+
+static void
+nautilus_file_undo_info_strings_func (NautilusFileUndoInfo *self,
+ gchar **undo_label,
+ gchar **undo_description,
+ gchar **redo_label,
+ gchar **redo_description)
+{
+ if (undo_label != NULL)
+ {
+ *undo_label = g_strdup (_("Undo"));
+ }
+ if (undo_description != NULL)
+ {
+ *undo_description = g_strdup (_("Undo last action"));
+ }
+
+ if (redo_label != NULL)
+ {
+ *redo_label = g_strdup (_("Redo"));
+ }
+ if (redo_description != NULL)
+ {
+ *redo_description = g_strdup (_("Redo last undone action"));
+ }
+}
+
+static void
+nautilus_file_undo_info_finalize (GObject *object)
+{
+ NautilusFileUndoInfo *self;
+ NautilusFileUndoInfoPrivate *priv;
+
+ self = NAUTILUS_FILE_UNDO_INFO (object);
+ priv = nautilus_file_undo_info_get_instance_private (self);
+
+ g_clear_object (&priv->apply_async_task);
+
+ G_OBJECT_CLASS (nautilus_file_undo_info_parent_class)->finalize (object);
+}
+
+static void
+nautilus_file_undo_info_class_init (NautilusFileUndoInfoClass *klass)
+{
+ GObjectClass *oclass = G_OBJECT_CLASS (klass);
+
+ oclass->finalize = nautilus_file_undo_info_finalize;
+ oclass->get_property = nautilus_file_undo_info_get_property;
+ oclass->set_property = nautilus_file_undo_info_set_property;
+
+ klass->undo_func = nautilus_file_undo_info_warn_undo;
+ klass->redo_func = nautilus_file_redo_info_warn_redo;
+ klass->strings_func = nautilus_file_undo_info_strings_func;
+
+ properties[PROP_OP_TYPE] =
+ g_param_spec_int ("op-type",
+ "Undo info op type",
+ "Type of undo operation",
+ 0, NAUTILUS_FILE_UNDO_OP_NUM_TYPES - 1, 0,
+ G_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT_ONLY);
+ properties[PROP_ITEM_COUNT] =
+ g_param_spec_int ("item-count",
+ "Number of items",
+ "Number of items",
+ 0, G_MAXINT, 0,
+ G_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT_ONLY);
+
+ g_object_class_install_properties (oclass, N_PROPERTIES, properties);
+}
+
+NautilusFileUndoOp
+nautilus_file_undo_info_get_op_type (NautilusFileUndoInfo *self)
+{
+ NautilusFileUndoInfoPrivate *priv;
+
+ g_return_val_if_fail (NAUTILUS_IS_FILE_UNDO_INFO (self), NAUTILUS_FILE_UNDO_OP_INVALID);
+
+ priv = nautilus_file_undo_info_get_instance_private (self);
+
+ return priv->op_type;
+}
+
+static gint
+nautilus_file_undo_info_get_item_count (NautilusFileUndoInfo *self)
+{
+ NautilusFileUndoInfoPrivate *priv;
+
+ priv = nautilus_file_undo_info_get_instance_private (self);
+
+ return priv->count;
+}
+
+void
+nautilus_file_undo_info_apply_async (NautilusFileUndoInfo *self,
+ gboolean undo,
+ GtkWindow *parent_window,
+ NautilusFileOperationsDBusData *dbus_data,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ NautilusFileUndoInfoPrivate *priv;
+
+ g_return_if_fail (NAUTILUS_IS_FILE_UNDO_INFO (self));
+
+ priv = nautilus_file_undo_info_get_instance_private (self);
+
+ g_assert (priv->apply_async_task == NULL);
+
+ priv->apply_async_task = g_task_new (G_OBJECT (self),
+ NULL,
+ callback,
+ user_data);
+
+ if (undo)
+ {
+ NAUTILUS_FILE_UNDO_INFO_CLASS (G_OBJECT_GET_CLASS (self))->undo_func (self,
+ parent_window,
+ dbus_data);
+ }
+ else
+ {
+ NAUTILUS_FILE_UNDO_INFO_CLASS (G_OBJECT_GET_CLASS (self))->redo_func (self,
+ parent_window,
+ dbus_data);
+ }
+}
+
+typedef struct
+{
+ gboolean success;
+ gboolean user_cancel;
+} FileUndoInfoOpRes;
+
+static void
+file_undo_info_op_res_free (gpointer data)
+{
+ g_slice_free (FileUndoInfoOpRes, data);
+}
+
+gboolean
+nautilus_file_undo_info_apply_finish (NautilusFileUndoInfo *self,
+ GAsyncResult *res,
+ gboolean *user_cancel,
+ GError **error)
+{
+ FileUndoInfoOpRes *op_res;
+ gboolean success = FALSE;
+
+ op_res = g_task_propagate_pointer (G_TASK (res), error);
+
+ if (op_res != NULL)
+ {
+ *user_cancel = op_res->user_cancel;
+ success = op_res->success;
+
+ file_undo_info_op_res_free (op_res);
+ }
+
+ return success;
+}
+
+void
+nautilus_file_undo_info_get_strings (NautilusFileUndoInfo *self,
+ gchar **undo_label,
+ gchar **undo_description,
+ gchar **redo_label,
+ gchar **redo_description)
+{
+ NAUTILUS_FILE_UNDO_INFO_CLASS (G_OBJECT_GET_CLASS (self))->strings_func (self,
+ undo_label, undo_description,
+ redo_label, redo_description);
+}
+
+static void
+file_undo_info_complete_apply (NautilusFileUndoInfo *self,
+ gboolean success,
+ gboolean user_cancel)
+{
+ NautilusFileUndoInfoPrivate *priv;
+ FileUndoInfoOpRes *op_res;
+
+ priv = nautilus_file_undo_info_get_instance_private (self);
+ op_res = g_slice_new0 (FileUndoInfoOpRes);
+
+ op_res->user_cancel = user_cancel;
+ op_res->success = success;
+
+ g_task_return_pointer (priv->apply_async_task, op_res,
+ file_undo_info_op_res_free);
+
+ g_clear_object (&priv->apply_async_task);
+}
+
+static void
+file_undo_info_transfer_callback (GHashTable *debuting_uris,
+ gboolean success,
+ gpointer user_data)
+{
+ NautilusFileUndoInfo *self = user_data;
+
+ /* TODO: we need to forward the cancelled state from
+ * the file operation to the file undo info object.
+ */
+ file_undo_info_complete_apply (self, success, FALSE);
+}
+
+static void
+file_undo_info_operation_callback (NautilusFile *file,
+ GFile *result_location,
+ GError *error,
+ gpointer user_data)
+{
+ NautilusFileUndoInfo *self = user_data;
+
+ file_undo_info_complete_apply (self, (error == NULL),
+ g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED));
+}
+
+static void
+file_undo_info_delete_callback (GHashTable *debuting_uris,
+ gboolean user_cancel,
+ gpointer user_data)
+{
+ NautilusFileUndoInfo *self = user_data;
+
+ file_undo_info_complete_apply (self,
+ !user_cancel,
+ user_cancel);
+}
+
+/* copy/move/duplicate/link/restore from trash */
+struct _NautilusFileUndoInfoExt
+{
+ NautilusFileUndoInfo parent_instance;
+
+ GFile *src_dir;
+ GFile *dest_dir;
+ GQueue *sources; /* Relative to src_dir */
+ GQueue *destinations; /* Relative to dest_dir */
+};
+
+G_DEFINE_TYPE (NautilusFileUndoInfoExt, nautilus_file_undo_info_ext, NAUTILUS_TYPE_FILE_UNDO_INFO)
+
+static char *
+ext_get_first_target_short_name (NautilusFileUndoInfoExt *self)
+{
+ GList *targets_first;
+ char *file_name = NULL;
+
+ targets_first = g_queue_peek_head_link (self->destinations);
+
+ if (targets_first != NULL &&
+ targets_first->data != NULL)
+ {
+ file_name = g_file_get_basename (targets_first->data);
+ }
+
+ return file_name;
+}
+
+static void
+ext_strings_func (NautilusFileUndoInfo *info,
+ gchar **undo_label,
+ gchar **undo_description,
+ gchar **redo_label,
+ gchar **redo_description)
+{
+ NautilusFileUndoInfoExt *self = NAUTILUS_FILE_UNDO_INFO_EXT (info);
+ NautilusFileUndoOp op_type = nautilus_file_undo_info_get_op_type (info);
+ gint count = nautilus_file_undo_info_get_item_count (info);
+ gchar *name = NULL, *source, *destination;
+
+ source = g_file_get_path (self->src_dir);
+ destination = g_file_get_path (self->dest_dir);
+
+ if (count <= 1)
+ {
+ name = ext_get_first_target_short_name (self);
+ }
+
+ if (op_type == NAUTILUS_FILE_UNDO_OP_MOVE)
+ {
+ if (count > 1)
+ {
+ *undo_description = g_strdup_printf (ngettext ("Move %d item back to “%s”",
+ "Move %d items back to “%s”", count),
+ count, source);
+ *redo_description = g_strdup_printf (ngettext ("Move %d item to “%s”",
+ "Move %d items to “%s”", count),
+ count, destination);
+
+ *undo_label = g_strdup_printf (ngettext ("_Undo Move %d item",
+ "_Undo Move %d items", count),
+ count);
+ *redo_label = g_strdup_printf (ngettext ("_Redo Move %d item",
+ "_Redo Move %d items", count),
+ count);
+ }
+ else
+ {
+ *undo_description = g_strdup_printf (_("Move “%s” back to “%s”"), name, source);
+ *redo_description = g_strdup_printf (_("Move “%s” to “%s”"), name, destination);
+
+ *undo_label = g_strdup (_("_Undo Move"));
+ *redo_label = g_strdup (_("_Redo Move"));
+ }
+ }
+ else if (op_type == NAUTILUS_FILE_UNDO_OP_RESTORE_FROM_TRASH)
+ {
+ *undo_label = g_strdup (_("_Undo Restore from Trash"));
+ *redo_label = g_strdup (_("_Redo Restore from Trash"));
+
+ if (count > 1)
+ {
+ *undo_description = g_strdup_printf (ngettext ("Move %d item back to trash",
+ "Move %d items back to trash", count),
+ count);
+ *redo_description = g_strdup_printf (ngettext ("Restore %d item from trash",
+ "Restore %d items from trash", count),
+ count);
+ }
+ else
+ {
+ *undo_description = g_strdup_printf (_("Move “%s” back to trash"), name);
+ *redo_description = g_strdup_printf (_("Restore “%s” from trash"), name);
+ }
+ }
+ else if (op_type == NAUTILUS_FILE_UNDO_OP_COPY)
+ {
+ if (count > 1)
+ {
+ *undo_description = g_strdup_printf (ngettext ("Delete %d copied item",
+ "Delete %d copied items", count),
+ count);
+ *redo_description = g_strdup_printf (ngettext ("Copy %d item to “%s”",
+ "Copy %d items to “%s”", count),
+ count, destination);
+
+ *undo_label = g_strdup_printf (ngettext ("_Undo Copy %d item",
+ "_Undo Copy %d items", count),
+ count);
+ *redo_label = g_strdup_printf (ngettext ("_Redo Copy %d item",
+ "_Redo Copy %d items", count),
+ count);
+ }
+ else
+ {
+ *undo_description = g_strdup_printf (_("Delete “%s”"), name);
+ *redo_description = g_strdup_printf (_("Copy “%s” to “%s”"), name, destination);
+
+ *undo_label = g_strdup (_("_Undo Copy"));
+ *redo_label = g_strdup (_("_Redo Copy"));
+ }
+ }
+ else if (op_type == NAUTILUS_FILE_UNDO_OP_DUPLICATE)
+ {
+ if (count > 1)
+ {
+ *undo_description = g_strdup_printf (ngettext ("Delete %d duplicated item",
+ "Delete %d duplicated items", count),
+ count);
+ *redo_description = g_strdup_printf (ngettext ("Duplicate %d item in “%s”",
+ "Duplicate %d items in “%s”", count),
+ count, destination);
+
+ *undo_label = g_strdup_printf (ngettext ("_Undo Duplicate %d item",
+ "_Undo Duplicate %d items", count),
+ count);
+ *redo_label = g_strdup_printf (ngettext ("_Redo Duplicate %d item",
+ "_Redo Duplicate %d items", count),
+ count);
+ }
+ else
+ {
+ *undo_description = g_strdup_printf (_("Delete “%s”"), name);
+ *redo_description = g_strdup_printf (_("Duplicate “%s” in “%s”"),
+ name, destination);
+
+ *undo_label = g_strdup (_("_Undo Duplicate"));
+ *redo_label = g_strdup (_("_Redo Duplicate"));
+ }
+ }
+ else if (op_type == NAUTILUS_FILE_UNDO_OP_CREATE_LINK)
+ {
+ if (count > 1)
+ {
+ *undo_description = g_strdup_printf (ngettext ("Delete links to %d item",
+ "Delete links to %d items", count),
+ count);
+ *redo_description = g_strdup_printf (ngettext ("Create links to %d item",
+ "Create links to %d items", count),
+ count);
+ }
+ else
+ {
+ *undo_description = g_strdup_printf (_("Delete link to “%s”"), name);
+ *redo_description = g_strdup_printf (_("Create link to “%s”"), name);
+
+ *undo_label = g_strdup (_("_Undo Create Link"));
+ *redo_label = g_strdup (_("_Redo Create Link"));
+ }
+ }
+ else
+ {
+ g_assert_not_reached ();
+ }
+
+ g_free (name);
+ g_free (source);
+ g_free (destination);
+}
+
+static void
+ext_create_link_redo_func (NautilusFileUndoInfoExt *self,
+ GtkWindow *parent_window,
+ NautilusFileOperationsDBusData *dbus_data)
+{
+ nautilus_file_operations_link (g_queue_peek_head_link (self->sources),
+ self->dest_dir,
+ parent_window,
+ dbus_data,
+ file_undo_info_transfer_callback,
+ self);
+}
+
+static void
+ext_duplicate_redo_func (NautilusFileUndoInfoExt *self,
+ GtkWindow *parent_window,
+ NautilusFileOperationsDBusData *dbus_data)
+{
+ nautilus_file_operations_duplicate (g_queue_peek_head_link (self->sources),
+ parent_window,
+ dbus_data,
+ file_undo_info_transfer_callback,
+ self);
+}
+
+static void
+ext_copy_redo_func (NautilusFileUndoInfoExt *self,
+ GtkWindow *parent_window,
+ NautilusFileOperationsDBusData *dbus_data)
+{
+ nautilus_file_operations_copy_async (g_queue_peek_head_link (self->sources),
+ self->dest_dir,
+ parent_window,
+ dbus_data,
+ file_undo_info_transfer_callback,
+ self);
+}
+
+static void
+ext_move_restore_redo_func (NautilusFileUndoInfoExt *self,
+ GtkWindow *parent_window,
+ NautilusFileOperationsDBusData *dbus_data)
+{
+ nautilus_file_operations_move_async (g_queue_peek_head_link (self->sources),
+ self->dest_dir,
+ parent_window,
+ dbus_data,
+ file_undo_info_transfer_callback,
+ self);
+}
+
+static void
+ext_redo_func (NautilusFileUndoInfo *info,
+ GtkWindow *parent_window,
+ NautilusFileOperationsDBusData *dbus_data)
+{
+ NautilusFileUndoInfoExt *self = NAUTILUS_FILE_UNDO_INFO_EXT (info);
+ NautilusFileUndoOp op_type = nautilus_file_undo_info_get_op_type (info);
+
+ if (op_type == NAUTILUS_FILE_UNDO_OP_MOVE ||
+ op_type == NAUTILUS_FILE_UNDO_OP_RESTORE_FROM_TRASH)
+ {
+ ext_move_restore_redo_func (self, parent_window, dbus_data);
+ }
+ else if (op_type == NAUTILUS_FILE_UNDO_OP_COPY)
+ {
+ ext_copy_redo_func (self, parent_window, dbus_data);
+ }
+ else if (op_type == NAUTILUS_FILE_UNDO_OP_DUPLICATE)
+ {
+ ext_duplicate_redo_func (self, parent_window, dbus_data);
+ }
+ else if (op_type == NAUTILUS_FILE_UNDO_OP_CREATE_LINK)
+ {
+ ext_create_link_redo_func (self, parent_window, dbus_data);
+ }
+ else
+ {
+ g_assert_not_reached ();
+ }
+}
+
+static void
+ext_restore_undo_func (NautilusFileUndoInfoExt *self,
+ GtkWindow *parent_window,
+ NautilusFileOperationsDBusData *dbus_data)
+{
+ nautilus_file_operations_trash_or_delete_async (g_queue_peek_head_link (self->destinations),
+ parent_window,
+ dbus_data,
+ file_undo_info_delete_callback,
+ self);
+}
+
+
+static void
+ext_move_undo_func (NautilusFileUndoInfoExt *self,
+ GtkWindow *parent_window,
+ NautilusFileOperationsDBusData *dbus_data)
+{
+ nautilus_file_operations_move_async (g_queue_peek_head_link (self->destinations),
+ self->src_dir,
+ parent_window,
+ dbus_data,
+ file_undo_info_transfer_callback,
+ self);
+}
+
+static void
+ext_copy_duplicate_undo_func (NautilusFileUndoInfoExt *self,
+ GtkWindow *parent_window,
+ NautilusFileOperationsDBusData *dbus_data)
+{
+ GList *files;
+
+ files = g_list_copy (g_queue_peek_head_link (self->destinations));
+ files = g_list_reverse (files); /* Deleting must be done in reverse */
+
+ nautilus_file_operations_delete_async (files, parent_window,
+ dbus_data,
+ file_undo_info_delete_callback, self);
+
+ g_list_free (files);
+}
+
+static void
+ext_undo_func (NautilusFileUndoInfo *info,
+ GtkWindow *parent_window,
+ NautilusFileOperationsDBusData *dbus_data)
+{
+ NautilusFileUndoInfoExt *self = NAUTILUS_FILE_UNDO_INFO_EXT (info);
+ NautilusFileUndoOp op_type = nautilus_file_undo_info_get_op_type (info);
+
+ if (op_type == NAUTILUS_FILE_UNDO_OP_COPY ||
+ op_type == NAUTILUS_FILE_UNDO_OP_DUPLICATE ||
+ op_type == NAUTILUS_FILE_UNDO_OP_CREATE_LINK)
+ {
+ ext_copy_duplicate_undo_func (self, parent_window, dbus_data);
+ }
+ else if (op_type == NAUTILUS_FILE_UNDO_OP_MOVE)
+ {
+ ext_move_undo_func (self, parent_window, dbus_data);
+ }
+ else if (op_type == NAUTILUS_FILE_UNDO_OP_RESTORE_FROM_TRASH)
+ {
+ ext_restore_undo_func (self, parent_window, dbus_data);
+ }
+ else
+ {
+ g_assert_not_reached ();
+ }
+}
+
+static void
+nautilus_file_undo_info_ext_init (NautilusFileUndoInfoExt *self)
+{
+}
+
+static void
+nautilus_file_undo_info_ext_finalize (GObject *obj)
+{
+ NautilusFileUndoInfoExt *self = NAUTILUS_FILE_UNDO_INFO_EXT (obj);
+
+ if (self->sources)
+ {
+ g_queue_free_full (self->sources, g_object_unref);
+ }
+
+ if (self->destinations)
+ {
+ g_queue_free_full (self->destinations, g_object_unref);
+ }
+
+ g_clear_object (&self->src_dir);
+ g_clear_object (&self->dest_dir);
+
+ G_OBJECT_CLASS (nautilus_file_undo_info_ext_parent_class)->finalize (obj);
+}
+
+static void
+nautilus_file_undo_info_ext_class_init (NautilusFileUndoInfoExtClass *klass)
+{
+ GObjectClass *oclass = G_OBJECT_CLASS (klass);
+ NautilusFileUndoInfoClass *iclass = NAUTILUS_FILE_UNDO_INFO_CLASS (klass);
+
+ oclass->finalize = nautilus_file_undo_info_ext_finalize;
+
+ iclass->undo_func = ext_undo_func;
+ iclass->redo_func = ext_redo_func;
+ iclass->strings_func = ext_strings_func;
+}
+
+NautilusFileUndoInfo *
+nautilus_file_undo_info_ext_new (NautilusFileUndoOp op_type,
+ gint item_count,
+ GFile *src_dir,
+ GFile *target_dir)
+{
+ NautilusFileUndoInfoExt *self;
+
+ self = g_object_new (NAUTILUS_TYPE_FILE_UNDO_INFO_EXT,
+ "op-type", op_type,
+ "item-count", item_count,
+ NULL);
+
+ self->src_dir = g_object_ref (src_dir);
+ self->dest_dir = g_object_ref (target_dir);
+ self->sources = g_queue_new ();
+ self->destinations = g_queue_new ();
+
+ return NAUTILUS_FILE_UNDO_INFO (self);
+}
+
+void
+nautilus_file_undo_info_ext_add_origin_target_pair (NautilusFileUndoInfoExt *self,
+ GFile *origin,
+ GFile *target)
+{
+ g_queue_push_tail (self->sources, g_object_ref (origin));
+ g_queue_push_tail (self->destinations, g_object_ref (target));
+}
+
+/* create new file/folder */
+struct _NautilusFileUndoInfoCreate
+{
+ NautilusFileUndoInfo parent_instance;
+
+ char *template;
+ GFile *target_file;
+ gint length;
+};
+
+G_DEFINE_TYPE (NautilusFileUndoInfoCreate, nautilus_file_undo_info_create, NAUTILUS_TYPE_FILE_UNDO_INFO)
+
+static void
+create_strings_func (NautilusFileUndoInfo *info,
+ gchar **undo_label,
+ gchar **undo_description,
+ gchar **redo_label,
+ gchar **redo_description)
+{
+ NautilusFileUndoInfoCreate *self = NAUTILUS_FILE_UNDO_INFO_CREATE (info);
+ NautilusFileUndoOp op_type = nautilus_file_undo_info_get_op_type (info);
+ char *name;
+
+ name = g_file_get_parse_name (self->target_file);
+ *undo_description = g_strdup_printf (_("Delete “%s”"), name);
+
+ if (op_type == NAUTILUS_FILE_UNDO_OP_CREATE_EMPTY_FILE)
+ {
+ *redo_description = g_strdup_printf (_("Create an empty file “%s”"), name);
+
+ *undo_label = g_strdup (_("_Undo Create Empty File"));
+ *redo_label = g_strdup (_("_Redo Create Empty File"));
+ }
+ else if (op_type == NAUTILUS_FILE_UNDO_OP_CREATE_FOLDER)
+ {
+ *redo_description = g_strdup_printf (_("Create a new folder “%s”"), name);
+
+ *undo_label = g_strdup (_("_Undo Create Folder"));
+ *redo_label = g_strdup (_("_Redo Create Folder"));
+ }
+ else if (op_type == NAUTILUS_FILE_UNDO_OP_CREATE_FILE_FROM_TEMPLATE)
+ {
+ *redo_description = g_strdup_printf (_("Create new file “%s” from template "), name);
+
+ *undo_label = g_strdup (_("_Undo Create from Template"));
+ *redo_label = g_strdup (_("_Redo Create from Template"));
+ }
+ else
+ {
+ g_assert_not_reached ();
+ }
+
+ g_free (name);
+}
+
+static void
+create_callback (GFile *new_file,
+ gboolean success,
+ gpointer callback_data)
+{
+ file_undo_info_transfer_callback (NULL, success, callback_data);
+}
+
+static void
+create_from_template_redo_func (NautilusFileUndoInfoCreate *self,
+ GtkWindow *parent_window,
+ NautilusFileOperationsDBusData *dbus_data)
+{
+ GFile *parent;
+ gchar *parent_uri, *new_name;
+
+ parent = g_file_get_parent (self->target_file);
+ parent_uri = g_file_get_uri (parent);
+ new_name = g_file_get_basename (self->target_file);
+ nautilus_file_operations_new_file_from_template (NULL,
+ parent_uri, new_name,
+ self->template,
+ create_callback, self);
+
+ g_free (parent_uri);
+ g_free (new_name);
+ g_object_unref (parent);
+}
+
+static void
+create_folder_redo_func (NautilusFileUndoInfoCreate *self,
+ GtkWindow *parent_window,
+ NautilusFileOperationsDBusData *dbus_data)
+{
+ GFile *parent;
+ gchar *parent_uri;
+ gchar *name;
+
+ name = g_file_get_basename (self->target_file);
+ parent = g_file_get_parent (self->target_file);
+ parent_uri = g_file_get_uri (parent);
+ nautilus_file_operations_new_folder (NULL,
+ dbus_data,
+ parent_uri, name,
+ create_callback, self);
+
+ g_free (name);
+ g_free (parent_uri);
+ g_object_unref (parent);
+}
+
+static void
+create_empty_redo_func (NautilusFileUndoInfoCreate *self,
+ GtkWindow *parent_window,
+ NautilusFileOperationsDBusData *dbus_data)
+{
+ GFile *parent;
+ gchar *parent_uri;
+ gchar *new_name;
+
+ parent = g_file_get_parent (self->target_file);
+ parent_uri = g_file_get_uri (parent);
+ new_name = g_file_get_parse_name (self->target_file);
+ nautilus_file_operations_new_file (NULL, parent_uri,
+ new_name,
+ self->template,
+ self->length,
+ create_callback, self);
+
+ g_free (parent_uri);
+ g_free (new_name);
+ g_object_unref (parent);
+}
+
+static void
+create_redo_func (NautilusFileUndoInfo *info,
+ GtkWindow *parent_window,
+ NautilusFileOperationsDBusData *dbus_data)
+{
+ NautilusFileUndoInfoCreate *self = NAUTILUS_FILE_UNDO_INFO_CREATE (info);
+ NautilusFileUndoOp op_type = nautilus_file_undo_info_get_op_type (info);
+
+ if (op_type == NAUTILUS_FILE_UNDO_OP_CREATE_EMPTY_FILE)
+ {
+ create_empty_redo_func (self, parent_window, dbus_data);
+ }
+ else if (op_type == NAUTILUS_FILE_UNDO_OP_CREATE_FOLDER)
+ {
+ create_folder_redo_func (self, parent_window, dbus_data);
+ }
+ else if (op_type == NAUTILUS_FILE_UNDO_OP_CREATE_FILE_FROM_TEMPLATE)
+ {
+ create_from_template_redo_func (self, parent_window, dbus_data);
+ }
+ else
+ {
+ g_assert_not_reached ();
+ }
+}
+
+static void
+create_undo_func (NautilusFileUndoInfo *info,
+ GtkWindow *parent_window,
+ NautilusFileOperationsDBusData *dbus_data)
+{
+ NautilusFileUndoInfoCreate *self = NAUTILUS_FILE_UNDO_INFO_CREATE (info);
+ GList *files = NULL;
+
+ files = g_list_append (files, g_object_ref (self->target_file));
+ nautilus_file_operations_delete_async (files, parent_window,
+ dbus_data,
+ file_undo_info_delete_callback, self);
+
+ g_list_free_full (files, g_object_unref);
+}
+
+static void
+nautilus_file_undo_info_create_init (NautilusFileUndoInfoCreate *self)
+{
+}
+
+static void
+nautilus_file_undo_info_create_finalize (GObject *obj)
+{
+ NautilusFileUndoInfoCreate *self = NAUTILUS_FILE_UNDO_INFO_CREATE (obj);
+ g_clear_object (&self->target_file);
+ g_free (self->template);
+
+ G_OBJECT_CLASS (nautilus_file_undo_info_create_parent_class)->finalize (obj);
+}
+
+static void
+nautilus_file_undo_info_create_class_init (NautilusFileUndoInfoCreateClass *klass)
+{
+ GObjectClass *oclass = G_OBJECT_CLASS (klass);
+ NautilusFileUndoInfoClass *iclass = NAUTILUS_FILE_UNDO_INFO_CLASS (klass);
+
+ oclass->finalize = nautilus_file_undo_info_create_finalize;
+
+ iclass->undo_func = create_undo_func;
+ iclass->redo_func = create_redo_func;
+ iclass->strings_func = create_strings_func;
+}
+
+NautilusFileUndoInfo *
+nautilus_file_undo_info_create_new (NautilusFileUndoOp op_type)
+{
+ return g_object_new (NAUTILUS_TYPE_FILE_UNDO_INFO_CREATE,
+ "op-type", op_type,
+ "item-count", 1,
+ NULL);
+}
+
+void
+nautilus_file_undo_info_create_set_data (NautilusFileUndoInfoCreate *self,
+ GFile *file,
+ const char *template,
+ gint length)
+{
+ self->target_file = g_object_ref (file);
+ self->template = g_strdup (template);
+ self->length = length;
+}
+
+/* rename */
+struct _NautilusFileUndoInfoRename
+{
+ NautilusFileUndoInfo parent_instance;
+
+ GFile *old_file;
+ GFile *new_file;
+ gchar *old_display_name;
+ gchar *new_display_name;
+};
+
+G_DEFINE_TYPE (NautilusFileUndoInfoRename, nautilus_file_undo_info_rename, NAUTILUS_TYPE_FILE_UNDO_INFO)
+
+static void
+rename_strings_func (NautilusFileUndoInfo *info,
+ gchar **undo_label,
+ gchar **undo_description,
+ gchar **redo_label,
+ gchar **redo_description)
+{
+ NautilusFileUndoInfoRename *self = NAUTILUS_FILE_UNDO_INFO_RENAME (info);
+ gchar *new_name, *old_name;
+
+ new_name = g_file_get_parse_name (self->new_file);
+ old_name = g_file_get_parse_name (self->old_file);
+
+ *undo_description = g_strdup_printf (_("Rename “%s” as “%s”"), new_name, old_name);
+ *redo_description = g_strdup_printf (_("Rename “%s” as “%s”"), old_name, new_name);
+
+ *undo_label = g_strdup (_("_Undo Rename"));
+ *redo_label = g_strdup (_("_Redo Rename"));
+
+ g_free (old_name);
+ g_free (new_name);
+}
+
+static void
+rename_redo_func (NautilusFileUndoInfo *info,
+ GtkWindow *parent_window,
+ NautilusFileOperationsDBusData *dbus_data)
+{
+ NautilusFileUndoInfoRename *self = NAUTILUS_FILE_UNDO_INFO_RENAME (info);
+ NautilusFile *file;
+
+ file = nautilus_file_get (self->old_file);
+ nautilus_file_rename (file, self->new_display_name,
+ file_undo_info_operation_callback, self);
+
+ nautilus_file_unref (file);
+}
+
+static void
+rename_undo_func (NautilusFileUndoInfo *info,
+ GtkWindow *parent_window,
+ NautilusFileOperationsDBusData *dbus_data)
+{
+ NautilusFileUndoInfoRename *self = NAUTILUS_FILE_UNDO_INFO_RENAME (info);
+ NautilusFile *file;
+
+ file = nautilus_file_get (self->new_file);
+ nautilus_file_rename (file, self->old_display_name,
+ file_undo_info_operation_callback, self);
+
+ nautilus_file_unref (file);
+}
+
+static void
+nautilus_file_undo_info_rename_init (NautilusFileUndoInfoRename *self)
+{
+}
+
+static void
+nautilus_file_undo_info_rename_finalize (GObject *obj)
+{
+ NautilusFileUndoInfoRename *self = NAUTILUS_FILE_UNDO_INFO_RENAME (obj);
+ g_clear_object (&self->old_file);
+ g_clear_object (&self->new_file);
+ g_free (self->old_display_name);
+ g_free (self->new_display_name);
+
+ G_OBJECT_CLASS (nautilus_file_undo_info_rename_parent_class)->finalize (obj);
+}
+
+static void
+nautilus_file_undo_info_rename_class_init (NautilusFileUndoInfoRenameClass *klass)
+{
+ GObjectClass *oclass = G_OBJECT_CLASS (klass);
+ NautilusFileUndoInfoClass *iclass = NAUTILUS_FILE_UNDO_INFO_CLASS (klass);
+
+ oclass->finalize = nautilus_file_undo_info_rename_finalize;
+
+ iclass->undo_func = rename_undo_func;
+ iclass->redo_func = rename_redo_func;
+ iclass->strings_func = rename_strings_func;
+}
+
+NautilusFileUndoInfo *
+nautilus_file_undo_info_rename_new (void)
+{
+ return g_object_new (NAUTILUS_TYPE_FILE_UNDO_INFO_RENAME,
+ "op-type", NAUTILUS_FILE_UNDO_OP_RENAME,
+ "item-count", 1,
+ NULL);
+}
+
+void
+nautilus_file_undo_info_rename_set_data_pre (NautilusFileUndoInfoRename *self,
+ GFile *old_file,
+ gchar *old_display_name,
+ gchar *new_display_name)
+{
+ self->old_file = g_object_ref (old_file);
+ self->old_display_name = g_strdup (old_display_name);
+ self->new_display_name = g_strdup (new_display_name);
+}
+
+void
+nautilus_file_undo_info_rename_set_data_post (NautilusFileUndoInfoRename *self,
+ GFile *new_file)
+{
+ self->new_file = g_object_ref (new_file);
+}
+
+/* batch rename */
+struct _NautilusFileUndoInfoBatchRename
+{
+ NautilusFileUndoInfo parent_instance;
+
+ GList *old_files;
+ GList *new_files;
+ GList *old_display_names;
+ GList *new_display_names;
+};
+
+G_DEFINE_TYPE (NautilusFileUndoInfoBatchRename, nautilus_file_undo_info_batch_rename, NAUTILUS_TYPE_FILE_UNDO_INFO);
+
+static void
+batch_rename_strings_func (NautilusFileUndoInfo *info,
+ gchar **undo_label,
+ gchar **undo_description,
+ gchar **redo_label,
+ gchar **redo_description)
+{
+ NautilusFileUndoInfoBatchRename *self = NAUTILUS_FILE_UNDO_INFO_BATCH_RENAME (info);
+
+ *undo_description = g_strdup_printf (ngettext ("Batch rename %d file",
+ "Batch rename %d files",
+ g_list_length (self->new_files)),
+ g_list_length (self->new_files));
+ *redo_description = g_strdup_printf (ngettext ("Batch rename %d file",
+ "Batch rename %d files",
+ g_list_length (self->new_files)),
+ g_list_length (self->new_files));
+
+ *undo_label = g_strdup (_("_Undo Batch Rename"));
+ *redo_label = g_strdup (_("_Redo Batch Rename"));
+}
+
+static void
+batch_rename_redo_func (NautilusFileUndoInfo *info,
+ GtkWindow *parent_window,
+ NautilusFileOperationsDBusData *dbus_data)
+{
+ NautilusFileUndoInfoBatchRename *self = NAUTILUS_FILE_UNDO_INFO_BATCH_RENAME (info);
+
+ GList *l, *files;
+ NautilusFile *file;
+ GFile *old_file;
+
+ files = NULL;
+
+ for (l = self->old_files; l != NULL; l = l->next)
+ {
+ old_file = l->data;
+
+ file = nautilus_file_get (old_file);
+ files = g_list_prepend (files, file);
+ }
+
+ files = g_list_reverse (files);
+
+ batch_rename_sort_lists_for_rename (&files,
+ &self->new_display_names,
+ &self->old_display_names,
+ &self->new_files,
+ &self->old_files,
+ TRUE);
+
+ nautilus_file_batch_rename (files, self->new_display_names, file_undo_info_operation_callback, self);
+}
+
+static void
+batch_rename_undo_func (NautilusFileUndoInfo *info,
+ GtkWindow *parent_window,
+ NautilusFileOperationsDBusData *dbus_data)
+{
+ NautilusFileUndoInfoBatchRename *self = NAUTILUS_FILE_UNDO_INFO_BATCH_RENAME (info);
+
+ GList *l, *files;
+ NautilusFile *file;
+ GFile *new_file;
+
+ files = NULL;
+
+ for (l = self->new_files; l != NULL; l = l->next)
+ {
+ new_file = l->data;
+
+ file = nautilus_file_get (new_file);
+ files = g_list_prepend (files, file);
+ }
+
+ files = g_list_reverse (files);
+
+ batch_rename_sort_lists_for_rename (&files,
+ &self->old_display_names,
+ &self->new_display_names,
+ &self->old_files,
+ &self->new_files,
+ TRUE);
+
+ nautilus_file_batch_rename (files, self->old_display_names, file_undo_info_operation_callback, self);
+}
+
+static void
+nautilus_file_undo_info_batch_rename_init (NautilusFileUndoInfoBatchRename *self)
+{
+}
+
+static void
+nautilus_file_undo_info_batch_rename_finalize (GObject *obj)
+{
+ GList *l;
+ GFile *file;
+ GString *string;
+ NautilusFileUndoInfoBatchRename *self = NAUTILUS_FILE_UNDO_INFO_BATCH_RENAME (obj);
+
+ for (l = self->new_files; l != NULL; l = l->next)
+ {
+ file = l->data;
+
+ g_clear_object (&file);
+ }
+
+ for (l = self->old_files; l != NULL; l = l->next)
+ {
+ file = l->data;
+
+ g_clear_object (&file);
+ }
+
+ for (l = self->new_display_names; l != NULL; l = l->next)
+ {
+ string = l->data;
+
+ g_string_free (string, TRUE);
+ }
+
+ for (l = self->old_display_names; l != NULL; l = l->next)
+ {
+ string = l->data;
+
+ g_string_free (string, TRUE);
+ }
+
+ g_list_free (self->new_files);
+ g_list_free (self->old_files);
+ g_list_free (self->new_display_names);
+ g_list_free (self->old_display_names);
+
+ G_OBJECT_CLASS (nautilus_file_undo_info_batch_rename_parent_class)->finalize (obj);
+}
+
+static void
+nautilus_file_undo_info_batch_rename_class_init (NautilusFileUndoInfoBatchRenameClass *klass)
+{
+ GObjectClass *oclass = G_OBJECT_CLASS (klass);
+ NautilusFileUndoInfoClass *iclass = NAUTILUS_FILE_UNDO_INFO_CLASS (klass);
+
+ oclass->finalize = nautilus_file_undo_info_batch_rename_finalize;
+
+ iclass->undo_func = batch_rename_undo_func;
+ iclass->redo_func = batch_rename_redo_func;
+ iclass->strings_func = batch_rename_strings_func;
+}
+
+NautilusFileUndoInfo *
+nautilus_file_undo_info_batch_rename_new (gint item_count)
+{
+ return g_object_new (NAUTILUS_TYPE_FILE_UNDO_INFO_BATCH_RENAME,
+ "op-type", NAUTILUS_FILE_UNDO_OP_BATCH_RENAME,
+ "item-count", item_count,
+ NULL);
+}
+
+void
+nautilus_file_undo_info_batch_rename_set_data_pre (NautilusFileUndoInfoBatchRename *self,
+ GList *old_files)
+{
+ GList *l;
+ GString *old_name;
+ GFile *file;
+
+ self->old_files = old_files;
+ self->old_display_names = NULL;
+
+ for (l = old_files; l != NULL; l = l->next)
+ {
+ file = l->data;
+
+ old_name = g_string_new (g_file_get_basename (file));
+
+ self->old_display_names = g_list_prepend (self->old_display_names, old_name);
+ }
+
+ self->old_display_names = g_list_reverse (self->old_display_names);
+}
+
+void
+nautilus_file_undo_info_batch_rename_set_data_post (NautilusFileUndoInfoBatchRename *self,
+ GList *new_files)
+{
+ GList *l;
+ GString *new_name;
+ GFile *file;
+
+ self->new_files = new_files;
+ self->new_display_names = NULL;
+
+ for (l = new_files; l != NULL; l = l->next)
+ {
+ file = l->data;
+
+ new_name = g_string_new (g_file_get_basename (file));
+
+ self->new_display_names = g_list_prepend (self->new_display_names, new_name);
+ }
+
+ self->new_display_names = g_list_reverse (self->new_display_names);
+}
+
+/* starred files */
+struct _NautilusFileUndoInfoStarred
+{
+ NautilusFileUndoInfo parent_instance;
+
+ GList *files;
+ /* Whether the action was starring or unstarring */
+ gboolean starred;
+};
+
+G_DEFINE_TYPE (NautilusFileUndoInfoStarred, nautilus_file_undo_info_starred, NAUTILUS_TYPE_FILE_UNDO_INFO);
+
+enum
+{
+ PROP_FILES = 1,
+ PROP_STARRED,
+ NUM_PROPERTIES
+};
+
+static void
+starred_strings_func (NautilusFileUndoInfo *info,
+ gchar **undo_label,
+ gchar **undo_description,
+ gchar **redo_label,
+ gchar **redo_description)
+{
+ NautilusFileUndoInfoStarred *self = NAUTILUS_FILE_UNDO_INFO_STARRED (info);
+
+ if (self->starred)
+ {
+ *undo_description = g_strdup_printf (ngettext ("Unstar %d file",
+ "Unstar %d files",
+ g_list_length (self->files)),
+ g_list_length (self->files));
+ *redo_description = g_strdup_printf (ngettext ("Star %d file",
+ "Star %d files",
+ g_list_length (self->files)),
+ g_list_length (self->files));
+ *undo_label = g_strdup (_("_Undo Starring"));
+ *redo_label = g_strdup (_("_Redo Starring"));
+ }
+ else
+ {
+ *undo_description = g_strdup_printf (ngettext ("Star %d file",
+ "Star %d files",
+ g_list_length (self->files)),
+ g_list_length (self->files));
+ *redo_description = g_strdup_printf (ngettext ("Unstar %d file",
+ "Unstar %d files",
+ g_list_length (self->files)),
+ g_list_length (self->files));
+ *undo_label = g_strdup (_("_Undo Unstarring"));
+ *redo_label = g_strdup (_("_Redo Unstarring"));
+ }
+}
+
+static void
+on_undo_starred_tags_updated (GObject *object,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ GTask *task;
+ NautilusFileUndoInfo *undo_info;
+
+ undo_info = NAUTILUS_FILE_UNDO_INFO (object);
+
+ task = user_data;
+ g_clear_object (&task);
+
+ file_undo_info_operation_callback (NULL, NULL, NULL, undo_info);
+}
+
+static void
+starred_redo_func (NautilusFileUndoInfo *info,
+ GtkWindow *parent_window,
+ NautilusFileOperationsDBusData *dbus_data)
+{
+ NautilusFileUndoInfoStarred *self = NAUTILUS_FILE_UNDO_INFO_STARRED (info);
+
+ if (self->starred)
+ {
+ nautilus_tag_manager_star_files (nautilus_tag_manager_get (),
+ G_OBJECT (info),
+ self->files,
+ on_undo_starred_tags_updated,
+ NULL);
+ }
+ else
+ {
+ nautilus_tag_manager_unstar_files (nautilus_tag_manager_get (),
+ G_OBJECT (info),
+ self->files,
+ on_undo_starred_tags_updated,
+ NULL);
+ }
+}
+
+static void
+starred_undo_func (NautilusFileUndoInfo *info,
+ GtkWindow *parent_window,
+ NautilusFileOperationsDBusData *dbus_data)
+{
+ NautilusFileUndoInfoStarred *self = NAUTILUS_FILE_UNDO_INFO_STARRED (info);
+
+ if (self->starred)
+ {
+ nautilus_tag_manager_unstar_files (nautilus_tag_manager_get (),
+ G_OBJECT (info),
+ self->files,
+ on_undo_starred_tags_updated,
+ NULL);
+ }
+ else
+ {
+ nautilus_tag_manager_star_files (nautilus_tag_manager_get (),
+ G_OBJECT (info),
+ self->files,
+ on_undo_starred_tags_updated,
+ NULL);
+ }
+}
+
+static void
+nautilus_file_undo_info_starred_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ NautilusFileUndoInfoStarred *self = NAUTILUS_FILE_UNDO_INFO_STARRED (object);
+
+ switch (prop_id)
+ {
+ case PROP_FILES:
+ {
+ self->files = nautilus_file_list_copy (g_value_get_pointer (value));
+ }
+ break;
+
+ case PROP_STARRED:
+ {
+ self->starred = g_value_get_boolean (value);
+ }
+ break;
+
+ default:
+ {
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+ }
+}
+
+static void
+nautilus_file_undo_info_starred_init (NautilusFileUndoInfoStarred *self)
+{
+}
+
+static void
+nautilus_file_undo_info_starred_finalize (GObject *obj)
+{
+ NautilusFileUndoInfoStarred *self = NAUTILUS_FILE_UNDO_INFO_STARRED (obj);
+
+ nautilus_file_list_free (self->files);
+
+ G_OBJECT_CLASS (nautilus_file_undo_info_starred_parent_class)->finalize (obj);
+}
+
+static void
+nautilus_file_undo_info_starred_class_init (NautilusFileUndoInfoStarredClass *klass)
+{
+ GObjectClass *oclass = G_OBJECT_CLASS (klass);
+ NautilusFileUndoInfoClass *iclass = NAUTILUS_FILE_UNDO_INFO_CLASS (klass);
+
+ oclass->finalize = nautilus_file_undo_info_starred_finalize;
+ oclass->set_property = nautilus_file_undo_info_starred_set_property;
+
+ iclass->undo_func = starred_undo_func;
+ iclass->redo_func = starred_redo_func;
+ iclass->strings_func = starred_strings_func;
+
+ g_object_class_install_property (oclass,
+ PROP_FILES,
+ g_param_spec_pointer ("files",
+ "files",
+ "The files for which to undo star/unstar",
+ G_PARAM_WRITABLE |
+ G_PARAM_CONSTRUCT_ONLY));
+ g_object_class_install_property (oclass,
+ PROP_STARRED,
+ g_param_spec_boolean ("starred",
+ "starred",
+ "Whether the files were starred or unstarred",
+ FALSE,
+ G_PARAM_WRITABLE |
+ G_PARAM_CONSTRUCT_ONLY));
+}
+
+GList *
+nautilus_file_undo_info_starred_get_files (NautilusFileUndoInfoStarred *self)
+{
+ return self->files;
+}
+
+gboolean
+nautilus_file_undo_info_starred_is_starred (NautilusFileUndoInfoStarred *self)
+{
+ return self->starred;
+}
+
+NautilusFileUndoInfo *
+nautilus_file_undo_info_starred_new (GList *files,
+ gboolean starred)
+{
+ NautilusFileUndoInfoStarred *self;
+
+ self = g_object_new (NAUTILUS_TYPE_FILE_UNDO_INFO_STARRED,
+ "op-type", NAUTILUS_FILE_UNDO_OP_STARRED,
+ "item-count", g_list_length (files),
+ "files", files,
+ "starred", starred,
+ NULL);
+
+ return NAUTILUS_FILE_UNDO_INFO (self);
+}
+
+/* trash */
+struct _NautilusFileUndoInfoTrash
+{
+ NautilusFileUndoInfo parent_instance;
+
+ GHashTable *trashed;
+};
+
+G_DEFINE_TYPE (NautilusFileUndoInfoTrash, nautilus_file_undo_info_trash, NAUTILUS_TYPE_FILE_UNDO_INFO)
+
+static void
+trash_strings_func (NautilusFileUndoInfo *info,
+ gchar **undo_label,
+ gchar **undo_description,
+ gchar **redo_label,
+ gchar **redo_description)
+{
+ NautilusFileUndoInfoTrash *self = NAUTILUS_FILE_UNDO_INFO_TRASH (info);
+ gint count = g_hash_table_size (self->trashed);
+
+ if (count != 1)
+ {
+ *undo_description = g_strdup_printf (ngettext ("Restore %d item from trash",
+ "Restore %d items from trash", count),
+ count);
+ *redo_description = g_strdup_printf (ngettext ("Move %d item to trash",
+ "Move %d items to trash", count),
+ count);
+ }
+ else
+ {
+ GList *keys;
+ char *name, *orig_path;
+ GFile *file;
+
+ keys = g_hash_table_get_keys (self->trashed);
+ file = keys->data;
+ name = g_file_get_basename (file);
+ orig_path = g_file_get_path (file);
+ *undo_description = g_strdup_printf (_("Restore “%s” to “%s”"), name, orig_path);
+
+ g_free (name);
+ g_free (orig_path);
+ g_list_free (keys);
+
+ name = g_file_get_parse_name (file);
+ *redo_description = g_strdup_printf (_("Move “%s” to trash"), name);
+
+ g_free (name);
+ }
+
+ *undo_label = g_strdup (_("_Undo Trash"));
+ *redo_label = g_strdup (_("_Redo Trash"));
+}
+
+static void
+trash_redo_func_callback (GHashTable *debuting_uris,
+ gboolean user_cancel,
+ gpointer user_data)
+{
+ NautilusFileUndoInfoTrash *self = user_data;
+ GHashTable *new_trashed_files;
+ GTimeVal current_time;
+ gsize updated_trash_time;
+ GFile *file;
+ GList *keys, *l;
+
+ if (!user_cancel)
+ {
+ new_trashed_files =
+ g_hash_table_new_full (g_file_hash, (GEqualFunc) g_file_equal,
+ g_object_unref, NULL);
+
+ keys = g_hash_table_get_keys (self->trashed);
+
+ g_get_current_time (&current_time);
+ updated_trash_time = current_time.tv_sec;
+
+ for (l = keys; l != NULL; l = l->next)
+ {
+ file = l->data;
+ g_hash_table_insert (new_trashed_files,
+ g_object_ref (file), GSIZE_TO_POINTER (updated_trash_time));
+ }
+
+ g_list_free (keys);
+ g_hash_table_destroy (self->trashed);
+
+ self->trashed = new_trashed_files;
+ }
+
+ file_undo_info_delete_callback (debuting_uris, user_cancel, user_data);
+}
+
+static void
+trash_redo_func (NautilusFileUndoInfo *info,
+ GtkWindow *parent_window,
+ NautilusFileOperationsDBusData *dbus_data)
+{
+ NautilusFileUndoInfoTrash *self = NAUTILUS_FILE_UNDO_INFO_TRASH (info);
+
+ if (g_hash_table_size (self->trashed) > 0)
+ {
+ GList *locations;
+
+ locations = g_hash_table_get_keys (self->trashed);
+ nautilus_file_operations_trash_or_delete_async (locations, parent_window,
+ dbus_data,
+ trash_redo_func_callback, self);
+
+ g_list_free (locations);
+ }
+}
+
+static void
+trash_retrieve_files_to_restore_thread (GTask *task,
+ gpointer source_object,
+ gpointer task_data,
+ GCancellable *cancellable)
+{
+ NautilusFileUndoInfoTrash *self = NAUTILUS_FILE_UNDO_INFO_TRASH (source_object);
+ GFileEnumerator *enumerator;
+ GHashTable *to_restore;
+ GFile *trash;
+ GError *error = NULL;
+
+ to_restore = g_hash_table_new_full (g_file_hash, (GEqualFunc) g_file_equal,
+ g_object_unref, g_object_unref);
+
+ trash = g_file_new_for_uri ("trash:///");
+
+ enumerator = g_file_enumerate_children (trash,
+ G_FILE_ATTRIBUTE_STANDARD_NAME ","
+ G_FILE_ATTRIBUTE_TRASH_DELETION_DATE ","
+ G_FILE_ATTRIBUTE_TRASH_ORIG_PATH,
+ G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
+ NULL, &error);
+
+ if (enumerator)
+ {
+ GFileInfo *info;
+ gpointer lookupvalue;
+ GFile *item;
+ glong trash_time, orig_trash_time;
+ const char *origpath;
+ GFile *origfile;
+
+ while ((info = g_file_enumerator_next_file (enumerator, NULL, &error)) != NULL)
+ {
+ /* Retrieve the original file uri */
+ origpath = g_file_info_get_attribute_byte_string (info, G_FILE_ATTRIBUTE_TRASH_ORIG_PATH);
+ origfile = g_file_new_for_path (origpath);
+
+ lookupvalue = g_hash_table_lookup (self->trashed, origfile);
+
+ if (lookupvalue)
+ {
+ GDateTime *date;
+
+ orig_trash_time = GPOINTER_TO_SIZE (lookupvalue);
+ trash_time = 0;
+ date = g_file_info_get_deletion_date (info);
+ if (date)
+ {
+ trash_time = g_date_time_to_unix (date);
+ g_date_time_unref (date);
+ }
+
+ if (ABS (orig_trash_time - trash_time) <= TRASH_TIME_EPSILON)
+ {
+ /* File in the trash */
+ item = g_file_get_child (trash, g_file_info_get_name (info));
+ g_hash_table_insert (to_restore, item, g_object_ref (origfile));
+ }
+ }
+
+ g_object_unref (origfile);
+ }
+ g_file_enumerator_close (enumerator, FALSE, NULL);
+ g_object_unref (enumerator);
+ }
+ g_object_unref (trash);
+
+ if (error != NULL)
+ {
+ g_task_return_error (task, error);
+ g_hash_table_destroy (to_restore);
+ }
+ else
+ {
+ g_task_return_pointer (task, to_restore, NULL);
+ }
+}
+
+static void
+trash_retrieve_files_to_restore_async (NautilusFileUndoInfoTrash *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GTask *task;
+
+ task = g_task_new (G_OBJECT (self), NULL, callback, user_data);
+
+ g_task_run_in_thread (task, trash_retrieve_files_to_restore_thread);
+
+ g_object_unref (task);
+}
+
+static void
+trash_retrieve_files_ready (GObject *source,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ NautilusFileUndoInfoTrash *self = NAUTILUS_FILE_UNDO_INFO_TRASH (source);
+ GHashTable *files_to_restore;
+ GError *error = NULL;
+
+ files_to_restore = g_task_propagate_pointer (G_TASK (res), &error);
+
+ if (error == NULL && g_hash_table_size (files_to_restore) > 0)
+ {
+ GList *gfiles_in_trash, *l;
+ GFile *item;
+ GFile *dest;
+
+ gfiles_in_trash = g_hash_table_get_keys (files_to_restore);
+
+ for (l = gfiles_in_trash; l != NULL; l = l->next)
+ {
+ item = l->data;
+ dest = g_hash_table_lookup (files_to_restore, item);
+
+ g_file_move (item, dest, G_FILE_COPY_NOFOLLOW_SYMLINKS, NULL, NULL, NULL, NULL);
+ }
+
+ g_list_free (gfiles_in_trash);
+
+ /* Here we must do what's necessary for the callback */
+ file_undo_info_transfer_callback (NULL, (error == NULL), self);
+ }
+ else
+ {
+ file_undo_info_transfer_callback (NULL, FALSE, self);
+ }
+
+ if (files_to_restore != NULL)
+ {
+ g_hash_table_destroy (files_to_restore);
+ }
+
+ g_clear_error (&error);
+}
+
+static void
+trash_undo_func (NautilusFileUndoInfo *info,
+ GtkWindow *parent_window,
+ NautilusFileOperationsDBusData *dbus_data)
+{
+ NautilusFileUndoInfoTrash *self = NAUTILUS_FILE_UNDO_INFO_TRASH (info);
+
+ trash_retrieve_files_to_restore_async (self, trash_retrieve_files_ready, NULL);
+}
+
+static void
+nautilus_file_undo_info_trash_init (NautilusFileUndoInfoTrash *self)
+{
+ self->trashed = g_hash_table_new_full (g_file_hash, (GEqualFunc) g_file_equal,
+ g_object_unref, NULL);
+}
+
+static void
+nautilus_file_undo_info_trash_finalize (GObject *obj)
+{
+ NautilusFileUndoInfoTrash *self = NAUTILUS_FILE_UNDO_INFO_TRASH (obj);
+ g_hash_table_destroy (self->trashed);
+
+ G_OBJECT_CLASS (nautilus_file_undo_info_trash_parent_class)->finalize (obj);
+}
+
+static void
+nautilus_file_undo_info_trash_class_init (NautilusFileUndoInfoTrashClass *klass)
+{
+ GObjectClass *oclass = G_OBJECT_CLASS (klass);
+ NautilusFileUndoInfoClass *iclass = NAUTILUS_FILE_UNDO_INFO_CLASS (klass);
+
+ oclass->finalize = nautilus_file_undo_info_trash_finalize;
+
+ iclass->undo_func = trash_undo_func;
+ iclass->redo_func = trash_redo_func;
+ iclass->strings_func = trash_strings_func;
+}
+
+NautilusFileUndoInfo *
+nautilus_file_undo_info_trash_new (gint item_count)
+{
+ return g_object_new (NAUTILUS_TYPE_FILE_UNDO_INFO_TRASH,
+ "op-type", NAUTILUS_FILE_UNDO_OP_MOVE_TO_TRASH,
+ "item-count", item_count,
+ NULL);
+}
+
+void
+nautilus_file_undo_info_trash_add_file (NautilusFileUndoInfoTrash *self,
+ GFile *file)
+{
+ GTimeVal current_time;
+ gsize orig_trash_time;
+
+ g_get_current_time (&current_time);
+ orig_trash_time = current_time.tv_sec;
+
+ g_hash_table_insert (self->trashed, g_object_ref (file), GSIZE_TO_POINTER (orig_trash_time));
+}
+
+GList *
+nautilus_file_undo_info_trash_get_files (NautilusFileUndoInfoTrash *self)
+{
+ return g_hash_table_get_keys (self->trashed);
+}
+
+/* recursive permissions */
+struct _NautilusFileUndoInfoRecPermissions
+{
+ NautilusFileUndoInfo parent_instance;
+
+ GFile *dest_dir;
+ GHashTable *original_permissions;
+ guint32 dir_mask;
+ guint32 dir_permissions;
+ guint32 file_mask;
+ guint32 file_permissions;
+};
+
+G_DEFINE_TYPE (NautilusFileUndoInfoRecPermissions, nautilus_file_undo_info_rec_permissions, NAUTILUS_TYPE_FILE_UNDO_INFO)
+
+static void
+rec_permissions_strings_func (NautilusFileUndoInfo *info,
+ gchar **undo_label,
+ gchar **undo_description,
+ gchar **redo_label,
+ gchar **redo_description)
+{
+ NautilusFileUndoInfoRecPermissions *self = NAUTILUS_FILE_UNDO_INFO_REC_PERMISSIONS (info);
+ char *name;
+
+ name = g_file_get_path (self->dest_dir);
+
+ *undo_description = g_strdup_printf (_("Restore original permissions of items enclosed in “%s”"), name);
+ *redo_description = g_strdup_printf (_("Set permissions of items enclosed in “%s”"), name);
+
+ *undo_label = g_strdup (_("_Undo Change Permissions"));
+ *redo_label = g_strdup (_("_Redo Change Permissions"));
+
+ g_free (name);
+}
+
+static void
+rec_permissions_callback (gboolean success,
+ gpointer callback_data)
+{
+ file_undo_info_transfer_callback (NULL, success, callback_data);
+}
+
+static void
+rec_permissions_redo_func (NautilusFileUndoInfo *info,
+ GtkWindow *parent_window,
+ NautilusFileOperationsDBusData *dbus_data)
+{
+ NautilusFileUndoInfoRecPermissions *self = NAUTILUS_FILE_UNDO_INFO_REC_PERMISSIONS (info);
+ gchar *parent_uri;
+
+ parent_uri = g_file_get_uri (self->dest_dir);
+ nautilus_file_set_permissions_recursive (parent_uri,
+ self->file_permissions,
+ self->file_mask,
+ self->dir_permissions,
+ self->dir_mask,
+ rec_permissions_callback, self);
+ g_free (parent_uri);
+}
+
+static void
+rec_permissions_undo_func (NautilusFileUndoInfo *info,
+ GtkWindow *parent_window,
+ NautilusFileOperationsDBusData *dbus_data)
+{
+ NautilusFileUndoInfoRecPermissions *self = NAUTILUS_FILE_UNDO_INFO_REC_PERMISSIONS (info);
+
+ if (g_hash_table_size (self->original_permissions) > 0)
+ {
+ GList *gfiles_list;
+ guint32 perm;
+ GList *l;
+ GFile *dest;
+ char *item;
+
+ gfiles_list = g_hash_table_get_keys (self->original_permissions);
+ for (l = gfiles_list; l != NULL; l = l->next)
+ {
+ item = l->data;
+ perm = GPOINTER_TO_UINT (g_hash_table_lookup (self->original_permissions, item));
+ dest = g_file_new_for_uri (item);
+ g_file_set_attribute_uint32 (dest,
+ G_FILE_ATTRIBUTE_UNIX_MODE,
+ perm, G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS, NULL, NULL);
+ g_object_unref (dest);
+ }
+
+ g_list_free (gfiles_list);
+ /* Here we must do what's necessary for the callback */
+ file_undo_info_transfer_callback (NULL, TRUE, self);
+ }
+}
+
+static void
+nautilus_file_undo_info_rec_permissions_init (NautilusFileUndoInfoRecPermissions *self)
+{
+ self->original_permissions = g_hash_table_new_full (g_str_hash, g_str_equal,
+ g_free, NULL);
+}
+
+static void
+nautilus_file_undo_info_rec_permissions_finalize (GObject *obj)
+{
+ NautilusFileUndoInfoRecPermissions *self = NAUTILUS_FILE_UNDO_INFO_REC_PERMISSIONS (obj);
+
+ g_hash_table_destroy (self->original_permissions);
+ g_clear_object (&self->dest_dir);
+
+ G_OBJECT_CLASS (nautilus_file_undo_info_rec_permissions_parent_class)->finalize (obj);
+}
+
+static void
+nautilus_file_undo_info_rec_permissions_class_init (NautilusFileUndoInfoRecPermissionsClass *klass)
+{
+ GObjectClass *oclass = G_OBJECT_CLASS (klass);
+ NautilusFileUndoInfoClass *iclass = NAUTILUS_FILE_UNDO_INFO_CLASS (klass);
+
+ oclass->finalize = nautilus_file_undo_info_rec_permissions_finalize;
+
+ iclass->undo_func = rec_permissions_undo_func;
+ iclass->redo_func = rec_permissions_redo_func;
+ iclass->strings_func = rec_permissions_strings_func;
+}
+
+NautilusFileUndoInfo *
+nautilus_file_undo_info_rec_permissions_new (GFile *dest,
+ guint32 file_permissions,
+ guint32 file_mask,
+ guint32 dir_permissions,
+ guint32 dir_mask)
+{
+ NautilusFileUndoInfoRecPermissions *self;
+
+ self = g_object_new (NAUTILUS_TYPE_FILE_UNDO_INFO_REC_PERMISSIONS,
+ "op-type", NAUTILUS_FILE_UNDO_OP_RECURSIVE_SET_PERMISSIONS,
+ "item-count", 1,
+ NULL);
+
+ self->dest_dir = g_object_ref (dest);
+ self->file_permissions = file_permissions;
+ self->file_mask = file_mask;
+ self->dir_permissions = dir_permissions;
+ self->dir_mask = dir_mask;
+
+ return NAUTILUS_FILE_UNDO_INFO (self);
+}
+
+void
+nautilus_file_undo_info_rec_permissions_add_file (NautilusFileUndoInfoRecPermissions *self,
+ GFile *file,
+ guint32 permission)
+{
+ gchar *original_uri = g_file_get_uri (file);
+ g_hash_table_insert (self->original_permissions, original_uri, GUINT_TO_POINTER (permission));
+}
+
+/* single file change permissions */
+struct _NautilusFileUndoInfoPermissions
+{
+ NautilusFileUndoInfo parent_instance;
+
+ GFile *target_file;
+ guint32 current_permissions;
+ guint32 new_permissions;
+};
+
+G_DEFINE_TYPE (NautilusFileUndoInfoPermissions, nautilus_file_undo_info_permissions, NAUTILUS_TYPE_FILE_UNDO_INFO)
+
+static void
+permissions_strings_func (NautilusFileUndoInfo *info,
+ gchar **undo_label,
+ gchar **undo_description,
+ gchar **redo_label,
+ gchar **redo_description)
+{
+ NautilusFileUndoInfoPermissions *self = NAUTILUS_FILE_UNDO_INFO_PERMISSIONS (info);
+ gchar *name;
+
+ name = g_file_get_parse_name (self->target_file);
+ *undo_description = g_strdup_printf (_("Restore original permissions of “%s”"), name);
+ *redo_description = g_strdup_printf (_("Set permissions of “%s”"), name);
+
+ *undo_label = g_strdup (_("_Undo Change Permissions"));
+ *redo_label = g_strdup (_("_Redo Change Permissions"));
+
+ g_free (name);
+}
+
+static void
+permissions_real_func (NautilusFileUndoInfoPermissions *self,
+ guint32 permissions)
+{
+ NautilusFile *file;
+
+ file = nautilus_file_get (self->target_file);
+ nautilus_file_set_permissions (file, permissions,
+ file_undo_info_operation_callback, self);
+
+ nautilus_file_unref (file);
+}
+
+static void
+permissions_redo_func (NautilusFileUndoInfo *info,
+ GtkWindow *parent_window,
+ NautilusFileOperationsDBusData *dbus_data)
+{
+ NautilusFileUndoInfoPermissions *self = NAUTILUS_FILE_UNDO_INFO_PERMISSIONS (info);
+ permissions_real_func (self, self->new_permissions);
+}
+
+static void
+permissions_undo_func (NautilusFileUndoInfo *info,
+ GtkWindow *parent_window,
+ NautilusFileOperationsDBusData *dbus_data)
+{
+ NautilusFileUndoInfoPermissions *self = NAUTILUS_FILE_UNDO_INFO_PERMISSIONS (info);
+ permissions_real_func (self, self->current_permissions);
+}
+
+static void
+nautilus_file_undo_info_permissions_init (NautilusFileUndoInfoPermissions *self)
+{
+}
+
+static void
+nautilus_file_undo_info_permissions_finalize (GObject *obj)
+{
+ NautilusFileUndoInfoPermissions *self = NAUTILUS_FILE_UNDO_INFO_PERMISSIONS (obj);
+ g_clear_object (&self->target_file);
+
+ G_OBJECT_CLASS (nautilus_file_undo_info_permissions_parent_class)->finalize (obj);
+}
+
+static void
+nautilus_file_undo_info_permissions_class_init (NautilusFileUndoInfoPermissionsClass *klass)
+{
+ GObjectClass *oclass = G_OBJECT_CLASS (klass);
+ NautilusFileUndoInfoClass *iclass = NAUTILUS_FILE_UNDO_INFO_CLASS (klass);
+
+ oclass->finalize = nautilus_file_undo_info_permissions_finalize;
+
+ iclass->undo_func = permissions_undo_func;
+ iclass->redo_func = permissions_redo_func;
+ iclass->strings_func = permissions_strings_func;
+}
+
+NautilusFileUndoInfo *
+nautilus_file_undo_info_permissions_new (GFile *file,
+ guint32 current_permissions,
+ guint32 new_permissions)
+{
+ NautilusFileUndoInfoPermissions *self;
+
+ self = g_object_new (NAUTILUS_TYPE_FILE_UNDO_INFO_PERMISSIONS,
+ "op-type", NAUTILUS_FILE_UNDO_OP_SET_PERMISSIONS,
+ "item-count", 1,
+ NULL);
+
+ self->target_file = g_object_ref (file);
+ self->current_permissions = current_permissions;
+ self->new_permissions = new_permissions;
+
+ return NAUTILUS_FILE_UNDO_INFO (self);
+}
+
+/* group and owner change */
+struct _NautilusFileUndoInfoOwnership
+{
+ NautilusFileUndoInfo parent_instance;
+
+ GFile *target_file;
+ char *original_ownership;
+ char *new_ownership;
+};
+
+G_DEFINE_TYPE (NautilusFileUndoInfoOwnership, nautilus_file_undo_info_ownership, NAUTILUS_TYPE_FILE_UNDO_INFO)
+
+static void
+ownership_strings_func (NautilusFileUndoInfo *info,
+ gchar **undo_label,
+ gchar **undo_description,
+ gchar **redo_label,
+ gchar **redo_description)
+{
+ NautilusFileUndoInfoOwnership *self = NAUTILUS_FILE_UNDO_INFO_OWNERSHIP (info);
+ NautilusFileUndoOp op_type = nautilus_file_undo_info_get_op_type (info);
+ gchar *name;
+
+ name = g_file_get_parse_name (self->target_file);
+
+ if (op_type == NAUTILUS_FILE_UNDO_OP_CHANGE_OWNER)
+ {
+ *undo_description = g_strdup_printf (_("Restore group of “%s” to “%s”"),
+ name, self->original_ownership);
+ *redo_description = g_strdup_printf (_("Set group of “%s” to “%s”"),
+ name, self->new_ownership);
+
+ *undo_label = g_strdup (_("_Undo Change Group"));
+ *redo_label = g_strdup (_("_Redo Change Group"));
+ }
+ else if (op_type == NAUTILUS_FILE_UNDO_OP_CHANGE_GROUP)
+ {
+ *undo_description = g_strdup_printf (_("Restore owner of “%s” to “%s”"),
+ name, self->original_ownership);
+ *redo_description = g_strdup_printf (_("Set owner of “%s” to “%s”"),
+ name, self->new_ownership);
+
+ *undo_label = g_strdup (_("_Undo Change Owner"));
+ *redo_label = g_strdup (_("_Redo Change Owner"));
+ }
+
+ g_free (name);
+}
+
+static void
+ownership_real_func (NautilusFileUndoInfoOwnership *self,
+ const gchar *ownership)
+{
+ NautilusFileUndoOp op_type = nautilus_file_undo_info_get_op_type (NAUTILUS_FILE_UNDO_INFO (self));
+ NautilusFile *file;
+
+ file = nautilus_file_get (self->target_file);
+
+ if (op_type == NAUTILUS_FILE_UNDO_OP_CHANGE_OWNER)
+ {
+ nautilus_file_set_owner (file,
+ ownership,
+ file_undo_info_operation_callback, self);
+ }
+ else if (op_type == NAUTILUS_FILE_UNDO_OP_CHANGE_GROUP)
+ {
+ nautilus_file_set_group (file,
+ ownership,
+ file_undo_info_operation_callback, self);
+ }
+
+ nautilus_file_unref (file);
+}
+
+static void
+ownership_redo_func (NautilusFileUndoInfo *info,
+ GtkWindow *parent_window,
+ NautilusFileOperationsDBusData *dbus_data)
+{
+ NautilusFileUndoInfoOwnership *self = NAUTILUS_FILE_UNDO_INFO_OWNERSHIP (info);
+ ownership_real_func (self, self->new_ownership);
+}
+
+static void
+ownership_undo_func (NautilusFileUndoInfo *info,
+ GtkWindow *parent_window,
+ NautilusFileOperationsDBusData *dbus_data)
+{
+ NautilusFileUndoInfoOwnership *self = NAUTILUS_FILE_UNDO_INFO_OWNERSHIP (info);
+ ownership_real_func (self, self->original_ownership);
+}
+
+static void
+nautilus_file_undo_info_ownership_init (NautilusFileUndoInfoOwnership *self)
+{
+}
+
+static void
+nautilus_file_undo_info_ownership_finalize (GObject *obj)
+{
+ NautilusFileUndoInfoOwnership *self = NAUTILUS_FILE_UNDO_INFO_OWNERSHIP (obj);
+
+ g_clear_object (&self->target_file);
+ g_free (self->original_ownership);
+ g_free (self->new_ownership);
+
+ G_OBJECT_CLASS (nautilus_file_undo_info_ownership_parent_class)->finalize (obj);
+}
+
+static void
+nautilus_file_undo_info_ownership_class_init (NautilusFileUndoInfoOwnershipClass *klass)
+{
+ GObjectClass *oclass = G_OBJECT_CLASS (klass);
+ NautilusFileUndoInfoClass *iclass = NAUTILUS_FILE_UNDO_INFO_CLASS (klass);
+
+ oclass->finalize = nautilus_file_undo_info_ownership_finalize;
+
+ iclass->undo_func = ownership_undo_func;
+ iclass->redo_func = ownership_redo_func;
+ iclass->strings_func = ownership_strings_func;
+}
+
+NautilusFileUndoInfo *
+nautilus_file_undo_info_ownership_new (NautilusFileUndoOp op_type,
+ GFile *file,
+ const char *current_data,
+ const char *new_data)
+{
+ NautilusFileUndoInfoOwnership *self;
+
+ self = g_object_new (NAUTILUS_TYPE_FILE_UNDO_INFO_OWNERSHIP,
+ "item-count", 1,
+ "op-type", op_type,
+ NULL);
+
+ self->target_file = g_object_ref (file);
+ self->original_ownership = g_strdup (current_data);
+ self->new_ownership = g_strdup (new_data);
+
+ return NAUTILUS_FILE_UNDO_INFO (self);
+}
+
+/* extract */
+struct _NautilusFileUndoInfoExtract
+{
+ NautilusFileUndoInfo parent_instance;
+
+ GList *sources;
+ GFile *destination_directory;
+ GList *outputs;
+};
+
+G_DEFINE_TYPE (NautilusFileUndoInfoExtract, nautilus_file_undo_info_extract, NAUTILUS_TYPE_FILE_UNDO_INFO)
+
+static void
+extract_callback (GList *outputs,
+ gpointer callback_data)
+{
+ NautilusFileUndoInfoExtract *self = NAUTILUS_FILE_UNDO_INFO_EXTRACT (callback_data);
+ gboolean success;
+
+ nautilus_file_undo_info_extract_set_outputs (self, outputs);
+
+ success = self->outputs != NULL;
+
+ file_undo_info_transfer_callback (NULL, success, self);
+}
+
+static void
+extract_strings_func (NautilusFileUndoInfo *info,
+ gchar **undo_label,
+ gchar **undo_description,
+ gchar **redo_label,
+ gchar **redo_description)
+{
+ NautilusFileUndoInfoExtract *self = NAUTILUS_FILE_UNDO_INFO_EXTRACT (info);
+ gint total_sources;
+ gint total_outputs;
+
+ *undo_label = g_strdup (_("_Undo Extract"));
+ *redo_label = g_strdup (_("_Redo Extract"));
+
+ total_sources = g_list_length (self->sources);
+ total_outputs = g_list_length (self->outputs);
+
+ if (total_outputs == 1)
+ {
+ GFile *output;
+ g_autofree gchar *name = NULL;
+
+ output = self->outputs->data;
+ name = g_file_get_parse_name (output);
+
+ *undo_description = g_strdup_printf (_("Delete “%s”"), name);
+ }
+ else
+ {
+ *undo_description = g_strdup_printf (ngettext ("Delete %d extracted file",
+ "Delete %d extracted files",
+ total_outputs),
+ total_outputs);
+ }
+
+ if (total_sources == 1)
+ {
+ GFile *source;
+ g_autofree gchar *name = NULL;
+
+ source = self->sources->data;
+ name = g_file_get_parse_name (source);
+
+ *redo_description = g_strdup_printf (_("Extract “%s”"), name);
+ }
+ else
+ {
+ *redo_description = g_strdup_printf (ngettext ("Extract %d file",
+ "Extract %d files",
+ total_sources),
+ total_sources);
+ }
+}
+
+static void
+extract_redo_func (NautilusFileUndoInfo *info,
+ GtkWindow *parent_window,
+ NautilusFileOperationsDBusData *dbus_data)
+{
+ NautilusFileUndoInfoExtract *self = NAUTILUS_FILE_UNDO_INFO_EXTRACT (info);
+
+ nautilus_file_operations_extract_files (self->sources,
+ self->destination_directory,
+ parent_window,
+ dbus_data,
+ extract_callback,
+ self);
+}
+
+static void
+extract_undo_func (NautilusFileUndoInfo *info,
+ GtkWindow *parent_window,
+ NautilusFileOperationsDBusData *dbus_data)
+{
+ NautilusFileUndoInfoExtract *self = NAUTILUS_FILE_UNDO_INFO_EXTRACT (info);
+
+ nautilus_file_operations_delete_async (self->outputs, parent_window,
+ dbus_data,
+ file_undo_info_delete_callback, self);
+}
+
+static void
+nautilus_file_undo_info_extract_init (NautilusFileUndoInfoExtract *self)
+{
+}
+
+static void
+nautilus_file_undo_info_extract_finalize (GObject *obj)
+{
+ NautilusFileUndoInfoExtract *self = NAUTILUS_FILE_UNDO_INFO_EXTRACT (obj);
+
+ g_object_unref (self->destination_directory);
+ g_list_free_full (self->sources, g_object_unref);
+ if (self->outputs)
+ {
+ g_list_free_full (self->outputs, g_object_unref);
+ }
+
+ G_OBJECT_CLASS (nautilus_file_undo_info_extract_parent_class)->finalize (obj);
+}
+
+static void
+nautilus_file_undo_info_extract_class_init (NautilusFileUndoInfoExtractClass *klass)
+{
+ GObjectClass *oclass = G_OBJECT_CLASS (klass);
+ NautilusFileUndoInfoClass *iclass = NAUTILUS_FILE_UNDO_INFO_CLASS (klass);
+
+ oclass->finalize = nautilus_file_undo_info_extract_finalize;
+
+ iclass->undo_func = extract_undo_func;
+ iclass->redo_func = extract_redo_func;
+ iclass->strings_func = extract_strings_func;
+}
+
+void
+nautilus_file_undo_info_extract_set_outputs (NautilusFileUndoInfoExtract *self,
+ GList *outputs)
+{
+ if (self->outputs)
+ {
+ g_list_free_full (self->outputs, g_object_unref);
+ }
+ self->outputs = g_list_copy_deep (outputs,
+ (GCopyFunc) g_object_ref,
+ NULL);
+}
+
+NautilusFileUndoInfo *
+nautilus_file_undo_info_extract_new (GList *sources,
+ GFile *destination_directory)
+{
+ NautilusFileUndoInfoExtract *self;
+
+ self = g_object_new (NAUTILUS_TYPE_FILE_UNDO_INFO_EXTRACT,
+ "item-count", 1,
+ "op-type", NAUTILUS_FILE_UNDO_OP_EXTRACT,
+ NULL);
+
+ self->sources = g_list_copy_deep (sources,
+ (GCopyFunc) g_object_ref,
+ NULL);
+ self->destination_directory = g_object_ref (destination_directory);
+
+ return NAUTILUS_FILE_UNDO_INFO (self);
+}
+
+
+/* compress */
+struct _NautilusFileUndoInfoCompress
+{
+ NautilusFileUndoInfo parent_instance;
+
+ GList *sources;
+ GFile *output;
+ AutoarFormat format;
+ AutoarFilter filter;
+ gchar *passphrase;
+};
+
+G_DEFINE_TYPE (NautilusFileUndoInfoCompress, nautilus_file_undo_info_compress, NAUTILUS_TYPE_FILE_UNDO_INFO)
+
+static void
+compress_callback (GFile *new_file,
+ gboolean success,
+ gpointer callback_data)
+{
+ NautilusFileUndoInfoCompress *self = NAUTILUS_FILE_UNDO_INFO_COMPRESS (callback_data);
+
+ if (success)
+ {
+ g_set_object (&self->output, new_file);
+ }
+
+ file_undo_info_transfer_callback (NULL, success, self);
+}
+
+static void
+compress_strings_func (NautilusFileUndoInfo *info,
+ gchar **undo_label,
+ gchar **undo_description,
+ gchar **redo_label,
+ gchar **redo_description)
+{
+ NautilusFileUndoInfoCompress *self = NAUTILUS_FILE_UNDO_INFO_COMPRESS (info);
+ g_autofree gchar *output_name = NULL;
+ gint sources_count;
+
+ output_name = g_file_get_parse_name (self->output);
+ *undo_description = g_strdup_printf (_("Delete “%s”"), output_name);
+
+ sources_count = g_list_length (self->sources);
+ if (sources_count == 1)
+ {
+ GFile *source;
+ g_autofree gchar *source_name = NULL;
+
+ source = self->sources->data;
+ source_name = g_file_get_parse_name (source);
+
+ *redo_description = g_strdup_printf (_("Compress “%s”"), source_name);
+ }
+ else
+ {
+ *redo_description = g_strdup_printf (ngettext ("Compress %d file",
+ "Compress %d files",
+ sources_count),
+ sources_count);
+ }
+
+ *undo_label = g_strdup (_("_Undo Compress"));
+ *redo_label = g_strdup (_("_Redo Compress"));
+}
+
+static void
+compress_redo_func (NautilusFileUndoInfo *info,
+ GtkWindow *parent_window,
+ NautilusFileOperationsDBusData *dbus_data)
+{
+ NautilusFileUndoInfoCompress *self = NAUTILUS_FILE_UNDO_INFO_COMPRESS (info);
+
+ nautilus_file_operations_compress (self->sources,
+ self->output,
+ self->format,
+ self->filter,
+ self->passphrase,
+ parent_window,
+ dbus_data,
+ compress_callback,
+ self);
+}
+
+static void
+compress_undo_func (NautilusFileUndoInfo *info,
+ GtkWindow *parent_window,
+ NautilusFileOperationsDBusData *dbus_data)
+{
+ NautilusFileUndoInfoCompress *self = NAUTILUS_FILE_UNDO_INFO_COMPRESS (info);
+ GList *files = NULL;
+
+ files = g_list_prepend (files, self->output);
+
+ nautilus_file_operations_delete_async (files, parent_window,
+ dbus_data,
+ file_undo_info_delete_callback, self);
+
+ g_list_free (files);
+}
+
+static void
+nautilus_file_undo_info_compress_init (NautilusFileUndoInfoCompress *self)
+{
+}
+
+static void
+nautilus_file_undo_info_compress_finalize (GObject *obj)
+{
+ NautilusFileUndoInfoCompress *self = NAUTILUS_FILE_UNDO_INFO_COMPRESS (obj);
+
+ g_list_free_full (self->sources, g_object_unref);
+ g_clear_object (&self->output);
+ g_free (self->passphrase);
+
+ G_OBJECT_CLASS (nautilus_file_undo_info_compress_parent_class)->finalize (obj);
+}
+
+static void
+nautilus_file_undo_info_compress_class_init (NautilusFileUndoInfoCompressClass *klass)
+{
+ GObjectClass *oclass = G_OBJECT_CLASS (klass);
+ NautilusFileUndoInfoClass *iclass = NAUTILUS_FILE_UNDO_INFO_CLASS (klass);
+
+ oclass->finalize = nautilus_file_undo_info_compress_finalize;
+
+ iclass->undo_func = compress_undo_func;
+ iclass->redo_func = compress_redo_func;
+ iclass->strings_func = compress_strings_func;
+}
+
+NautilusFileUndoInfo *
+nautilus_file_undo_info_compress_new (GList *sources,
+ GFile *output,
+ AutoarFormat format,
+ AutoarFilter filter,
+ const gchar *passphrase)
+{
+ NautilusFileUndoInfoCompress *self;
+
+ self = g_object_new (NAUTILUS_TYPE_FILE_UNDO_INFO_COMPRESS,
+ "item-count", 1,
+ "op-type", NAUTILUS_FILE_UNDO_OP_COMPRESS,
+ NULL);
+
+ self->sources = g_list_copy_deep (sources, (GCopyFunc) g_object_ref, NULL);
+ self->output = g_object_ref (output);
+ self->format = format;
+ self->filter = filter;
+ self->passphrase = g_strdup (passphrase);
+
+ return NAUTILUS_FILE_UNDO_INFO (self);
+}
diff --git a/src/nautilus-file-undo-operations.h b/src/nautilus-file-undo-operations.h
new file mode 100644
index 0000000..09ae17c
--- /dev/null
+++ b/src/nautilus-file-undo-operations.h
@@ -0,0 +1,230 @@
+
+/* nautilus-file-undo-operations.h - Manages undo/redo of file operations
+ *
+ * Copyright (C) 2007-2011 Amos Brocco
+ * Copyright (C) 2010 Red Hat, Inc.
+ *
+ * This library 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 library 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 library; if not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Amos Brocco <amos.brocco@gmail.com>
+ * Cosimo Cecchi <cosimoc@redhat.com>
+ *
+ */
+
+#pragma once
+
+#include <gio/gio.h>
+#include <gtk/gtk.h>
+#include <gnome-autoar/gnome-autoar.h>
+
+#include "nautilus-file-operations-dbus-data.h"
+
+typedef enum
+{
+ NAUTILUS_FILE_UNDO_OP_INVALID,
+ NAUTILUS_FILE_UNDO_OP_COPY,
+ NAUTILUS_FILE_UNDO_OP_DUPLICATE,
+ NAUTILUS_FILE_UNDO_OP_MOVE,
+ NAUTILUS_FILE_UNDO_OP_RENAME,
+ NAUTILUS_FILE_UNDO_OP_BATCH_RENAME,
+ NAUTILUS_FILE_UNDO_OP_STARRED,
+ NAUTILUS_FILE_UNDO_OP_CREATE_EMPTY_FILE,
+ NAUTILUS_FILE_UNDO_OP_CREATE_FILE_FROM_TEMPLATE,
+ NAUTILUS_FILE_UNDO_OP_CREATE_FOLDER,
+ NAUTILUS_FILE_UNDO_OP_EXTRACT,
+ NAUTILUS_FILE_UNDO_OP_COMPRESS,
+ NAUTILUS_FILE_UNDO_OP_MOVE_TO_TRASH,
+ NAUTILUS_FILE_UNDO_OP_RESTORE_FROM_TRASH,
+ NAUTILUS_FILE_UNDO_OP_CREATE_LINK,
+ NAUTILUS_FILE_UNDO_OP_RECURSIVE_SET_PERMISSIONS,
+ NAUTILUS_FILE_UNDO_OP_SET_PERMISSIONS,
+ NAUTILUS_FILE_UNDO_OP_CHANGE_GROUP,
+ NAUTILUS_FILE_UNDO_OP_CHANGE_OWNER,
+ NAUTILUS_FILE_UNDO_OP_NUM_TYPES,
+} NautilusFileUndoOp;
+
+#define NAUTILUS_TYPE_FILE_UNDO_INFO nautilus_file_undo_info_get_type ()
+G_DECLARE_DERIVABLE_TYPE (NautilusFileUndoInfo, nautilus_file_undo_info,
+ NAUTILUS, FILE_UNDO_INFO,
+ GObject)
+
+struct _NautilusFileUndoInfoClass
+{
+ GObjectClass parent_class;
+
+ void (* undo_func) (NautilusFileUndoInfo *self,
+ GtkWindow *parent_window,
+ NautilusFileOperationsDBusData *dbus_data);
+ void (* redo_func) (NautilusFileUndoInfo *self,
+ GtkWindow *parent_window,
+ NautilusFileOperationsDBusData *dbus_data);
+
+ void (* strings_func) (NautilusFileUndoInfo *self,
+ gchar **undo_label,
+ gchar **undo_description,
+ gchar **redo_label,
+ gchar **redo_description);
+};
+
+void nautilus_file_undo_info_apply_async (NautilusFileUndoInfo *self,
+ gboolean undo,
+ GtkWindow *parent_window,
+ NautilusFileOperationsDBusData *dbus_data,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+gboolean nautilus_file_undo_info_apply_finish (NautilusFileUndoInfo *self,
+ GAsyncResult *res,
+ gboolean *user_cancel,
+ GError **error);
+
+void nautilus_file_undo_info_get_strings (NautilusFileUndoInfo *self,
+ gchar **undo_label,
+ gchar **undo_description,
+ gchar **redo_label,
+ gchar **redo_description);
+
+NautilusFileUndoOp nautilus_file_undo_info_get_op_type (NautilusFileUndoInfo *self);
+
+/* copy/move/duplicate/link/restore from trash */
+#define NAUTILUS_TYPE_FILE_UNDO_INFO_EXT nautilus_file_undo_info_ext_get_type ()
+G_DECLARE_FINAL_TYPE (NautilusFileUndoInfoExt, nautilus_file_undo_info_ext,
+ NAUTILUS, FILE_UNDO_INFO_EXT,
+ NautilusFileUndoInfo)
+
+NautilusFileUndoInfo *nautilus_file_undo_info_ext_new (NautilusFileUndoOp op_type,
+ gint item_count,
+ GFile *src_dir,
+ GFile *target_dir);
+void nautilus_file_undo_info_ext_add_origin_target_pair (NautilusFileUndoInfoExt *self,
+ GFile *origin,
+ GFile *target);
+
+/* create new file/folder */
+#define NAUTILUS_TYPE_FILE_UNDO_INFO_CREATE nautilus_file_undo_info_create_get_type ()
+G_DECLARE_FINAL_TYPE (NautilusFileUndoInfoCreate, nautilus_file_undo_info_create,
+ NAUTILUS, FILE_UNDO_INFO_CREATE,
+ NautilusFileUndoInfo)
+
+NautilusFileUndoInfo *nautilus_file_undo_info_create_new (NautilusFileUndoOp op_type);
+void nautilus_file_undo_info_create_set_data (NautilusFileUndoInfoCreate *self,
+ GFile *file,
+ const char *template,
+ gint length);
+
+/* rename */
+#define NAUTILUS_TYPE_FILE_UNDO_INFO_RENAME nautilus_file_undo_info_rename_get_type ()
+G_DECLARE_FINAL_TYPE (NautilusFileUndoInfoRename, nautilus_file_undo_info_rename,
+ NAUTILUS, FILE_UNDO_INFO_RENAME,
+ NautilusFileUndoInfo)
+
+NautilusFileUndoInfo *nautilus_file_undo_info_rename_new (void);
+void nautilus_file_undo_info_rename_set_data_pre (NautilusFileUndoInfoRename *self,
+ GFile *old_file,
+ gchar *old_display_name,
+ gchar *new_display_name);
+void nautilus_file_undo_info_rename_set_data_post (NautilusFileUndoInfoRename *self,
+ GFile *new_file);
+
+/* batch rename */
+#define NAUTILUS_TYPE_FILE_UNDO_INFO_BATCH_RENAME nautilus_file_undo_info_batch_rename_get_type ()
+G_DECLARE_FINAL_TYPE (NautilusFileUndoInfoBatchRename, nautilus_file_undo_info_batch_rename,
+ NAUTILUS, FILE_UNDO_INFO_BATCH_RENAME,
+ NautilusFileUndoInfo)
+
+NautilusFileUndoInfo *nautilus_file_undo_info_batch_rename_new (gint item_count);
+void nautilus_file_undo_info_batch_rename_set_data_pre (NautilusFileUndoInfoBatchRename *self,
+ GList *old_files);
+void nautilus_file_undo_info_batch_rename_set_data_post (NautilusFileUndoInfoBatchRename *self,
+ GList *new_files);
+
+/* starred files */
+#define NAUTILUS_TYPE_FILE_UNDO_INFO_STARRED (nautilus_file_undo_info_starred_get_type ())
+G_DECLARE_FINAL_TYPE (NautilusFileUndoInfoStarred, nautilus_file_undo_info_starred,
+ NAUTILUS, FILE_UNDO_INFO_STARRED,
+ NautilusFileUndoInfo)
+
+NautilusFileUndoInfo *nautilus_file_undo_info_starred_new (GList *files,
+ gboolean starred);
+GList *nautilus_file_undo_info_starred_get_files (NautilusFileUndoInfoStarred *self);
+gboolean nautilus_file_undo_info_starred_is_starred (NautilusFileUndoInfoStarred *self);
+
+/* trash */
+#define NAUTILUS_TYPE_FILE_UNDO_INFO_TRASH (nautilus_file_undo_info_trash_get_type ())
+G_DECLARE_FINAL_TYPE (NautilusFileUndoInfoTrash, nautilus_file_undo_info_trash,
+ NAUTILUS, FILE_UNDO_INFO_TRASH,
+ NautilusFileUndoInfo)
+
+NautilusFileUndoInfo *nautilus_file_undo_info_trash_new (gint item_count);
+void nautilus_file_undo_info_trash_add_file (NautilusFileUndoInfoTrash *self,
+ GFile *file);
+GList *nautilus_file_undo_info_trash_get_files (NautilusFileUndoInfoTrash *self);
+
+/* recursive permissions */
+#define NAUTILUS_TYPE_FILE_UNDO_INFO_REC_PERMISSIONS nautilus_file_undo_info_rec_permissions_get_type ()
+G_DECLARE_FINAL_TYPE (NautilusFileUndoInfoRecPermissions, nautilus_file_undo_info_rec_permissions,
+ NAUTILUS, FILE_UNDO_INFO_REC_PERMISSIONS,
+ NautilusFileUndoInfo)
+
+NautilusFileUndoInfo *nautilus_file_undo_info_rec_permissions_new (GFile *dest,
+ guint32 file_permissions,
+ guint32 file_mask,
+ guint32 dir_permissions,
+ guint32 dir_mask);
+void nautilus_file_undo_info_rec_permissions_add_file (NautilusFileUndoInfoRecPermissions *self,
+ GFile *file,
+ guint32 permission);
+
+/* single file change permissions */
+#define NAUTILUS_TYPE_FILE_UNDO_INFO_PERMISSIONS nautilus_file_undo_info_permissions_get_type ()
+G_DECLARE_FINAL_TYPE (NautilusFileUndoInfoPermissions, nautilus_file_undo_info_permissions,
+ NAUTILUS, FILE_UNDO_INFO_PERMISSIONS,
+ NautilusFileUndoInfo)
+
+NautilusFileUndoInfo *nautilus_file_undo_info_permissions_new (GFile *file,
+ guint32 current_permissions,
+ guint32 new_permissions);
+
+/* group and owner change */
+#define NAUTILUS_TYPE_FILE_UNDO_INFO_OWNERSHIP nautilus_file_undo_info_ownership_get_type ()
+G_DECLARE_FINAL_TYPE (NautilusFileUndoInfoOwnership, nautilus_file_undo_info_ownership,
+ NAUTILUS, FILE_UNDO_INFO_OWNERSHIP,
+ NautilusFileUndoInfo)
+
+NautilusFileUndoInfo *nautilus_file_undo_info_ownership_new (NautilusFileUndoOp op_type,
+ GFile *file,
+ const char *current_data,
+ const char *new_data);
+
+/* extract */
+#define NAUTILUS_TYPE_FILE_UNDO_INFO_EXTRACT nautilus_file_undo_info_extract_get_type ()
+G_DECLARE_FINAL_TYPE (NautilusFileUndoInfoExtract, nautilus_file_undo_info_extract,
+ NAUTILUS, FILE_UNDO_INFO_EXTRACT,
+ NautilusFileUndoInfo)
+
+NautilusFileUndoInfo * nautilus_file_undo_info_extract_new (GList *sources,
+ GFile *destination_directory);
+void nautilus_file_undo_info_extract_set_outputs (NautilusFileUndoInfoExtract *self,
+ GList *outputs);
+
+/* compress */
+#define NAUTILUS_TYPE_FILE_UNDO_INFO_COMPRESS nautilus_file_undo_info_compress_get_type ()
+G_DECLARE_FINAL_TYPE (NautilusFileUndoInfoCompress, nautilus_file_undo_info_compress,
+ NAUTILUS, FILE_UNDO_INFO_COMPRESS,
+ NautilusFileUndoInfo)
+
+NautilusFileUndoInfo * nautilus_file_undo_info_compress_new (GList *sources,
+ GFile *output,
+ AutoarFormat format,
+ AutoarFilter filter,
+ const gchar *passphrase);
diff --git a/src/nautilus-file-utilities.c b/src/nautilus-file-utilities.c
new file mode 100644
index 0000000..6043a11
--- /dev/null
+++ b/src/nautilus-file-utilities.c
@@ -0,0 +1,1515 @@
+/* nautilus-file-utilities.c - implementation of file manipulation routines.
+ *
+ * Copyright (C) 1999, 2000, 2001 Eazel, Inc.
+ *
+ * The Gnome Library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * The Gnome Library 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
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with the Gnome Library; see the file COPYING.LIB. If not,
+ * see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: John Sullivan <sullivan@eazel.com>
+ */
+
+#include <config.h>
+#include "nautilus-file-utilities.h"
+
+#include "nautilus-application.h"
+#include "nautilus-global-preferences.h"
+#include "nautilus-icon-names.h"
+#include "nautilus-lib-self-check-functions.h"
+#include "nautilus-metadata.h"
+#include "nautilus-file.h"
+#include "nautilus-file-operations.h"
+#include "nautilus-search-directory.h"
+#include "nautilus-starred-directory.h"
+#include "nautilus-ui-utilities.h"
+#include <eel/eel-glib-extensions.h>
+#include <eel/eel-string.h>
+#include <eel/eel-debug.h>
+#include <eel/eel-vfs-extensions.h>
+#include <glib.h>
+#include <glib/gi18n.h>
+#include <glib/gstdio.h>
+#include <gio/gio.h>
+#include <unistd.h>
+#include <stdlib.h>
+
+#define NAUTILUS_USER_DIRECTORY_NAME "nautilus"
+#define DEFAULT_NAUTILUS_DIRECTORY_MODE (0755)
+
+/* Allowed characters outside alphanumeric for unreserved. */
+#define G_URI_OTHER_UNRESERVED "-._~"
+
+/* This or something equivalent will eventually go into glib/guri.h */
+gboolean
+nautilus_uri_parse (const char *uri,
+ char **host,
+ guint16 *port,
+ char **userinfo)
+{
+ g_autofree char *tmp_str = NULL;
+ const char *start, *p;
+ char c;
+
+ g_return_val_if_fail (uri != NULL, FALSE);
+
+ if (host)
+ {
+ *host = NULL;
+ }
+
+ if (port)
+ {
+ *port = 0;
+ }
+
+ if (userinfo)
+ {
+ *userinfo = NULL;
+ }
+
+ /* From RFC 3986 Decodes:
+ * URI = scheme ":" hier-part [ "?" query ] [ "#" fragment ]
+ * hier-part = "//" authority path-abempty
+ * path-abempty = *( "/" segment )
+ * authority = [ userinfo "@" ] host [ ":" port ]
+ */
+
+ /* Check we have a valid scheme */
+ tmp_str = g_uri_parse_scheme (uri);
+
+ if (tmp_str == NULL)
+ {
+ return FALSE;
+ }
+
+ /* Decode hier-part:
+ * hier-part = "//" authority path-abempty
+ */
+ p = uri;
+ start = strstr (p, "//");
+
+ if (start == NULL)
+ {
+ return FALSE;
+ }
+
+ start += 2;
+
+ if (strchr (start, '@') != NULL)
+ {
+ /* Decode userinfo:
+ * userinfo = *( unreserved / pct-encoded / sub-delims / ":" )
+ * unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~"
+ * pct-encoded = "%" HEXDIG HEXDIG
+ */
+ p = start;
+ while (1)
+ {
+ c = *p++;
+
+ if (c == '@')
+ {
+ break;
+ }
+
+ /* pct-encoded */
+ if (c == '%')
+ {
+ if (!(g_ascii_isxdigit (p[0]) ||
+ g_ascii_isxdigit (p[1])))
+ {
+ return FALSE;
+ }
+
+ p++;
+
+ continue;
+ }
+
+ /* unreserved / sub-delims / : */
+ if (!(g_ascii_isalnum (c) ||
+ strchr (G_URI_OTHER_UNRESERVED, c) ||
+ strchr (G_URI_RESERVED_CHARS_SUBCOMPONENT_DELIMITERS, c) ||
+ c == ':'))
+ {
+ return FALSE;
+ }
+ }
+
+ if (userinfo)
+ {
+ *userinfo = g_strndup (start, p - start - 1);
+ }
+
+ start = p;
+ }
+ else
+ {
+ p = start;
+ }
+
+
+ /* decode host:
+ * host = IP-literal / IPv4address / reg-name
+ * reg-name = *( unreserved / pct-encoded / sub-delims )
+ */
+
+ /* If IPv6 or IPvFuture */
+ if (*p == '[')
+ {
+ start++;
+ p++;
+ while (1)
+ {
+ c = *p++;
+
+ if (c == ']')
+ {
+ break;
+ }
+
+ /* unreserved / sub-delims */
+ if (!(g_ascii_isalnum (c) ||
+ strchr (G_URI_OTHER_UNRESERVED, c) ||
+ strchr (G_URI_RESERVED_CHARS_SUBCOMPONENT_DELIMITERS, c) ||
+ c == ':' ||
+ c == '.'))
+ {
+ goto error;
+ }
+ }
+ }
+ else
+ {
+ while (1)
+ {
+ c = *p++;
+
+ if (c == ':' ||
+ c == '/' ||
+ c == '?' ||
+ c == '#' ||
+ c == '\0')
+ {
+ break;
+ }
+
+ /* pct-encoded */
+ if (c == '%')
+ {
+ if (!(g_ascii_isxdigit (p[0]) ||
+ g_ascii_isxdigit (p[1])))
+ {
+ goto error;
+ }
+
+ p++;
+
+ continue;
+ }
+
+ /* unreserved / sub-delims */
+ if (!(g_ascii_isalnum (c) ||
+ strchr (G_URI_OTHER_UNRESERVED, c) ||
+ strchr (G_URI_RESERVED_CHARS_SUBCOMPONENT_DELIMITERS, c)))
+ {
+ goto error;
+ }
+ }
+ }
+
+ if (host)
+ {
+ *host = g_uri_unescape_segment (start, p - 1, NULL);
+ }
+
+ if (c == ':')
+ {
+ /* Decode pot:
+ * port = *DIGIT
+ */
+ guint tmp = 0;
+
+ while (1)
+ {
+ c = *p++;
+
+ if (c == '/' ||
+ c == '?' ||
+ c == '#' ||
+ c == '\0')
+ {
+ break;
+ }
+
+ if (!g_ascii_isdigit (c))
+ {
+ goto error;
+ }
+
+ tmp = (tmp * 10) + (c - '0');
+
+ if (tmp > 65535)
+ {
+ goto error;
+ }
+ }
+ if (port)
+ {
+ *port = (guint16) tmp;
+ }
+ }
+
+ return TRUE;
+
+error:
+ if (host && *host)
+ {
+ g_free (*host);
+ *host = NULL;
+ }
+
+ if (userinfo && *userinfo)
+ {
+ g_free (*userinfo);
+ *userinfo = NULL;
+ }
+
+ return FALSE;
+}
+
+char *
+nautilus_compute_title_for_location (GFile *location)
+{
+ NautilusFile *file;
+ GMount *mount;
+ char *title;
+
+ /* TODO-gio: This doesn't really work all that great if the
+ * info about the file isn't known atm... */
+
+ if (nautilus_is_home_directory (location))
+ {
+ return g_strdup (_("Home"));
+ }
+
+ if ((mount = nautilus_get_mounted_mount_for_root (location)) != NULL)
+ {
+ title = g_mount_get_name (mount);
+
+ g_object_unref (mount);
+
+ return title;
+ }
+
+ title = NULL;
+ if (location)
+ {
+ file = nautilus_file_get (location);
+
+ if (nautilus_file_is_other_locations (file))
+ {
+ title = g_strdup (_("Other Locations"));
+ }
+ else if (nautilus_file_is_starred_location (file))
+ {
+ title = g_strdup (_("Starred"));
+ }
+ else
+ {
+ title = nautilus_file_get_description (file);
+
+ if (title == NULL)
+ {
+ title = nautilus_file_get_display_name (file);
+ }
+ }
+ nautilus_file_unref (file);
+ }
+
+ if (title == NULL)
+ {
+ title = g_file_get_basename (location);
+ }
+
+ return title;
+}
+
+
+/**
+ * nautilus_get_user_directory:
+ *
+ * Get the path for the directory containing nautilus settings.
+ *
+ * Return value: the directory path.
+ **/
+char *
+nautilus_get_user_directory (void)
+{
+ char *user_directory = NULL;
+
+ user_directory = g_build_filename (g_get_user_config_dir (),
+ NAUTILUS_USER_DIRECTORY_NAME,
+ NULL);
+
+ if (!g_file_test (user_directory, G_FILE_TEST_EXISTS))
+ {
+ g_mkdir_with_parents (user_directory, DEFAULT_NAUTILUS_DIRECTORY_MODE);
+ /* FIXME bugzilla.gnome.org 41286:
+ * How should we handle the case where this mkdir fails?
+ * Note that nautilus_application_startup will refuse to launch if this
+ * directory doesn't get created, so that case is OK. But the directory
+ * could be deleted after Nautilus was launched, and perhaps
+ * there is some bad side-effect of not handling that case.
+ */
+ }
+
+ return user_directory;
+}
+
+/**
+ * nautilus_get_scripts_directory_path:
+ *
+ * Get the path for the directory containing nautilus scripts.
+ *
+ * Return value: the directory path containing nautilus scripts
+ **/
+char *
+nautilus_get_scripts_directory_path (void)
+{
+ return g_build_filename (g_get_user_data_dir (), "nautilus", "scripts", NULL);
+}
+
+char *
+nautilus_get_home_directory_uri (void)
+{
+ return g_filename_to_uri (g_get_home_dir (), NULL, NULL);
+}
+
+
+gboolean
+nautilus_should_use_templates_directory (void)
+{
+ const char *dir;
+ gboolean res;
+
+ dir = g_get_user_special_dir (G_USER_DIRECTORY_TEMPLATES);
+ res = dir && (g_strcmp0 (dir, g_get_home_dir ()) != 0);
+ return res;
+}
+
+char *
+nautilus_get_templates_directory (void)
+{
+ return g_strdup (g_get_user_special_dir (G_USER_DIRECTORY_TEMPLATES));
+}
+
+char *
+nautilus_get_templates_directory_uri (void)
+{
+ char *directory, *uri;
+
+ directory = nautilus_get_templates_directory ();
+ uri = g_filename_to_uri (directory, NULL, NULL);
+ g_free (directory);
+ return uri;
+}
+
+gboolean
+nautilus_is_home_directory_file (GFile *dir,
+ const char *filename)
+{
+ char *dirname;
+ static GFile *home_dir_dir = NULL;
+ static char *home_dir_filename = NULL;
+
+ if (home_dir_dir == NULL)
+ {
+ dirname = g_path_get_dirname (g_get_home_dir ());
+ home_dir_dir = g_file_new_for_path (dirname);
+ g_free (dirname);
+ home_dir_filename = g_path_get_basename (g_get_home_dir ());
+ }
+
+ return (g_file_equal (dir, home_dir_dir) &&
+ strcmp (filename, home_dir_filename) == 0);
+}
+
+gboolean
+nautilus_is_home_directory (GFile *dir)
+{
+ static GFile *home_dir = NULL;
+
+ if (home_dir == NULL)
+ {
+ home_dir = g_file_new_for_path (g_get_home_dir ());
+ }
+
+ return g_file_equal (dir, home_dir);
+}
+
+gboolean
+nautilus_is_root_directory (GFile *dir)
+{
+ static GFile *root_dir = NULL;
+
+ if (root_dir == NULL)
+ {
+ root_dir = g_file_new_for_path ("/");
+ }
+
+ return g_file_equal (dir, root_dir);
+}
+
+gboolean
+nautilus_is_search_directory (GFile *dir)
+{
+ g_autofree gchar *uri = NULL;
+
+ uri = g_file_get_uri (dir);
+ return eel_uri_is_search (uri);
+}
+
+gboolean
+nautilus_is_recent_directory (GFile *dir)
+{
+ g_autofree gchar *uri = NULL;
+
+ uri = g_file_get_uri (dir);
+
+ return eel_uri_is_recent (uri);
+}
+
+gboolean
+nautilus_is_starred_directory (GFile *dir)
+{
+ g_autofree gchar *uri = NULL;
+
+ uri = g_file_get_uri (dir);
+
+ if (eel_uri_is_starred (uri))
+ {
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+gboolean
+nautilus_is_trash_directory (GFile *dir)
+{
+ g_autofree gchar *uri = NULL;
+
+ uri = g_file_get_uri (dir);
+ return eel_uri_is_trash (uri);
+}
+
+gboolean
+nautilus_is_other_locations_directory (GFile *dir)
+{
+ g_autofree gchar *uri = NULL;
+
+ uri = g_file_get_uri (dir);
+ return eel_uri_is_other_locations (uri);
+}
+
+GMount *
+nautilus_get_mounted_mount_for_root (GFile *location)
+{
+ g_autoptr (GVolumeMonitor) volume_monitor = NULL;
+ GList *mounts;
+ GList *l;
+ GMount *mount;
+ GMount *result = NULL;
+ GFile *root = NULL;
+
+ volume_monitor = g_volume_monitor_get ();
+ mounts = g_volume_monitor_get_mounts (volume_monitor);
+
+ for (l = mounts; l != NULL; l = l->next)
+ {
+ mount = l->data;
+
+ if (g_mount_is_shadowed (mount))
+ {
+ continue;
+ }
+
+ root = g_mount_get_root (mount);
+ if (g_file_equal (location, root))
+ {
+ result = g_object_ref (mount);
+ break;
+ }
+ }
+
+ g_clear_object (&root);
+ g_list_free_full (mounts, g_object_unref);
+
+ return result;
+}
+
+GFile *
+nautilus_generate_unique_file_in_directory (GFile *directory,
+ const char *basename)
+{
+ g_autofree char *basename_without_extension = NULL;
+ const char *extension;
+ GFile *child;
+ int copy;
+
+ g_return_val_if_fail (directory != NULL, NULL);
+ g_return_val_if_fail (basename != NULL, NULL);
+ g_return_val_if_fail (g_file_query_exists (directory, NULL), NULL);
+
+ basename_without_extension = eel_filename_strip_extension (basename);
+ extension = eel_filename_get_extension_offset (basename);
+
+ child = g_file_get_child (directory, basename);
+
+ copy = 1;
+ while (g_file_query_exists (child, NULL))
+ {
+ g_autofree char *filename = NULL;
+
+ g_object_unref (child);
+
+ filename = g_strdup_printf ("%s (%d)%s",
+ basename_without_extension,
+ copy,
+ extension ? extension : "");
+ child = g_file_get_child (directory, filename);
+
+ copy++;
+ }
+
+ return child;
+}
+
+GFile *
+nautilus_find_existing_uri_in_hierarchy (GFile *location)
+{
+ GFileInfo *info;
+ GFile *tmp;
+
+ g_assert (location != NULL);
+
+ location = g_object_ref (location);
+ while (location != NULL)
+ {
+ info = g_file_query_info (location,
+ G_FILE_ATTRIBUTE_STANDARD_NAME,
+ 0, NULL, NULL);
+ g_object_unref (info);
+ if (info != NULL)
+ {
+ return location;
+ }
+ tmp = location;
+ location = g_file_get_parent (location);
+ g_object_unref (tmp);
+ }
+
+ return location;
+}
+
+static gboolean
+have_program_in_path (const char *name)
+{
+ gchar *path;
+ gboolean result;
+
+ path = g_find_program_in_path (name);
+ result = (path != NULL);
+ g_free (path);
+ return result;
+}
+
+static GIcon *
+special_directory_get_icon (GUserDirectory directory,
+ gboolean symbolic)
+{
+#define ICON_CASE(x) \
+ case G_USER_DIRECTORY_ ## x: \
+ return (symbolic) ? g_themed_icon_new (NAUTILUS_ICON_FOLDER_ ## x) : g_themed_icon_new (NAUTILUS_ICON_FULLCOLOR_FOLDER_ ## x);
+
+ switch (directory)
+ {
+ ICON_CASE (DOCUMENTS);
+ ICON_CASE (DOWNLOAD);
+ ICON_CASE (MUSIC);
+ ICON_CASE (PICTURES);
+ ICON_CASE (PUBLIC_SHARE);
+ ICON_CASE (TEMPLATES);
+ ICON_CASE (VIDEOS);
+
+ default:
+ {
+ return (symbolic) ? g_themed_icon_new (NAUTILUS_ICON_FOLDER) : g_themed_icon_new (NAUTILUS_ICON_FULLCOLOR_FOLDER);
+ }
+ }
+
+#undef ICON_CASE
+}
+
+GIcon *
+nautilus_special_directory_get_symbolic_icon (GUserDirectory directory)
+{
+ return special_directory_get_icon (directory, TRUE);
+}
+
+GIcon *
+nautilus_special_directory_get_icon (GUserDirectory directory)
+{
+ return special_directory_get_icon (directory, FALSE);
+}
+
+gboolean
+nautilus_is_file_roller_installed (void)
+{
+ static int installed = -1;
+
+ if (installed < 0)
+ {
+ if (have_program_in_path ("file-roller"))
+ {
+ installed = 1;
+ }
+ else
+ {
+ installed = 0;
+ }
+ }
+
+ return installed > 0 ? TRUE : FALSE;
+}
+
+GHashTable *
+nautilus_trashed_files_get_original_directories (GList *files,
+ GList **unhandled_files)
+{
+ GHashTable *directories;
+ NautilusFile *file, *original_file, *original_dir;
+ GList *l, *m;
+
+ directories = NULL;
+
+ if (unhandled_files != NULL)
+ {
+ *unhandled_files = NULL;
+ }
+
+ for (l = files; l != NULL; l = l->next)
+ {
+ file = NAUTILUS_FILE (l->data);
+ original_file = nautilus_file_get_trash_original_file (file);
+
+ original_dir = NULL;
+ if (original_file != NULL)
+ {
+ original_dir = nautilus_file_get_parent (original_file);
+ }
+
+ if (original_dir != NULL)
+ {
+ if (directories == NULL)
+ {
+ directories = g_hash_table_new_full (g_direct_hash, g_direct_equal,
+ (GDestroyNotify) nautilus_file_unref,
+ (GDestroyNotify) nautilus_file_list_free);
+ }
+ nautilus_file_ref (original_dir);
+ m = g_hash_table_lookup (directories, original_dir);
+ if (m != NULL)
+ {
+ g_hash_table_steal (directories, original_dir);
+ nautilus_file_unref (original_dir);
+ }
+ m = g_list_append (m, nautilus_file_ref (file));
+ g_hash_table_insert (directories, original_dir, m);
+ }
+ else if (unhandled_files != NULL)
+ {
+ *unhandled_files = g_list_append (*unhandled_files, nautilus_file_ref (file));
+ }
+
+ nautilus_file_unref (original_file);
+ nautilus_file_unref (original_dir);
+ }
+
+ return directories;
+}
+
+GList *
+nautilus_file_list_from_uri_list (GList *uris)
+{
+ GList *l;
+ GList *result = NULL;
+
+ for (l = uris; l != NULL; l = l->next)
+ {
+ g_autoptr (GFile) location = NULL;
+
+ location = g_file_new_for_uri (l->data);
+ result = g_list_prepend (result, nautilus_file_get (location));
+ }
+
+ return g_list_reverse (result);
+}
+
+static GList *
+locations_from_file_list (GList *file_list)
+{
+ NautilusFile *file;
+ GList *l, *ret;
+
+ ret = NULL;
+
+ for (l = file_list; l != NULL; l = l->next)
+ {
+ file = NAUTILUS_FILE (l->data);
+ ret = g_list_prepend (ret, nautilus_file_get_location (file));
+ }
+
+ return g_list_reverse (ret);
+}
+
+typedef struct
+{
+ GHashTable *original_dirs_hash;
+ GtkWindow *parent_window;
+} RestoreFilesData;
+
+static void
+ensure_dirs_task_ready_cb (GObject *_source,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ NautilusFile *original_dir;
+ GFile *original_dir_location;
+ GList *original_dirs, *files, *locations, *l;
+ RestoreFilesData *data = user_data;
+
+ original_dirs = g_hash_table_get_keys (data->original_dirs_hash);
+ for (l = original_dirs; l != NULL; l = l->next)
+ {
+ original_dir = NAUTILUS_FILE (l->data);
+ original_dir_location = nautilus_file_get_location (original_dir);
+
+ files = g_hash_table_lookup (data->original_dirs_hash, original_dir);
+ locations = locations_from_file_list (files);
+
+ nautilus_file_operations_move_async (locations,
+ original_dir_location,
+ data->parent_window,
+ NULL, NULL, NULL);
+
+ g_list_free_full (locations, g_object_unref);
+ g_object_unref (original_dir_location);
+ }
+
+ g_list_free (original_dirs);
+
+ g_hash_table_unref (data->original_dirs_hash);
+ g_slice_free (RestoreFilesData, data);
+}
+
+static void
+ensure_dirs_task_thread_func (GTask *task,
+ gpointer source,
+ gpointer task_data,
+ GCancellable *cancellable)
+{
+ RestoreFilesData *data = task_data;
+ NautilusFile *original_dir;
+ GFile *original_dir_location;
+ GList *original_dirs, *l;
+
+ original_dirs = g_hash_table_get_keys (data->original_dirs_hash);
+ for (l = original_dirs; l != NULL; l = l->next)
+ {
+ original_dir = NAUTILUS_FILE (l->data);
+ original_dir_location = nautilus_file_get_location (original_dir);
+
+ g_file_make_directory_with_parents (original_dir_location, cancellable, NULL);
+ g_object_unref (original_dir_location);
+ }
+
+ g_task_return_pointer (task, NULL, NULL);
+}
+
+static void
+restore_files_ensure_parent_directories (GHashTable *original_dirs_hash,
+ GtkWindow *parent_window)
+{
+ RestoreFilesData *data;
+ GTask *ensure_dirs_task;
+
+ data = g_slice_new0 (RestoreFilesData);
+ data->parent_window = parent_window;
+ data->original_dirs_hash = g_hash_table_ref (original_dirs_hash);
+
+ ensure_dirs_task = g_task_new (NULL, NULL, ensure_dirs_task_ready_cb, data);
+ g_task_set_task_data (ensure_dirs_task, data, NULL);
+ g_task_run_in_thread (ensure_dirs_task, ensure_dirs_task_thread_func);
+ g_object_unref (ensure_dirs_task);
+}
+
+void
+nautilus_restore_files_from_trash (GList *files,
+ GtkWindow *parent_window)
+{
+ NautilusFile *file;
+ GHashTable *original_dirs_hash;
+ GList *unhandled_files, *l;
+ char *message, *file_name;
+
+ original_dirs_hash = nautilus_trashed_files_get_original_directories (files, &unhandled_files);
+
+ for (l = unhandled_files; l != NULL; l = l->next)
+ {
+ file = NAUTILUS_FILE (l->data);
+ file_name = nautilus_file_get_display_name (file);
+ message = g_strdup_printf (_("Could not determine original location of “%s” "), file_name);
+ g_free (file_name);
+
+ show_dialog (message,
+ _("The item cannot be restored from trash"),
+ parent_window,
+ GTK_MESSAGE_WARNING);
+ g_free (message);
+ }
+
+ if (original_dirs_hash != NULL)
+ {
+ restore_files_ensure_parent_directories (original_dirs_hash, parent_window);
+ g_hash_table_unref (original_dirs_hash);
+ }
+
+ nautilus_file_list_unref (unhandled_files);
+}
+
+typedef struct
+{
+ NautilusMountGetContent callback;
+ gpointer user_data;
+} GetContentTypesData;
+
+static void
+get_types_cb (GObject *source_object,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ GetContentTypesData *data;
+ char **types;
+
+ data = user_data;
+ types = g_mount_guess_content_type_finish (G_MOUNT (source_object), res, NULL);
+
+ g_object_set_data_full (source_object,
+ "nautilus-content-type-cache",
+ g_strdupv (types),
+ (GDestroyNotify) g_strfreev);
+
+ if (data->callback)
+ {
+ data->callback ((const char **) types, data->user_data);
+ }
+ g_strfreev (types);
+ g_slice_free (GetContentTypesData, data);
+}
+
+void
+nautilus_get_x_content_types_for_mount_async (GMount *mount,
+ NautilusMountGetContent callback,
+ GCancellable *cancellable,
+ gpointer user_data)
+{
+ char **cached;
+ GetContentTypesData *data;
+
+ if (mount == NULL)
+ {
+ if (callback)
+ {
+ callback (NULL, user_data);
+ }
+ return;
+ }
+
+ cached = g_object_get_data (G_OBJECT (mount), "nautilus-content-type-cache");
+ if (cached != NULL)
+ {
+ if (callback)
+ {
+ callback ((const char **) cached, user_data);
+ }
+ return;
+ }
+
+ data = g_slice_new0 (GetContentTypesData);
+ data->callback = callback;
+ data->user_data = user_data;
+
+ g_mount_guess_content_type (mount,
+ FALSE,
+ cancellable,
+ get_types_cb,
+ data);
+}
+
+char **
+nautilus_get_cached_x_content_types_for_mount (GMount *mount)
+{
+ char **cached;
+
+ if (mount == NULL)
+ {
+ return NULL;
+ }
+
+ cached = g_object_get_data (G_OBJECT (mount), "nautilus-content-type-cache");
+ if (cached != NULL)
+ {
+ return g_strdupv (cached);
+ }
+
+ return NULL;
+}
+
+char *
+get_message_for_content_type (const char *content_type)
+{
+ char *message;
+ char *description;
+
+ description = g_content_type_get_description (content_type);
+
+ /* Customize greeting for well-known content types */
+ if (strcmp (content_type, "x-content/audio-cdda") == 0)
+ {
+ /* translators: these describe the contents of removable media */
+ message = g_strdup (_("Audio CD"));
+ }
+ else if (strcmp (content_type, "x-content/audio-dvd") == 0)
+ {
+ message = g_strdup (_("Audio DVD"));
+ }
+ else if (strcmp (content_type, "x-content/video-dvd") == 0)
+ {
+ message = g_strdup (_("Video DVD"));
+ }
+ else if (strcmp (content_type, "x-content/video-vcd") == 0)
+ {
+ message = g_strdup (_("Video CD"));
+ }
+ else if (strcmp (content_type, "x-content/video-svcd") == 0)
+ {
+ message = g_strdup (_("Super Video CD"));
+ }
+ else if (strcmp (content_type, "x-content/image-photocd") == 0)
+ {
+ message = g_strdup (_("Photo CD"));
+ }
+ else if (strcmp (content_type, "x-content/image-picturecd") == 0)
+ {
+ message = g_strdup (_("Picture CD"));
+ }
+ else if (strcmp (content_type, "x-content/image-dcf") == 0)
+ {
+ message = g_strdup (_("Contains digital photos"));
+ }
+ else if (strcmp (content_type, "x-content/audio-player") == 0)
+ {
+ message = g_strdup (_("Contains music"));
+ }
+ else if (strcmp (content_type, "x-content/unix-software") == 0)
+ {
+ message = g_strdup (_("Contains software to run"));
+ }
+ else if (strcmp (content_type, "x-content/ostree-repository") == 0)
+ {
+ message = g_strdup (_("Contains software to install"));
+ }
+ else
+ {
+ /* fallback to generic greeting */
+ message = g_strdup_printf (_("Detected as “%s”"), description);
+ }
+
+ g_free (description);
+
+ return message;
+}
+
+char *
+get_message_for_two_content_types (const char * const *content_types)
+{
+ char *message;
+
+ g_assert (content_types[0] != NULL);
+ g_assert (content_types[1] != NULL);
+
+ /* few combinations make sense */
+ if (strcmp (content_types[0], "x-content/image-dcf") == 0
+ || strcmp (content_types[1], "x-content/image-dcf") == 0)
+ {
+ if (strcmp (content_types[0], "x-content/audio-player") == 0)
+ {
+ /* translators: these describe the contents of removable media */
+ message = g_strdup (_("Contains music and photos"));
+ }
+ else if (strcmp (content_types[1], "x-content/audio-player") == 0)
+ {
+ message = g_strdup (_("Contains photos and music"));
+ }
+ else
+ {
+ message = g_strdup (_("Contains digital photos"));
+ }
+ }
+ else if ((strcmp (content_types[0], "x-content/video-vcd") == 0
+ || strcmp (content_types[1], "x-content/video-vcd") == 0)
+ && (strcmp (content_types[0], "x-content/video-dvd") == 0
+ || strcmp (content_types[1], "x-content/video-dvd") == 0))
+ {
+ message = g_strdup_printf ("%s/%s",
+ get_message_for_content_type (content_types[0]),
+ get_message_for_content_type (content_types[1]));
+ }
+ else
+ {
+ message = get_message_for_content_type (content_types[0]);
+ }
+
+ return message;
+}
+
+gboolean
+should_handle_content_type (const char *content_type)
+{
+ g_autoptr (GAppInfo) default_app = NULL;
+
+ default_app = g_app_info_get_default_for_type (content_type, FALSE);
+
+ return !g_str_has_prefix (content_type, "x-content/blank-") &&
+ !g_content_type_is_a (content_type, "x-content/win32-software") &&
+ default_app != NULL;
+}
+
+gboolean
+should_handle_content_types (const char * const *content_types)
+{
+ int i;
+
+ for (i = 0; content_types[i] != NULL; i++)
+ {
+ if (should_handle_content_type (content_types[i]))
+ {
+ return TRUE;
+ }
+ }
+
+ return FALSE;
+}
+
+gboolean
+nautilus_file_selection_equal (GList *selection_a,
+ GList *selection_b)
+{
+ GList *al, *bl;
+ gboolean selection_matches;
+
+ if (selection_a == NULL || selection_b == NULL)
+ {
+ return (selection_a == selection_b);
+ }
+
+ if (g_list_length (selection_a) != g_list_length (selection_b))
+ {
+ return FALSE;
+ }
+
+ selection_matches = TRUE;
+
+ for (al = selection_a; al; al = al->next)
+ {
+ GFile *a_location = nautilus_file_get_location (NAUTILUS_FILE (al->data));
+ gboolean found = FALSE;
+
+ for (bl = selection_b; bl; bl = bl->next)
+ {
+ GFile *b_location = nautilus_file_get_location (NAUTILUS_FILE (bl->data));
+ found = g_file_equal (b_location, a_location);
+ g_object_unref (b_location);
+
+ if (found)
+ {
+ break;
+ }
+ }
+
+ selection_matches = found;
+ g_object_unref (a_location);
+
+ if (!selection_matches)
+ {
+ break;
+ }
+ }
+
+ return selection_matches;
+}
+
+static char *
+trim_whitespace (const gchar *string)
+{
+ glong space_count;
+ glong length;
+ gchar *offset;
+
+ space_count = 0;
+ length = g_utf8_strlen (string, -1);
+ offset = g_utf8_offset_to_pointer (string, length);
+
+ while (space_count <= length)
+ {
+ gunichar character;
+
+ offset = g_utf8_prev_char (offset);
+ character = g_utf8_get_char (offset);
+
+ if (!g_unichar_isspace (character))
+ {
+ break;
+ }
+
+ space_count++;
+ }
+
+ if (space_count == 0)
+ {
+ return g_strdup (string);
+ }
+
+ return g_utf8_substring (string, 0, length - space_count);
+}
+
+char *
+nautilus_get_common_filename_prefix (GList *file_list,
+ int min_required_len)
+{
+ GList *file_names = NULL;
+ GList *directory_names = NULL;
+ char *result_files;
+ g_autofree char *result = NULL;
+ g_autofree char *result_trimmed = NULL;
+
+ if (file_list == NULL)
+ {
+ return NULL;
+ }
+
+ for (GList *l = file_list; l != NULL; l = l->next)
+ {
+ char *name;
+
+ g_return_val_if_fail (NAUTILUS_IS_FILE (l->data), NULL);
+
+ name = nautilus_file_get_display_name (l->data);
+
+ /* Since the concept of file extensions does not apply to directories,
+ * we filter those out.
+ */
+ if (nautilus_file_is_directory (l->data))
+ {
+ directory_names = g_list_prepend (directory_names, name);
+ }
+ else
+ {
+ file_names = g_list_prepend (file_names, name);
+ }
+ }
+
+ result_files = nautilus_get_common_filename_prefix_from_filenames (file_names, min_required_len);
+
+ if (directory_names == NULL)
+ {
+ return result_files;
+ }
+
+ if (result_files != NULL)
+ {
+ directory_names = g_list_prepend (directory_names, result_files);
+ }
+
+ result = eel_str_get_common_prefix (directory_names, min_required_len);
+
+ g_list_free_full (file_names, g_free);
+ g_list_free_full (directory_names, g_free);
+
+ if (result == NULL)
+ {
+ return NULL;
+ }
+
+ result_trimmed = trim_whitespace (result);
+
+ if (g_utf8_strlen (result_trimmed, -1) < min_required_len)
+ {
+ return NULL;
+ }
+
+ return g_steal_pointer (&result_trimmed);
+}
+
+char *
+nautilus_get_common_filename_prefix_from_filenames (GList *filenames,
+ int min_required_len)
+{
+ GList *stripped_filenames = NULL;
+ char *common_prefix;
+ char *truncated;
+ int common_prefix_len;
+
+ for (GList *i = filenames; i != NULL; i = i->next)
+ {
+ gchar *stripped_filename;
+
+ stripped_filename = eel_filename_strip_extension (i->data);
+
+ stripped_filenames = g_list_prepend (stripped_filenames, stripped_filename);
+ }
+
+ common_prefix = eel_str_get_common_prefix (stripped_filenames, min_required_len);
+ if (common_prefix == NULL)
+ {
+ return NULL;
+ }
+
+ g_list_free_full (stripped_filenames, g_free);
+
+ truncated = trim_whitespace (common_prefix);
+ g_free (common_prefix);
+
+ common_prefix_len = g_utf8_strlen (truncated, -1);
+ if (common_prefix_len < min_required_len)
+ {
+ g_free (truncated);
+ return NULL;
+ }
+
+ return truncated;
+}
+
+glong
+nautilus_get_max_child_name_length_for_location (GFile *location)
+{
+ g_autofree gchar *path = NULL;
+ glong name_max;
+ glong path_max;
+ glong max_child_name_length;
+
+ g_return_val_if_fail (G_IS_FILE (location), -1);
+
+ if (!g_file_has_uri_scheme (location, "file"))
+ {
+ /* FIXME: Can we query length limits for non-"file://" locations? */
+ return -1;
+ }
+
+ path = g_file_get_path (location);
+
+ g_return_val_if_fail (path != NULL, -1);
+
+ name_max = pathconf (path, _PC_NAME_MAX);
+ path_max = pathconf (path, _PC_PATH_MAX);
+ max_child_name_length = -1;
+
+ if (name_max == -1)
+ {
+ if (path_max != -1)
+ {
+ /* We don't know the name max, but we know the name can't make the
+ * path longer than this.
+ * Subtracting 1 because PATH_MAX includes the terminating null
+ * character, as per limits.h(0P). */
+ max_child_name_length = MAX ((path_max - 1) - strlen (path), 0);
+ }
+ }
+ else
+ {
+ /* No need to subtract 1, because NAME_MAX excludes the terminating null
+ * character, as per limits.h(0P) */
+ max_child_name_length = name_max;
+ if (path_max != -1)
+ {
+ max_child_name_length = CLAMP ((path_max - 1) - strlen (path),
+ 0,
+ max_child_name_length);
+ }
+ }
+
+ return max_child_name_length;
+}
+
+#if !defined (NAUTILUS_OMIT_SELF_CHECK)
+
+void
+nautilus_self_check_file_utilities (void)
+{
+}
+
+void
+nautilus_ensure_extension_builtins (void)
+{
+ /* Please add new extension types here, even if you can guarantee
+ * that they will be registered by the time the extension point
+ * is iterating over its extensions.
+ */
+ g_type_ensure (NAUTILUS_TYPE_SEARCH_DIRECTORY);
+ g_type_ensure (NAUTILUS_TYPE_STARRED_DIRECTORY);
+}
+
+void
+nautilus_ensure_extension_points (void)
+{
+ static gsize once_init_value = 0;
+
+ if (g_once_init_enter (&once_init_value))
+ {
+ GIOExtensionPoint *extension_point;
+
+ extension_point = g_io_extension_point_register (NAUTILUS_DIRECTORY_PROVIDER_EXTENSION_POINT_NAME);
+ g_io_extension_point_set_required_type (extension_point, NAUTILUS_TYPE_DIRECTORY);
+
+ g_once_init_leave (&once_init_value, 1);
+ }
+}
+
+#endif /* !NAUTILUS_OMIT_SELF_CHECK */
+
+gboolean
+nautilus_file_can_rename_files (GList *files)
+{
+ GList *l;
+ NautilusFile *file;
+
+ for (l = files; l != NULL; l = l->next)
+ {
+ file = NAUTILUS_FILE (l->data);
+
+ if (!nautilus_file_can_rename (file))
+ {
+ return FALSE;
+ }
+ }
+
+ return TRUE;
+}
+
+/* Try to get a native file:// URI instead of any other GVFS
+ * scheme, for interoperability with apps only handling file:// URIs.
+ */
+gchar *
+nautilus_uri_to_native_uri (const gchar *uri)
+{
+ g_autoptr (GFile) file = NULL;
+ g_autofree gchar *path = NULL;
+
+ file = g_file_new_for_uri (uri);
+ path = g_file_get_path (file);
+
+ if (path != NULL)
+ {
+ return g_filename_to_uri (path, NULL, NULL);
+ }
+
+ return NULL;
+}
+
+NautilusQueryRecursive
+location_settings_search_get_recursive (void)
+{
+ switch (g_settings_get_enum (nautilus_preferences, "recursive-search"))
+ {
+ case NAUTILUS_SPEED_TRADEOFF_ALWAYS:
+ {
+ return NAUTILUS_QUERY_RECURSIVE_ALWAYS;
+ }
+ break;
+
+ case NAUTILUS_SPEED_TRADEOFF_LOCAL_ONLY:
+ {
+ return NAUTILUS_QUERY_RECURSIVE_LOCAL_ONLY;
+ }
+ break;
+
+ case NAUTILUS_SPEED_TRADEOFF_NEVER:
+ {
+ return NAUTILUS_QUERY_RECURSIVE_NEVER;
+ }
+ break;
+ }
+
+ return NAUTILUS_QUERY_RECURSIVE_ALWAYS;
+}
+
+NautilusQueryRecursive
+location_settings_search_get_recursive_for_location (GFile *location)
+{
+ NautilusQueryRecursive recursive = location_settings_search_get_recursive ();
+
+ g_return_val_if_fail (location, recursive);
+
+ if (recursive == NAUTILUS_QUERY_RECURSIVE_LOCAL_ONLY)
+ {
+ g_autoptr (NautilusFile) file = nautilus_file_get_existing (location);
+
+ g_return_val_if_fail (file != NULL, recursive);
+
+ if (nautilus_file_is_remote (file))
+ {
+ recursive = NAUTILUS_QUERY_RECURSIVE_NEVER;
+ }
+ }
+
+ return recursive;
+}
+
+/* check_schema_available() was copied from GNOME Settings */
+gboolean
+check_schema_available (const gchar *schema_id)
+{
+ GSettingsSchemaSource *source;
+ g_autoptr (GSettingsSchema) schema = NULL;
+
+ if (nautilus_application_is_sandboxed ())
+ {
+ return TRUE;
+ }
+
+ source = g_settings_schema_source_get_default ();
+ if (!source)
+ {
+ return FALSE;
+ }
+
+ schema = g_settings_schema_source_lookup (source, schema_id, TRUE);
+ if (!schema)
+ {
+ return FALSE;
+ }
+
+ return TRUE;
+}
diff --git a/src/nautilus-file-utilities.h b/src/nautilus-file-utilities.h
new file mode 100644
index 0000000..e87c024
--- /dev/null
+++ b/src/nautilus-file-utilities.h
@@ -0,0 +1,145 @@
+
+/* nautilus-file-utilities.h - interface for file manipulation routines.
+
+ Copyright (C) 1999, 2000, 2001 Eazel, Inc.
+
+ The Gnome Library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Library General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version.
+
+ The Gnome Library 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
+ Library General Public License for more details.
+
+ You should have received a copy of the GNU Library General Public
+ License along with the Gnome Library; see the file COPYING.LIB. If not,
+ see <http://www.gnu.org/licenses/>.
+
+ Authors: John Sullivan <sullivan@eazel.com>
+*/
+
+#pragma once
+
+#include <gio/gio.h>
+#include <gtk/gtk.h>
+
+#include <config.h>
+
+#include "nautilus-query.h"
+
+#define NAUTILUS_DESKTOP_ID APPLICATION_ID ".desktop"
+
+/* These functions all return something something that needs to be
+ * freed with g_free, is not NULL, and is guaranteed to exist.
+ */
+char * nautilus_get_user_directory (void);
+char * nautilus_get_home_directory_uri (void);
+gboolean nautilus_is_root_directory (GFile *dir);
+gboolean nautilus_is_home_directory (GFile *dir);
+gboolean nautilus_is_home_directory_file (GFile *dir,
+ const char *filename);
+gboolean nautilus_is_search_directory (GFile *dir);
+gboolean nautilus_is_recent_directory (GFile *dir);
+gboolean nautilus_is_starred_directory (GFile *dir);
+gboolean nautilus_is_trash_directory (GFile *dir);
+gboolean nautilus_is_other_locations_directory (GFile *dir);
+GMount * nautilus_get_mounted_mount_for_root (GFile *location);
+
+gboolean nautilus_should_use_templates_directory (void);
+char * nautilus_get_templates_directory (void);
+char * nautilus_get_templates_directory_uri (void);
+
+char * nautilus_compute_title_for_location (GFile *file);
+
+gboolean nautilus_is_file_roller_installed (void);
+
+GIcon * nautilus_special_directory_get_icon (GUserDirectory directory);
+GIcon * nautilus_special_directory_get_symbolic_icon (GUserDirectory directory);
+
+gboolean nautilus_uri_parse (const char *uri,
+ char **host,
+ guint16 *port,
+ char **userinfo);
+
+/* Return an allocated file location that is guranteed to be unique, but
+ * tries to make the location name readable to users.
+ * This isn't race-free, so don't use for security-related things
+ */
+GFile * nautilus_generate_unique_file_in_directory (GFile *directory,
+ const char *basename);
+
+GFile * nautilus_find_existing_uri_in_hierarchy (GFile *location);
+
+char * nautilus_get_scripts_directory_path (void);
+
+GHashTable * nautilus_trashed_files_get_original_directories (GList *files,
+ GList **unhandled_files);
+void nautilus_restore_files_from_trash (GList *files,
+ GtkWindow *parent_window);
+
+typedef void (*NautilusMountGetContent) (const char **content, gpointer user_data);
+
+char ** nautilus_get_cached_x_content_types_for_mount (GMount *mount);
+void nautilus_get_x_content_types_for_mount_async (GMount *mount,
+ NautilusMountGetContent callback,
+ GCancellable *cancellable,
+ gpointer user_data);
+char * get_message_for_content_type (const char *content_type);
+char * get_message_for_two_content_types (const char * const *content_types);
+gboolean should_handle_content_type (const char *content_type);
+gboolean should_handle_content_types (const char * const *content_type);
+
+gboolean nautilus_file_selection_equal (GList *selection_a, GList *selection_b);
+
+/**
+ * nautilus_get_common_filename_prefix:
+ * @file_list: set of files (NautilusFile *)
+ * @min_required_len: the minimum number of characters required in the prefix
+ *
+ * Returns: the common filename prefix for a set of files, or NULL if
+ * there isn't a common prefix of length min_required_len
+ */
+char * nautilus_get_common_filename_prefix (GList *file_list,
+ int min_required_len);
+
+/**
+ * nautilus_get_common_filename_prefix_from_filenames:
+ * @filename_list: set of file names (char *)
+ * @min_required_len: the minimum number of characters required in the prefix
+ *
+ * Returns: the common filename prefix for a set of filenames, or NULL if
+ * there isn't a common prefix of length min_required_len
+ */
+char * nautilus_get_common_filename_prefix_from_filenames (GList *filename_list,
+ int min_required_len);
+
+/**
+ * nautilus_get_max_child_name_for_location:
+ * @location: a #GFile representing a directory
+ *
+ * Gets the maximum file name length for files inside @location.
+ *
+ * This call does blocking I/O.
+ *
+ * Returns: The maximum file name length in bytes (not including the
+ * terminating null of a filename string), -1 if the maximum length
+ * could not be determined or 0 if @location path is too long.
+ */
+
+glong nautilus_get_max_child_name_length_for_location (GFile *location);
+
+void nautilus_ensure_extension_points (void);
+void nautilus_ensure_extension_builtins (void);
+
+gboolean nautilus_file_can_rename_files (GList *files);
+
+GList * nautilus_file_list_from_uri_list (GList *uris);
+
+gchar * nautilus_uri_to_native_uri (const gchar *uri);
+
+NautilusQueryRecursive location_settings_search_get_recursive (void);
+NautilusQueryRecursive location_settings_search_get_recursive_for_location (GFile *location);
+
+gboolean check_schema_available (const gchar *schema_id);
diff --git a/src/nautilus-file.c b/src/nautilus-file.c
new file mode 100644
index 0000000..b340cfc
--- /dev/null
+++ b/src/nautilus-file.c
@@ -0,0 +1,9586 @@
+/*
+ * nautilus-file.c: Nautilus file model.
+ *
+ * Copyright (C) 1999, 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/>.
+ *
+ * Author: Darin Adler <darin@bentspoon.com>
+ */
+
+#include "nautilus-file.h"
+
+#ifndef NAUTILUS_COMPILATION
+#define NAUTILUS_COMPILATION
+#endif
+#include <libnautilus-extension/nautilus-extension-private.h>
+
+#include <eel/eel-debug.h>
+#include <eel/eel-glib-extensions.h>
+#include <eel/eel-string.h>
+#include <eel/eel-vfs-extensions.h>
+#include <gdesktop-enums.h>
+#include <gio/gio.h>
+#include <glib.h>
+#include <glib/gi18n.h>
+#include <glib/gstdio.h>
+#include <gnome-autoar/gnome-autoar.h>
+#include <grp.h>
+#include <gtk/gtk.h>
+#include <libxml/parser.h>
+#include <sys/types.h>
+#include <limits.h>
+#include <pwd.h>
+#include <stdlib.h>
+#include <sys/stat.h>
+#include <sys/time.h>
+#include <time.h>
+#include <unistd.h>
+
+#define DEBUG_FLAG NAUTILUS_DEBUG_FILE
+#include "nautilus-debug.h"
+
+#include "nautilus-directory-notify.h"
+#include "nautilus-directory-private.h"
+#include "nautilus-enums.h"
+#include "nautilus-file-operations.h"
+#include "nautilus-file-private.h"
+#include "nautilus-file-undo-manager.h"
+#include "nautilus-file-undo-operations.h"
+#include "nautilus-file-utilities.h"
+#include "nautilus-global-preferences.h"
+#include "nautilus-icon-info.h"
+#include "nautilus-lib-self-check-functions.h"
+#include "nautilus-metadata.h"
+#include "nautilus-module.h"
+#include "nautilus-signaller.h"
+#include "nautilus-tag-manager.h"
+#include "nautilus-thumbnails.h"
+#include "nautilus-ui-utilities.h"
+#include "nautilus-vfs-file.h"
+#include "nautilus-video-mime-types.h"
+
+#ifdef HAVE_SELINUX
+#include <selinux/selinux.h>
+#endif
+
+/* Time in seconds to cache getpwuid results */
+#define GETPWUID_CACHE_TIME (5 * 60)
+
+#define ICON_NAME_THUMBNAIL_LOADING "image-loading"
+
+#undef NAUTILUS_FILE_DEBUG_REF
+#undef NAUTILUS_FILE_DEBUG_REF_VALGRIND
+
+#ifdef NAUTILUS_FILE_DEBUG_REF_VALGRIND
+#include <valgrind/valgrind.h>
+#define DEBUG_REF_PRINTF VALGRIND_PRINTF_BACKTRACE
+#else
+#define DEBUG_REF_PRINTF printf
+#endif
+
+#define MEGA_TO_BASE_RATE 1000000
+
+/* Files that start with these characters sort after files that don't. */
+#define SORT_LAST_CHAR1 '.'
+#define SORT_LAST_CHAR2 '#'
+
+/* Name of Nautilus trash directories */
+#define TRASH_DIRECTORY_NAME ".Trash"
+
+#define METADATA_ID_IS_LIST_MASK (1U << 31)
+
+typedef enum
+{
+ SHOW_HIDDEN = 1 << 0,
+} FilterOptions;
+
+typedef enum
+{
+ NAUTILUS_DATE_FORMAT_REGULAR = 0,
+ NAUTILUS_DATE_FORMAT_REGULAR_WITH_TIME = 1,
+ NAUTILUS_DATE_FORMAT_FULL = 2,
+} NautilusDateFormat;
+
+typedef void (*ModifyListFunction) (GList **list,
+ NautilusFile *file);
+
+enum
+{
+ CHANGED,
+ UPDATED_DEEP_COUNT_IN_PROGRESS,
+ LAST_SIGNAL
+};
+
+static guint signals[LAST_SIGNAL];
+
+static GHashTable *symbolic_links;
+
+static guint64 cached_thumbnail_limit;
+static NautilusSpeedTradeoffValue show_file_thumbs;
+
+static NautilusSpeedTradeoffValue show_directory_item_count;
+
+static GQuark attribute_name_q,
+ attribute_size_q,
+ attribute_type_q,
+ attribute_detailed_type_q,
+ attribute_modification_date_q,
+ attribute_date_modified_q,
+ attribute_date_modified_full_q,
+ attribute_date_modified_with_time_q,
+ attribute_accessed_date_q,
+ attribute_date_accessed_q,
+ attribute_date_accessed_full_q,
+ attribute_date_created_q,
+ attribute_date_created_full_q,
+ attribute_mime_type_q,
+ attribute_size_detail_q,
+ attribute_deep_size_q,
+ attribute_deep_file_count_q,
+ attribute_deep_directory_count_q,
+ attribute_deep_total_count_q,
+ attribute_search_relevance_q,
+ attribute_trashed_on_q,
+ attribute_trashed_on_full_q,
+ attribute_trash_orig_path_q,
+ attribute_recency_q,
+ attribute_permissions_q,
+ attribute_selinux_context_q,
+ attribute_octal_permissions_q,
+ attribute_owner_q,
+ attribute_group_q,
+ attribute_uri_q,
+ attribute_where_q,
+ attribute_link_target_q,
+ attribute_volume_q,
+ attribute_free_space_q,
+ attribute_starred_q;
+
+static void nautilus_file_info_iface_init (NautilusFileInfoInterface *iface);
+static char *nautilus_file_get_owner_as_string (NautilusFile *file,
+ gboolean include_real_name);
+static char *nautilus_file_get_type_as_string (NautilusFile *file);
+static char *nautilus_file_get_type_as_string_no_extra_text (NautilusFile *file);
+static char *nautilus_file_get_detailed_type_as_string (NautilusFile *file);
+static gboolean update_info_and_name (NautilusFile *file,
+ GFileInfo *info);
+static const char *nautilus_file_peek_display_name (NautilusFile *file);
+static const char *nautilus_file_peek_display_name_collation_key (NautilusFile *file);
+static void file_mount_unmounted (GMount *mount,
+ gpointer data);
+static void metadata_hash_free (GHashTable *hash);
+
+G_DEFINE_TYPE_WITH_CODE (NautilusFile, nautilus_file, G_TYPE_OBJECT,
+ G_IMPLEMENT_INTERFACE (NAUTILUS_TYPE_FILE_INFO,
+ nautilus_file_info_iface_init));
+
+static void
+nautilus_file_init (NautilusFile *file)
+{
+ file->details = G_TYPE_INSTANCE_GET_PRIVATE ((file), NAUTILUS_TYPE_FILE, NautilusFileDetails);
+
+ nautilus_file_clear_info (file);
+ nautilus_file_invalidate_extension_info_internal (file);
+
+ file->details->free_space = -1;
+}
+
+static GObject *
+nautilus_file_constructor (GType type,
+ guint n_construct_properties,
+ GObjectConstructParam *construct_params)
+{
+ GObject *object;
+ NautilusFile *file;
+
+ object = (*G_OBJECT_CLASS (nautilus_file_parent_class)->constructor)(type,
+ n_construct_properties,
+ construct_params);
+
+ file = NAUTILUS_FILE (object);
+
+ /* Set to default type after full construction */
+ if (NAUTILUS_FILE_GET_CLASS (file)->default_file_type != G_FILE_TYPE_UNKNOWN)
+ {
+ file->details->type = NAUTILUS_FILE_GET_CLASS (file)->default_file_type;
+ }
+
+ return object;
+}
+
+gboolean
+nautilus_file_set_display_name (NautilusFile *file,
+ const char *display_name,
+ const char *edit_name,
+ gboolean custom)
+{
+ gboolean changed;
+
+ if (custom && display_name == NULL)
+ {
+ /* We're re-setting a custom display name, invalidate it if
+ * we already set it so that the old one is re-read */
+ if (file->details->got_custom_display_name)
+ {
+ file->details->got_custom_display_name = FALSE;
+ nautilus_file_invalidate_attributes (file,
+ NAUTILUS_FILE_ATTRIBUTE_INFO);
+ }
+ return FALSE;
+ }
+
+ if (display_name == NULL || *display_name == 0)
+ {
+ return FALSE;
+ }
+
+ if (!custom && file->details->got_custom_display_name)
+ {
+ return FALSE;
+ }
+
+ if (edit_name == NULL)
+ {
+ edit_name = display_name;
+ }
+
+ changed = FALSE;
+
+ if (g_strcmp0 (file->details->display_name, display_name) != 0)
+ {
+ changed = TRUE;
+
+ g_clear_pointer (&file->details->display_name, g_ref_string_release);
+
+ if (g_strcmp0 (file->details->name, display_name) == 0)
+ {
+ file->details->display_name = g_ref_string_acquire (file->details->name);
+ }
+ else
+ {
+ file->details->display_name = g_ref_string_new (display_name);
+ }
+
+ g_free (file->details->display_name_collation_key);
+ file->details->display_name_collation_key = g_utf8_collate_key_for_filename (display_name, -1);
+ }
+
+ if (g_strcmp0 (file->details->edit_name, edit_name) != 0)
+ {
+ changed = TRUE;
+
+ g_clear_pointer (&file->details->edit_name, g_ref_string_release);
+ if (g_strcmp0 (file->details->display_name, edit_name) == 0)
+ {
+ file->details->edit_name = g_ref_string_acquire (file->details->display_name);
+ }
+ else
+ {
+ file->details->edit_name = g_ref_string_new (edit_name);
+ }
+ }
+
+ file->details->got_custom_display_name = custom;
+ return changed;
+}
+
+static void
+nautilus_file_clear_display_name (NautilusFile *file)
+{
+ g_clear_pointer (&file->details->display_name, g_ref_string_release);
+ g_free (file->details->display_name_collation_key);
+ file->details->display_name_collation_key = NULL;
+ g_clear_pointer (&file->details->edit_name, g_ref_string_release);
+}
+
+static gboolean
+foreach_metadata_free (gpointer key,
+ gpointer value,
+ gpointer user_data)
+{
+ guint id;
+
+ id = GPOINTER_TO_UINT (key);
+
+ if (id & METADATA_ID_IS_LIST_MASK)
+ {
+ g_strfreev ((char **) value);
+ }
+ else
+ {
+ g_free ((char *) value);
+ }
+ return TRUE;
+}
+
+
+static void
+metadata_hash_free (GHashTable *hash)
+{
+ g_hash_table_foreach_remove (hash,
+ foreach_metadata_free,
+ NULL);
+ g_hash_table_destroy (hash);
+}
+
+static gboolean
+_g_strv_equal (GStrv a,
+ GStrv b)
+{
+ if (g_strv_length (a) != g_strv_length (b))
+ {
+ return FALSE;
+ }
+
+ for (int i = 0; a[i] != NULL; i++)
+ {
+ if (strcmp (a[i], b[i]) != 0)
+ {
+ return FALSE;
+ }
+ }
+
+ return TRUE;
+}
+
+static gboolean
+metadata_hash_equal (GHashTable *hash1,
+ GHashTable *hash2)
+{
+ GHashTableIter iter;
+ gpointer key1, value1, value2;
+ guint id;
+
+ if (hash1 == NULL && hash2 == NULL)
+ {
+ return TRUE;
+ }
+
+ if (hash1 == NULL || hash2 == NULL)
+ {
+ return FALSE;
+ }
+
+ if (g_hash_table_size (hash1) !=
+ g_hash_table_size (hash2))
+ {
+ return FALSE;
+ }
+
+ g_hash_table_iter_init (&iter, hash1);
+ while (g_hash_table_iter_next (&iter, &key1, &value1))
+ {
+ value2 = g_hash_table_lookup (hash2, key1);
+ if (value2 == NULL)
+ {
+ return FALSE;
+ }
+ id = GPOINTER_TO_UINT (key1);
+ if (id & METADATA_ID_IS_LIST_MASK)
+ {
+ if (!_g_strv_equal ((char **) value1, (char **) value2))
+ {
+ return FALSE;
+ }
+ }
+ else
+ {
+ if (strcmp ((char *) value1, (char *) value2) != 0)
+ {
+ return FALSE;
+ }
+ }
+ }
+
+ return TRUE;
+}
+
+static void
+clear_metadata (NautilusFile *file)
+{
+ if (file->details->metadata)
+ {
+ metadata_hash_free (file->details->metadata);
+ file->details->metadata = NULL;
+ }
+}
+
+static GHashTable *
+get_metadata_from_info (GFileInfo *info)
+{
+ GHashTable *metadata;
+ char **attrs;
+ guint id;
+ int i;
+ GFileAttributeType type;
+ gpointer value;
+
+ attrs = g_file_info_list_attributes (info, "metadata");
+
+ metadata = g_hash_table_new (NULL, NULL);
+
+ for (i = 0; attrs[i] != NULL; i++)
+ {
+ id = nautilus_metadata_get_id (attrs[i] + strlen ("metadata::"));
+ if (id == 0)
+ {
+ continue;
+ }
+
+ if (!g_file_info_get_attribute_data (info, attrs[i],
+ &type, &value, NULL))
+ {
+ continue;
+ }
+
+ if (type == G_FILE_ATTRIBUTE_TYPE_STRING)
+ {
+ g_hash_table_insert (metadata, GUINT_TO_POINTER (id),
+ g_strdup ((char *) value));
+ }
+ else if (type == G_FILE_ATTRIBUTE_TYPE_STRINGV)
+ {
+ id |= METADATA_ID_IS_LIST_MASK;
+ g_hash_table_insert (metadata, GUINT_TO_POINTER (id),
+ g_strdupv ((char **) value));
+ }
+ }
+
+ g_strfreev (attrs);
+
+ return metadata;
+}
+
+gboolean
+nautilus_file_update_metadata_from_info (NautilusFile *file,
+ GFileInfo *info)
+{
+ gboolean changed = FALSE;
+
+ if (g_file_info_has_namespace (info, "metadata"))
+ {
+ GHashTable *metadata;
+
+ metadata = get_metadata_from_info (info);
+ if (!metadata_hash_equal (metadata,
+ file->details->metadata))
+ {
+ changed = TRUE;
+ clear_metadata (file);
+ file->details->metadata = metadata;
+ }
+ else
+ {
+ metadata_hash_free (metadata);
+ }
+ }
+ else if (file->details->metadata)
+ {
+ changed = TRUE;
+ clear_metadata (file);
+ }
+ return changed;
+}
+
+void
+nautilus_file_clear_info (NautilusFile *file)
+{
+ file->details->got_file_info = FALSE;
+ if (file->details->get_info_error)
+ {
+ g_error_free (file->details->get_info_error);
+ file->details->get_info_error = NULL;
+ }
+ /* Reset to default type, which might be other than unknown for
+ * special kinds of files like the desktop or a search directory */
+ file->details->type = NAUTILUS_FILE_GET_CLASS (file)->default_file_type;
+
+ if (!file->details->got_custom_display_name)
+ {
+ nautilus_file_clear_display_name (file);
+ }
+
+ if (!file->details->got_custom_activation_uri &&
+ file->details->activation_uri != NULL)
+ {
+ g_free (file->details->activation_uri);
+ file->details->activation_uri = NULL;
+ }
+
+ if (file->details->icon != NULL)
+ {
+ g_object_unref (file->details->icon);
+ file->details->icon = NULL;
+ }
+
+ g_free (file->details->thumbnail_path);
+ file->details->thumbnail_path = NULL;
+ file->details->thumbnailing_failed = FALSE;
+
+ file->details->is_symlink = FALSE;
+ file->details->is_hidden = FALSE;
+ file->details->is_mountpoint = FALSE;
+ file->details->uid = -1;
+ file->details->gid = -1;
+ file->details->can_read = TRUE;
+ file->details->can_write = TRUE;
+ file->details->can_execute = TRUE;
+ file->details->can_delete = TRUE;
+ file->details->can_trash = TRUE;
+ file->details->can_rename = TRUE;
+ file->details->can_mount = FALSE;
+ file->details->can_unmount = FALSE;
+ file->details->can_eject = FALSE;
+ file->details->can_start = FALSE;
+ file->details->can_start_degraded = FALSE;
+ file->details->can_stop = FALSE;
+ file->details->start_stop_type = G_DRIVE_START_STOP_TYPE_UNKNOWN;
+ file->details->can_poll_for_media = FALSE;
+ file->details->is_media_check_automatic = FALSE;
+ file->details->has_permissions = FALSE;
+ file->details->permissions = 0;
+ file->details->size = -1;
+ file->details->sort_order = 0;
+ file->details->mtime = 0;
+ file->details->atime = 0;
+ file->details->btime = 0;
+ file->details->trash_time = 0;
+ file->details->recency = 0;
+ g_free (file->details->symlink_name);
+ file->details->symlink_name = NULL;
+ g_clear_pointer (&file->details->mime_type, g_ref_string_release);
+ g_free (file->details->selinux_context);
+ file->details->selinux_context = NULL;
+ g_free (file->details->description);
+ file->details->description = NULL;
+ g_clear_pointer (&file->details->owner, g_ref_string_release);
+ g_clear_pointer (&file->details->owner_real, g_ref_string_release);
+ g_clear_pointer (&file->details->group, g_ref_string_release);
+
+ g_clear_pointer (&file->details->filesystem_id, g_ref_string_release);
+
+ clear_metadata (file);
+}
+
+NautilusDirectory *
+nautilus_file_get_directory (NautilusFile *file)
+{
+ g_return_val_if_fail (NAUTILUS_IS_FILE (file), NULL);
+
+ return file->details->directory;
+}
+
+void
+nautilus_file_set_directory (NautilusFile *file,
+ NautilusDirectory *directory)
+{
+ char *parent_uri;
+
+ g_clear_object (&file->details->directory);
+ g_free (file->details->directory_name_collation_key);
+
+ file->details->directory = nautilus_directory_ref (directory);
+
+ parent_uri = nautilus_file_get_parent_uri (file);
+ file->details->directory_name_collation_key = g_utf8_collate_key_for_filename (parent_uri, -1);
+ g_free (parent_uri);
+}
+
+static NautilusFile *
+nautilus_file_new_from_filename (NautilusDirectory *directory,
+ const char *filename,
+ gboolean self_owned)
+{
+ NautilusFile *file;
+
+ g_assert (NAUTILUS_IS_DIRECTORY (directory));
+ g_assert (filename != NULL);
+ g_assert (filename[0] != '\0');
+
+ file = nautilus_directory_new_file_from_filename (directory, filename, self_owned);
+ file->details->name = g_ref_string_new (filename);
+
+#ifdef NAUTILUS_FILE_DEBUG_REF
+ DEBUG_REF_PRINTF ("%10p ref'd", file);
+#endif
+
+ return file;
+}
+
+static void
+modify_link_hash_table (NautilusFile *file,
+ ModifyListFunction modify_function)
+{
+ char *target_uri;
+ gboolean found;
+ gpointer original_key;
+ GList **list_ptr;
+
+ /* Check if there is a symlink name. If none, we are OK. */
+ if (file->details->symlink_name == NULL || !nautilus_file_is_symbolic_link (file))
+ {
+ return;
+ }
+
+ /* Create the hash table first time through. */
+ if (symbolic_links == NULL)
+ {
+ symbolic_links = g_hash_table_new (g_str_hash, g_str_equal);
+ }
+
+ target_uri = nautilus_file_get_symbolic_link_target_uri (file);
+
+ /* Find the old contents of the hash table. */
+ found = g_hash_table_lookup_extended
+ (symbolic_links, target_uri,
+ &original_key, (gpointer *) &list_ptr);
+ if (!found)
+ {
+ list_ptr = g_new0 (GList *, 1);
+ original_key = g_strdup (target_uri);
+ g_hash_table_insert (symbolic_links, original_key, list_ptr);
+ }
+ (*modify_function)(list_ptr, file);
+ if (*list_ptr == NULL)
+ {
+ g_hash_table_remove (symbolic_links, target_uri);
+ g_free (list_ptr);
+ g_free (original_key);
+ }
+ g_free (target_uri);
+}
+
+static void
+symbolic_link_weak_notify (gpointer data,
+ GObject *where_the_object_was)
+{
+ GList **list = data;
+ /* This really shouldn't happen, but we're seeing some strange things in
+ * bug #358172 where the symlink hashtable isn't correctly updated. */
+ *list = g_list_remove (*list, where_the_object_was);
+}
+
+static void
+add_to_link_hash_table_list (GList **list,
+ NautilusFile *file)
+{
+ if (g_list_find (*list, file) != NULL)
+ {
+ g_warning ("Adding file to symlink_table multiple times. "
+ "Please add feedback of what you were doing at http://bugzilla.gnome.org/show_bug.cgi?id=358172\n");
+ return;
+ }
+ g_object_weak_ref (G_OBJECT (file), symbolic_link_weak_notify, list);
+ *list = g_list_prepend (*list, file);
+}
+
+static void
+add_to_link_hash_table (NautilusFile *file)
+{
+ modify_link_hash_table (file, add_to_link_hash_table_list);
+}
+
+static void
+remove_from_link_hash_table_list (GList **list,
+ NautilusFile *file)
+{
+ if (g_list_find (*list, file) != NULL)
+ {
+ g_object_weak_unref (G_OBJECT (file), symbolic_link_weak_notify, list);
+ *list = g_list_remove (*list, file);
+ }
+}
+
+static void
+remove_from_link_hash_table (NautilusFile *file)
+{
+ modify_link_hash_table (file, remove_from_link_hash_table_list);
+}
+
+NautilusFile *
+nautilus_file_new_from_info (NautilusDirectory *directory,
+ GFileInfo *info)
+{
+ NautilusFile *file;
+
+ g_return_val_if_fail (NAUTILUS_IS_DIRECTORY (directory), NULL);
+ g_return_val_if_fail (info != NULL, NULL);
+
+ file = NAUTILUS_FILE (g_object_new (NAUTILUS_TYPE_VFS_FILE, NULL));
+ nautilus_file_set_directory (file, directory);
+
+ update_info_and_name (file, info);
+
+#ifdef NAUTILUS_FILE_DEBUG_REF
+ DEBUG_REF_PRINTF ("%10p ref'd", file);
+#endif
+
+ return file;
+}
+
+static NautilusFileInfo *
+nautilus_file_get_internal (GFile *location,
+ gboolean create)
+{
+ gboolean self_owned;
+ NautilusDirectory *directory;
+ NautilusFile *file;
+ GFile *parent;
+ char *basename;
+
+ g_assert (location != NULL);
+
+ parent = g_file_get_parent (location);
+
+ self_owned = FALSE;
+ if (parent == NULL)
+ {
+ self_owned = TRUE;
+ parent = g_object_ref (location);
+ }
+
+ /* Get object that represents the directory. */
+ directory = nautilus_directory_get_internal (parent, create);
+
+ g_object_unref (parent);
+
+ /* Get the name for the file. */
+ if (self_owned && directory != NULL)
+ {
+ basename = nautilus_directory_get_name_for_self_as_new_file (directory);
+ }
+ else
+ {
+ basename = g_file_get_basename (location);
+ }
+ /* Check to see if it's a file that's already known. */
+ if (directory == NULL)
+ {
+ file = NULL;
+ }
+ else if (self_owned)
+ {
+ file = directory->details->as_file;
+ }
+ else
+ {
+ file = nautilus_directory_find_file_by_name (directory, basename);
+ }
+
+ /* Ref or create the file. */
+ if (file != NULL)
+ {
+ nautilus_file_ref (file);
+ }
+ else if (create)
+ {
+ file = nautilus_file_new_from_filename (directory, basename, self_owned);
+ if (self_owned)
+ {
+ g_assert (directory->details->as_file == NULL);
+ directory->details->as_file = file;
+ }
+ else
+ {
+ nautilus_directory_add_file (directory, file);
+ }
+ }
+
+ g_free (basename);
+ nautilus_directory_unref (directory);
+
+ return NAUTILUS_FILE_INFO (file);
+}
+
+NautilusFile *
+nautilus_file_get (GFile *location)
+{
+ g_return_val_if_fail (G_IS_FILE (location), NULL);
+
+ return NAUTILUS_FILE (nautilus_file_get_internal (location, TRUE));
+}
+
+NautilusFile *
+nautilus_file_get_existing (GFile *location)
+{
+ g_return_val_if_fail (G_IS_FILE (location), NULL);
+
+ return NAUTILUS_FILE (nautilus_file_get_internal (location, FALSE));
+}
+
+NautilusFile *
+nautilus_file_get_existing_by_uri (const char *uri)
+{
+ g_autoptr (GFile) location = NULL;
+
+ location = g_file_new_for_uri (uri);
+
+ return nautilus_file_get_existing (location);
+}
+
+NautilusFile *
+nautilus_file_get_by_uri (const char *uri)
+{
+ g_autoptr (GFile) location = NULL;
+
+ location = g_file_new_for_uri (uri);
+
+ return nautilus_file_get (location);
+}
+
+gboolean
+nautilus_file_is_self_owned (NautilusFile *file)
+{
+ return file->details->directory->details->as_file == file;
+}
+
+static void
+finalize (GObject *object)
+{
+ NautilusDirectory *directory;
+ NautilusFile *file;
+ char *uri;
+
+ file = NAUTILUS_FILE (object);
+
+ g_assert (file->details->operations_in_progress == NULL);
+
+ if (file->details->is_thumbnailing)
+ {
+ uri = nautilus_file_get_uri (file);
+ nautilus_thumbnail_remove_from_queue (uri);
+ g_free (uri);
+ }
+
+ nautilus_async_destroying_file (file);
+
+ remove_from_link_hash_table (file);
+
+ directory = file->details->directory;
+
+ if (nautilus_file_is_self_owned (file))
+ {
+ directory->details->as_file = NULL;
+ }
+ else
+ {
+ if (!file->details->is_gone)
+ {
+ nautilus_directory_remove_file (directory, file);
+ }
+ }
+
+ if (file->details->get_info_error)
+ {
+ g_error_free (file->details->get_info_error);
+ }
+
+ nautilus_directory_unref (directory);
+ g_clear_pointer (&file->details->name, g_ref_string_release);
+ g_clear_pointer (&file->details->display_name, g_ref_string_release);
+ g_free (file->details->display_name_collation_key);
+ g_free (file->details->directory_name_collation_key);
+ g_clear_pointer (&file->details->edit_name, g_ref_string_release);
+ if (file->details->icon)
+ {
+ g_object_unref (file->details->icon);
+ }
+ g_free (file->details->thumbnail_path);
+ g_free (file->details->symlink_name);
+ g_clear_pointer (&file->details->mime_type, g_ref_string_release);
+ g_clear_pointer (&file->details->owner, g_ref_string_release);
+ g_clear_pointer (&file->details->owner_real, g_ref_string_release);
+ g_clear_pointer (&file->details->group, g_ref_string_release);
+ g_free (file->details->selinux_context);
+ g_free (file->details->description);
+ g_free (file->details->activation_uri);
+ g_clear_object (&file->details->custom_icon);
+
+ if (file->details->thumbnail)
+ {
+ g_object_unref (file->details->thumbnail);
+ }
+
+ if (file->details->mount)
+ {
+ g_signal_handlers_disconnect_by_func (file->details->mount, file_mount_unmounted, file);
+ g_object_unref (file->details->mount);
+ }
+
+ g_clear_pointer (&file->details->filesystem_id, g_ref_string_release);
+ g_clear_pointer (&file->details->filesystem_type, g_ref_string_release);
+ g_free (file->details->trash_orig_path);
+
+ g_list_free_full (file->details->mime_list, g_free);
+ g_list_free_full (file->details->pending_extension_emblems, g_free);
+ g_list_free_full (file->details->extension_emblems, g_free);
+ g_list_free_full (file->details->pending_info_providers, g_object_unref);
+
+ if (file->details->pending_extension_attributes)
+ {
+ g_hash_table_destroy (file->details->pending_extension_attributes);
+ }
+
+ if (file->details->extension_attributes)
+ {
+ g_hash_table_destroy (file->details->extension_attributes);
+ }
+
+ if (file->details->metadata)
+ {
+ metadata_hash_free (file->details->metadata);
+ }
+
+ g_free (file->details->fts_snippet);
+
+ G_OBJECT_CLASS (nautilus_file_parent_class)->finalize (object);
+}
+
+NautilusFile *
+nautilus_file_ref (NautilusFile *file)
+{
+ if (file == NULL)
+ {
+ return NULL;
+ }
+ g_return_val_if_fail (NAUTILUS_IS_FILE (file), NULL);
+
+#ifdef NAUTILUS_FILE_DEBUG_REF
+ DEBUG_REF_PRINTF ("%10p ref'd", file);
+#endif
+
+ return g_object_ref (file);
+}
+
+void
+nautilus_file_unref (NautilusFile *file)
+{
+ if (file == NULL)
+ {
+ return;
+ }
+
+ g_return_if_fail (NAUTILUS_IS_FILE (file));
+
+#ifdef NAUTILUS_FILE_DEBUG_REF
+ DEBUG_REF_PRINTF ("%10p unref'd", file);
+#endif
+
+ g_object_unref (file);
+}
+
+/**
+ * nautilus_file_get_parent_uri_for_display:
+ *
+ * Get the uri for the parent directory.
+ *
+ * @file: The file in question.
+ *
+ * Return value: A string representing the parent's location,
+ * formatted for user display (including stripping "file://"
+ * and adding trailing slash).
+ * If the parent is NULL, returns the empty string.
+ */
+char *
+nautilus_file_get_parent_uri_for_display (NautilusFile *file)
+{
+ g_autoptr (GFile) parent = NULL;
+ char *result;
+
+ g_assert (NAUTILUS_IS_FILE (file));
+
+ parent = nautilus_file_get_parent_location (file);
+ if (parent)
+ {
+ g_autofree gchar *parse_name = g_file_get_parse_name (parent);
+
+ /* Ensure a trailing slash to emphasize it is a directory */
+ if (g_str_has_suffix (parse_name, G_DIR_SEPARATOR_S))
+ {
+ result = g_steal_pointer (&parse_name);
+ }
+ else
+ {
+ result = g_strconcat (parse_name, G_DIR_SEPARATOR_S, NULL);
+ }
+ }
+ else
+ {
+ result = g_strdup ("");
+ }
+
+ return result;
+}
+
+/**
+ * nautilus_file_get_parent_uri:
+ *
+ * Get the uri for the parent directory.
+ *
+ * @file: The file in question.
+ *
+ * Return value: A string for the parent's location, in "raw URI" form.
+ * Use nautilus_file_get_parent_uri_for_display instead if the
+ * result is to be displayed on-screen.
+ * If the parent is NULL, returns the empty string.
+ */
+char *
+nautilus_file_get_parent_uri (NautilusFile *file)
+{
+ g_return_val_if_fail (NAUTILUS_IS_FILE (file), NULL);
+
+ return nautilus_file_info_get_parent_uri (NAUTILUS_FILE_INFO (file));
+}
+
+GFile *
+nautilus_file_get_parent_location (NautilusFile *file)
+{
+ g_return_val_if_fail (NAUTILUS_IS_FILE (file), NULL);
+
+ return nautilus_file_info_get_parent_location (NAUTILUS_FILE_INFO (file));
+}
+
+NautilusFile *
+nautilus_file_get_parent (NautilusFile *file)
+{
+ NautilusFileInfo *file_info;
+
+ g_return_val_if_fail (NAUTILUS_IS_FILE (file), NULL);
+
+ file_info = NAUTILUS_FILE_INFO (file);
+
+ return NAUTILUS_FILE (nautilus_file_info_get_parent_info (file_info));
+}
+
+/**
+ * nautilus_file_can_read:
+ *
+ * Check whether the user is allowed to read the contents of this file.
+ *
+ * @file: The file to check.
+ *
+ * Return value: FALSE if the user is definitely not allowed to read
+ * the contents of the file. If the user has read permission, or
+ * the code can't tell whether the user has read permission,
+ * returns TRUE (so failures must always be handled).
+ */
+gboolean
+nautilus_file_can_read (NautilusFile *file)
+{
+ g_return_val_if_fail (NAUTILUS_IS_FILE (file), FALSE);
+
+ return file->details->can_read;
+}
+
+/**
+ * nautilus_file_can_write:
+ *
+ * Check whether the user is allowed to write to this file.
+ *
+ * @file: The file to check.
+ *
+ * Return value: FALSE if the user is definitely not allowed to write
+ * to the file. If the user has write permission, or
+ * the code can't tell whether the user has write permission,
+ * returns TRUE (so failures must always be handled).
+ */
+gboolean
+nautilus_file_can_write (NautilusFile *file)
+{
+ g_return_val_if_fail (NAUTILUS_IS_FILE (file), FALSE);
+
+ return nautilus_file_info_can_write (NAUTILUS_FILE_INFO (file));
+}
+
+/**
+ * nautilus_file_can_execute:
+ *
+ * Check whether the user is allowed to execute this file.
+ *
+ * @file: The file to check.
+ *
+ * Return value: FALSE if the user is definitely not allowed to execute
+ * the file. If the user has execute permission, or
+ * the code can't tell whether the user has execute permission,
+ * returns TRUE (so failures must always be handled).
+ */
+gboolean
+nautilus_file_can_execute (NautilusFile *file)
+{
+ g_return_val_if_fail (NAUTILUS_IS_FILE (file), FALSE);
+
+ return file->details->can_execute;
+}
+
+gboolean
+nautilus_file_can_mount (NautilusFile *file)
+{
+ g_return_val_if_fail (NAUTILUS_IS_FILE (file), FALSE);
+
+ return file->details->can_mount;
+}
+
+gboolean
+nautilus_file_can_unmount (NautilusFile *file)
+{
+ g_return_val_if_fail (NAUTILUS_IS_FILE (file), FALSE);
+
+ return file->details->can_unmount ||
+ (file->details->mount != NULL &&
+ g_mount_can_unmount (file->details->mount));
+}
+
+gboolean
+nautilus_file_can_eject (NautilusFile *file)
+{
+ g_return_val_if_fail (NAUTILUS_IS_FILE (file), FALSE);
+
+ return file->details->can_eject ||
+ (file->details->mount != NULL &&
+ g_mount_can_eject (file->details->mount));
+}
+
+gboolean
+nautilus_file_can_start (NautilusFile *file)
+{
+ gboolean ret;
+ GDrive *drive;
+
+ g_return_val_if_fail (NAUTILUS_IS_FILE (file), FALSE);
+
+ ret = FALSE;
+
+ if (file->details->can_start)
+ {
+ ret = TRUE;
+ goto out;
+ }
+
+ if (file->details->mount != NULL)
+ {
+ drive = g_mount_get_drive (file->details->mount);
+ if (drive != NULL)
+ {
+ ret = g_drive_can_start (drive);
+ g_object_unref (drive);
+ }
+ }
+
+out:
+ return ret;
+}
+
+gboolean
+nautilus_file_can_start_degraded (NautilusFile *file)
+{
+ gboolean ret;
+ GDrive *drive;
+
+ g_return_val_if_fail (NAUTILUS_IS_FILE (file), FALSE);
+
+ ret = FALSE;
+
+ if (file->details->can_start_degraded)
+ {
+ ret = TRUE;
+ goto out;
+ }
+
+ if (file->details->mount != NULL)
+ {
+ drive = g_mount_get_drive (file->details->mount);
+ if (drive != NULL)
+ {
+ ret = g_drive_can_start_degraded (drive);
+ g_object_unref (drive);
+ }
+ }
+
+out:
+ return ret;
+}
+
+gboolean
+nautilus_file_can_poll_for_media (NautilusFile *file)
+{
+ gboolean ret;
+ GDrive *drive;
+
+ g_return_val_if_fail (NAUTILUS_IS_FILE (file), FALSE);
+
+ ret = FALSE;
+
+ if (file->details->can_poll_for_media)
+ {
+ ret = TRUE;
+ goto out;
+ }
+
+ if (file->details->mount != NULL)
+ {
+ drive = g_mount_get_drive (file->details->mount);
+ if (drive != NULL)
+ {
+ ret = g_drive_can_poll_for_media (drive);
+ g_object_unref (drive);
+ }
+ }
+
+out:
+ return ret;
+}
+
+gboolean
+nautilus_file_is_media_check_automatic (NautilusFile *file)
+{
+ gboolean ret;
+ GDrive *drive;
+
+ g_return_val_if_fail (NAUTILUS_IS_FILE (file), FALSE);
+
+ ret = FALSE;
+
+ if (file->details->is_media_check_automatic)
+ {
+ ret = TRUE;
+ goto out;
+ }
+
+ if (file->details->mount != NULL)
+ {
+ drive = g_mount_get_drive (file->details->mount);
+ if (drive != NULL)
+ {
+ ret = g_drive_is_media_check_automatic (drive);
+ g_object_unref (drive);
+ }
+ }
+
+out:
+ return ret;
+}
+
+
+gboolean
+nautilus_file_can_stop (NautilusFile *file)
+{
+ gboolean ret;
+ GDrive *drive;
+
+ g_return_val_if_fail (NAUTILUS_IS_FILE (file), FALSE);
+
+ ret = FALSE;
+
+ if (file->details->can_stop)
+ {
+ ret = TRUE;
+ goto out;
+ }
+
+ if (file->details->mount != NULL)
+ {
+ drive = g_mount_get_drive (file->details->mount);
+ if (drive != NULL)
+ {
+ ret = g_drive_can_stop (drive);
+ g_object_unref (drive);
+ }
+ }
+
+out:
+ return ret;
+}
+
+GDriveStartStopType
+nautilus_file_get_start_stop_type (NautilusFile *file)
+{
+ GDriveStartStopType ret;
+ GDrive *drive;
+
+ g_return_val_if_fail (NAUTILUS_IS_FILE (file), FALSE);
+
+ ret = file->details->start_stop_type;
+ if (ret != G_DRIVE_START_STOP_TYPE_UNKNOWN)
+ {
+ goto out;
+ }
+
+ if (file->details->mount != NULL)
+ {
+ drive = g_mount_get_drive (file->details->mount);
+ if (drive != NULL)
+ {
+ ret = g_drive_get_start_stop_type (drive);
+ g_object_unref (drive);
+ }
+ }
+
+out:
+ return ret;
+}
+
+void
+nautilus_file_mount (NautilusFile *file,
+ GMountOperation *mount_op,
+ GCancellable *cancellable,
+ NautilusFileOperationCallback callback,
+ gpointer callback_data)
+{
+ GError *error;
+
+ if (NAUTILUS_FILE_GET_CLASS (file)->mount == NULL)
+ {
+ if (callback)
+ {
+ error = NULL;
+ g_set_error_literal (&error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED,
+ _("This file cannot be mounted"));
+ callback (file, NULL, error, callback_data);
+ g_error_free (error);
+ }
+ }
+ else
+ {
+ NAUTILUS_FILE_GET_CLASS (file)->mount (file, mount_op, cancellable, callback, callback_data);
+ }
+}
+
+typedef struct
+{
+ NautilusFile *file;
+ NautilusFileOperationCallback callback;
+ gpointer callback_data;
+} UnmountData;
+
+static void
+unmount_done (void *callback_data)
+{
+ UnmountData *data;
+
+ data = (UnmountData *) callback_data;
+ if (data->callback)
+ {
+ data->callback (data->file, NULL, NULL, data->callback_data);
+ }
+ nautilus_file_unref (data->file);
+ g_free (data);
+}
+
+void
+nautilus_file_unmount (NautilusFile *file,
+ GMountOperation *mount_op,
+ GCancellable *cancellable,
+ NautilusFileOperationCallback callback,
+ gpointer callback_data)
+{
+ GError *error;
+ UnmountData *data;
+
+ if (file->details->can_unmount)
+ {
+ if (NAUTILUS_FILE_GET_CLASS (file)->unmount != NULL)
+ {
+ NAUTILUS_FILE_GET_CLASS (file)->unmount (file, mount_op, cancellable, callback, callback_data);
+ }
+ else
+ {
+ if (callback)
+ {
+ error = NULL;
+ g_set_error_literal (&error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED,
+ _("This file cannot be unmounted"));
+ callback (file, NULL, error, callback_data);
+ g_error_free (error);
+ }
+ }
+ }
+ else if (file->details->mount != NULL &&
+ g_mount_can_unmount (file->details->mount))
+ {
+ GtkWindow *parent;
+
+ parent = gtk_mount_operation_get_parent (GTK_MOUNT_OPERATION (mount_op));
+
+ data = g_new0 (UnmountData, 1);
+ data->file = nautilus_file_ref (file);
+ data->callback = callback;
+ data->callback_data = callback_data;
+ nautilus_file_operations_unmount_mount_full (parent, file->details->mount, mount_op, FALSE, TRUE, unmount_done, data);
+ }
+ else if (callback)
+ {
+ callback (file, NULL, NULL, callback_data);
+ }
+}
+
+void
+nautilus_file_eject (NautilusFile *file,
+ GMountOperation *mount_op,
+ GCancellable *cancellable,
+ NautilusFileOperationCallback callback,
+ gpointer callback_data)
+{
+ GError *error;
+ UnmountData *data;
+
+ if (file->details->can_eject)
+ {
+ if (NAUTILUS_FILE_GET_CLASS (file)->eject != NULL)
+ {
+ NAUTILUS_FILE_GET_CLASS (file)->eject (file, mount_op, cancellable, callback, callback_data);
+ }
+ else
+ {
+ if (callback)
+ {
+ error = NULL;
+ g_set_error_literal (&error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED,
+ _("This file cannot be ejected"));
+ callback (file, NULL, error, callback_data);
+ g_error_free (error);
+ }
+ }
+ }
+ else if (file->details->mount != NULL &&
+ g_mount_can_eject (file->details->mount))
+ {
+ GtkWindow *parent;
+
+ parent = gtk_mount_operation_get_parent (GTK_MOUNT_OPERATION (mount_op));
+
+ data = g_new0 (UnmountData, 1);
+ data->file = nautilus_file_ref (file);
+ data->callback = callback;
+ data->callback_data = callback_data;
+ nautilus_file_operations_unmount_mount_full (parent, file->details->mount, mount_op, TRUE, TRUE, unmount_done, data);
+ }
+ else if (callback)
+ {
+ callback (file, NULL, NULL, callback_data);
+ }
+}
+
+void
+nautilus_file_start (NautilusFile *file,
+ GMountOperation *start_op,
+ GCancellable *cancellable,
+ NautilusFileOperationCallback callback,
+ gpointer callback_data)
+{
+ GError *error;
+
+ if ((file->details->can_start || file->details->can_start_degraded) &&
+ NAUTILUS_FILE_GET_CLASS (file)->start != NULL)
+ {
+ NAUTILUS_FILE_GET_CLASS (file)->start (file, start_op, cancellable, callback, callback_data);
+ }
+ else
+ {
+ if (callback)
+ {
+ error = NULL;
+ g_set_error_literal (&error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED,
+ _("This file cannot be started"));
+ callback (file, NULL, error, callback_data);
+ g_error_free (error);
+ }
+ }
+}
+
+static void
+file_stop_callback (GObject *source_object,
+ GAsyncResult *res,
+ gpointer callback_data)
+{
+ NautilusFileOperation *op;
+ gboolean stopped;
+ GError *error;
+
+ op = callback_data;
+
+ error = NULL;
+ stopped = g_drive_stop_finish (G_DRIVE (source_object),
+ res, &error);
+
+ if (!stopped &&
+ error->domain == G_IO_ERROR &&
+ (error->code == G_IO_ERROR_FAILED_HANDLED ||
+ error->code == G_IO_ERROR_CANCELLED))
+ {
+ g_error_free (error);
+ error = NULL;
+ }
+
+ nautilus_file_operation_complete (op, NULL, error);
+ if (error)
+ {
+ g_error_free (error);
+ }
+}
+
+void
+nautilus_file_stop (NautilusFile *file,
+ GMountOperation *mount_op,
+ GCancellable *cancellable,
+ NautilusFileOperationCallback callback,
+ gpointer callback_data)
+{
+ GError *error;
+
+ if (NAUTILUS_FILE_GET_CLASS (file)->stop != NULL)
+ {
+ if (file->details->can_stop)
+ {
+ NAUTILUS_FILE_GET_CLASS (file)->stop (file, mount_op, cancellable, callback, callback_data);
+ }
+ else
+ {
+ if (callback)
+ {
+ error = NULL;
+ g_set_error_literal (&error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED,
+ _("This file cannot be stopped"));
+ callback (file, NULL, error, callback_data);
+ g_error_free (error);
+ }
+ }
+ }
+ else
+ {
+ GDrive *drive;
+
+ drive = NULL;
+ if (file->details->mount != NULL)
+ {
+ drive = g_mount_get_drive (file->details->mount);
+ }
+
+ if (drive != NULL && g_drive_can_stop (drive))
+ {
+ NautilusFileOperation *op;
+
+ op = nautilus_file_operation_new (file, callback, callback_data);
+ if (cancellable)
+ {
+ g_object_unref (op->cancellable);
+ op->cancellable = g_object_ref (cancellable);
+ }
+
+ g_drive_stop (drive,
+ G_MOUNT_UNMOUNT_NONE,
+ mount_op,
+ op->cancellable,
+ file_stop_callback,
+ op);
+ }
+ else
+ {
+ if (callback)
+ {
+ error = NULL;
+ g_set_error_literal (&error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED,
+ _("This file cannot be stopped"));
+ callback (file, NULL, error, callback_data);
+ g_error_free (error);
+ }
+ }
+
+ if (drive != NULL)
+ {
+ g_object_unref (drive);
+ }
+ }
+}
+
+void
+nautilus_file_poll_for_media (NautilusFile *file)
+{
+ if (file->details->can_poll_for_media)
+ {
+ if (NAUTILUS_FILE_GET_CLASS (file)->stop != NULL)
+ {
+ NAUTILUS_FILE_GET_CLASS (file)->poll_for_media (file);
+ }
+ }
+ else if (file->details->mount != NULL)
+ {
+ GDrive *drive;
+ drive = g_mount_get_drive (file->details->mount);
+ if (drive != NULL)
+ {
+ g_drive_poll_for_media (drive,
+ NULL, /* cancellable */
+ NULL, /* GAsyncReadyCallback */
+ NULL); /* user_data */
+ g_object_unref (drive);
+ }
+ }
+}
+
+/**
+ * nautilus_file_can_rename:
+ *
+ * Check whether the user is allowed to change the name of the file.
+ *
+ * @file: The file to check.
+ *
+ * Return value: FALSE if the user is definitely not allowed to change
+ * the name of the file. If the user is allowed to change the name, or
+ * the code can't tell whether the user is allowed to change the name,
+ * returns TRUE (so rename failures must always be handled).
+ */
+gboolean
+nautilus_file_can_rename (NautilusFile *file)
+{
+ gboolean can_rename;
+
+ g_return_val_if_fail (NAUTILUS_IS_FILE (file), FALSE);
+
+ /* Nonexistent files can't be renamed. */
+ if (nautilus_file_is_gone (file))
+ {
+ return FALSE;
+ }
+
+ /* Self-owned files can't be renamed */
+ if (nautilus_file_is_self_owned (file))
+ {
+ return FALSE;
+ }
+
+ if (nautilus_file_is_home (file))
+ {
+ return FALSE;
+ }
+
+ can_rename = TRUE;
+
+ if (!can_rename)
+ {
+ return FALSE;
+ }
+
+ return file->details->can_rename;
+}
+
+gboolean
+nautilus_file_can_delete (NautilusFile *file)
+{
+ g_return_val_if_fail (NAUTILUS_IS_FILE (file), FALSE);
+
+ /* Nonexistent files can't be deleted. */
+ if (nautilus_file_is_gone (file))
+ {
+ return FALSE;
+ }
+
+ /* Self-owned files can't be deleted */
+ if (nautilus_file_is_self_owned (file))
+ {
+ return FALSE;
+ }
+
+ return file->details->can_delete;
+}
+
+gboolean
+nautilus_file_can_trash (NautilusFile *file)
+{
+ g_return_val_if_fail (NAUTILUS_IS_FILE (file), FALSE);
+
+ /* Nonexistent files can't be deleted. */
+ if (nautilus_file_is_gone (file))
+ {
+ return FALSE;
+ }
+
+ /* Self-owned files can't be deleted */
+ if (nautilus_file_is_self_owned (file))
+ {
+ return FALSE;
+ }
+
+ return file->details->can_trash;
+}
+
+GFile *
+nautilus_file_get_location (NautilusFile *file)
+{
+ g_return_val_if_fail (NAUTILUS_IS_FILE (file), NULL);
+
+ return nautilus_file_info_get_location (NAUTILUS_FILE_INFO (file));
+}
+
+/* Return the actual uri associated with the passed-in file. */
+char *
+nautilus_file_get_uri (NautilusFile *file)
+{
+ g_return_val_if_fail (NAUTILUS_IS_FILE (file), NULL);
+
+ return nautilus_file_info_get_uri (NAUTILUS_FILE_INFO (file));
+}
+
+char *
+nautilus_file_get_uri_scheme (NautilusFile *file)
+{
+ g_return_val_if_fail (NAUTILUS_IS_FILE (file), NULL);
+
+ return nautilus_file_info_get_uri_scheme (NAUTILUS_FILE_INFO (file));
+}
+
+
+gboolean
+nautilus_file_opens_in_view (NautilusFile *file)
+{
+ return nautilus_file_is_directory (file);
+}
+
+NautilusFileOperation *
+nautilus_file_operation_new (NautilusFile *file,
+ NautilusFileOperationCallback callback,
+ gpointer callback_data)
+{
+ NautilusFileOperation *op;
+
+ op = g_new0 (NautilusFileOperation, 1);
+ op->file = nautilus_file_ref (file);
+ op->callback = callback;
+ op->callback_data = callback_data;
+ op->cancellable = g_cancellable_new ();
+
+ op->file->details->operations_in_progress = g_list_prepend
+ (op->file->details->operations_in_progress, op);
+
+ return op;
+}
+
+static void
+nautilus_file_operation_remove (NautilusFileOperation *op)
+{
+ GList *l;
+ NautilusFile *file;
+
+ op->file->details->operations_in_progress = g_list_remove
+ (op->file->details->operations_in_progress, op);
+
+
+ for (l = op->files; l != NULL; l = l->next)
+ {
+ file = NAUTILUS_FILE (l->data);
+ file->details->operations_in_progress = g_list_remove
+ (file->details->operations_in_progress, op);
+ }
+}
+
+void
+nautilus_file_operation_free (NautilusFileOperation *op)
+{
+ nautilus_file_operation_remove (op);
+
+ if (op->files == NULL)
+ {
+ nautilus_file_unref (op->file);
+ }
+ else
+ {
+ nautilus_file_list_free (op->files);
+ }
+
+ g_object_unref (op->cancellable);
+ if (op->free_data)
+ {
+ op->free_data (op->data);
+ }
+
+ if (op->undo_info != NULL)
+ {
+ nautilus_file_undo_manager_set_action (op->undo_info);
+ g_object_unref (op->undo_info);
+ }
+
+ g_free (op);
+}
+
+void
+nautilus_file_operation_complete (NautilusFileOperation *op,
+ GFile *result_file,
+ GError *error)
+{
+ /* Claim that something changed even if the operation failed.
+ * This makes it easier for some clients who see the "reverting"
+ * as "changing back".
+ */
+ nautilus_file_operation_remove (op);
+
+ if (op->files == NULL)
+ {
+ nautilus_file_changed (op->file);
+ }
+
+ if (op->callback)
+ {
+ (*op->callback)(op->file, result_file, error, op->callback_data);
+ }
+
+ if (error != NULL)
+ {
+ g_clear_object (&op->undo_info);
+ }
+
+ nautilus_file_operation_free (op);
+}
+
+void
+nautilus_file_operation_cancel (NautilusFileOperation *op)
+{
+ /* Cancel the operation if it's still in progress. */
+ g_cancellable_cancel (op->cancellable);
+}
+
+static void
+rename_get_info_callback (GObject *source_object,
+ GAsyncResult *res,
+ gpointer callback_data)
+{
+ NautilusFileOperation *op;
+ NautilusDirectory *directory;
+ NautilusFile *existing_file;
+ char *old_uri;
+ char *new_uri;
+ const char *new_name;
+ GFileInfo *new_info;
+ GError *error;
+
+ op = callback_data;
+
+ error = NULL;
+ new_info = g_file_query_info_finish (G_FILE (source_object), res, &error);
+ if (new_info != NULL)
+ {
+ g_autoptr (GFile) old_location = NULL;
+ g_autoptr (GFile) new_location = NULL;
+
+ directory = op->file->details->directory;
+
+ new_name = g_file_info_get_name (new_info);
+
+ /* If there was another file by the same name in this
+ * directory and it is not the same file that we are
+ * renaming, mark it gone.
+ */
+ existing_file = nautilus_directory_find_file_by_name (directory, new_name);
+ if (existing_file != NULL && existing_file != op->file)
+ {
+ nautilus_file_mark_gone (existing_file);
+ nautilus_file_changed (existing_file);
+ }
+
+ old_location = nautilus_file_get_location (op->file);
+ old_uri = g_file_get_uri (old_location);
+
+ update_info_and_name (op->file, new_info);
+
+ new_location = nautilus_file_get_location (op->file);
+ new_uri = g_file_get_uri (new_location);
+
+ nautilus_directory_moved (old_uri, new_uri);
+ nautilus_tag_manager_update_moved_uris (nautilus_tag_manager_get (),
+ old_location,
+ new_location);
+
+ g_free (new_uri);
+ g_free (old_uri);
+
+ g_object_unref (new_info);
+ }
+ nautilus_file_operation_complete (op, NULL, error);
+ if (error)
+ {
+ g_error_free (error);
+ }
+}
+
+static void
+rename_callback (GObject *source_object,
+ GAsyncResult *res,
+ gpointer callback_data)
+{
+ NautilusFileOperation *op;
+ GFile *new_file;
+ GError *error;
+
+ op = callback_data;
+
+ error = NULL;
+ new_file = g_file_set_display_name_finish (G_FILE (source_object),
+ res, &error);
+
+ if (new_file != NULL)
+ {
+ if (op->undo_info != NULL)
+ {
+ nautilus_file_undo_info_rename_set_data_post (NAUTILUS_FILE_UNDO_INFO_RENAME (op->undo_info),
+ new_file);
+ }
+ g_file_query_info_async (new_file,
+ NAUTILUS_FILE_DEFAULT_ATTRIBUTES,
+ 0,
+ G_PRIORITY_DEFAULT,
+ op->cancellable,
+ rename_get_info_callback, op);
+ }
+ else
+ {
+ nautilus_file_operation_complete (op, NULL, error);
+ g_error_free (error);
+ }
+}
+
+static gboolean
+name_is (NautilusFile *file,
+ const char *new_name)
+{
+ const char *old_name;
+ old_name = file->details->name;
+ return strcmp (new_name, old_name) == 0;
+}
+
+static gchar *
+nautilus_file_can_rename_file (NautilusFile *file,
+ const char *new_name,
+ NautilusFileOperationCallback callback,
+ gpointer callback_data)
+{
+ GError *error;
+ gchar *new_file_name;
+
+ /* Return an error for incoming names containing path separators.
+ * But not for .desktop files as '/' are allowed for them */
+ if (strstr (new_name, "/") != NULL)
+ {
+ error = g_error_new (G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT,
+ _("Slashes are not allowed in filenames"));
+ if (callback != NULL)
+ {
+ (*callback)(file, NULL, error, callback_data);
+ }
+ g_error_free (error);
+ return NULL;
+ }
+
+ /* Can't rename a file that's already gone.
+ * We need to check this here because there may be a new
+ * file with the same name.
+ */
+ if (nautilus_file_rename_handle_file_gone (file, callback, callback_data))
+ {
+ return NULL;
+ }
+
+ /* Test the name-hasn't-changed case explicitly, for two reasons.
+ * (1) rename returns an error if new & old are same.
+ * (2) We don't want to send file-changed signal if nothing changed.
+ */
+ if (name_is (file, new_name))
+ {
+ if (callback != NULL)
+ {
+ (*callback)(file, NULL, NULL, callback_data);
+ }
+ return NULL;
+ }
+
+ /* Self-owned files can't be renamed. Test the name-not-actually-changing
+ * case before this case.
+ */
+ if (nautilus_file_is_self_owned (file))
+ {
+ /* Claim that something changed even if the rename
+ * failed. This makes it easier for some clients who
+ * see the "reverting" to the old name as "changing
+ * back".
+ */
+ nautilus_file_changed (file);
+ error = g_error_new (G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED,
+ _("Toplevel files cannot be renamed"));
+
+ if (callback != NULL)
+ {
+ (*callback)(file, NULL, error, callback_data);
+ }
+ g_error_free (error);
+
+ return NULL;
+ }
+
+ new_file_name = g_strdup (new_name);
+
+ return new_file_name;
+}
+
+void
+nautilus_file_rename (NautilusFile *file,
+ const char *new_name,
+ NautilusFileOperationCallback callback,
+ gpointer callback_data)
+{
+ NautilusFileOperation *op;
+ char *old_name;
+ char *new_file_name;
+ GFile *location;
+
+ g_return_if_fail (NAUTILUS_IS_FILE (file));
+ g_return_if_fail (new_name != NULL);
+ g_return_if_fail (callback != NULL);
+
+ new_file_name = nautilus_file_can_rename_file (file,
+ new_name,
+ callback,
+ callback_data);
+
+ if (new_file_name == NULL)
+ {
+ return;
+ }
+
+ /* Set up a renaming operation. */
+ op = nautilus_file_operation_new (file, callback, callback_data);
+ op->is_rename = TRUE;
+ location = nautilus_file_get_location (file);
+
+ /* Tell the undo manager a rename is taking place */
+ if (!nautilus_file_undo_manager_is_operating ())
+ {
+ op->undo_info = nautilus_file_undo_info_rename_new ();
+
+ old_name = nautilus_file_get_display_name (file);
+ nautilus_file_undo_info_rename_set_data_pre (NAUTILUS_FILE_UNDO_INFO_RENAME (op->undo_info),
+ location, old_name, new_file_name);
+ g_free (old_name);
+ }
+
+ /* Do the renaming. */
+ g_file_set_display_name_async (location,
+ new_file_name,
+ G_PRIORITY_DEFAULT,
+ op->cancellable,
+ rename_callback,
+ op);
+ g_free (new_file_name);
+ g_object_unref (location);
+}
+
+gboolean
+nautilus_file_rename_handle_file_gone (NautilusFile *file,
+ NautilusFileOperationCallback callback,
+ gpointer callback_data)
+{
+ GError *error;
+
+ if (nautilus_file_is_gone (file))
+ {
+ /* Claim that something changed even if the rename
+ * failed. This makes it easier for some clients who
+ * see the "reverting" to the old name as "changing
+ * back".
+ */
+ nautilus_file_changed (file);
+ error = g_error_new (G_IO_ERROR, G_IO_ERROR_NOT_FOUND,
+ _("File not found"));
+ if (callback)
+ {
+ (*callback)(file, NULL, error, callback_data);
+ }
+ g_error_free (error);
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+typedef struct
+{
+ NautilusFileOperation *op;
+ NautilusFile *file;
+} BatchRenameData;
+
+static void
+batch_rename_get_info_callback (GObject *source_object,
+ GAsyncResult *res,
+ gpointer callback_data)
+{
+ NautilusFileOperation *op;
+ NautilusDirectory *directory;
+ NautilusFile *existing_file;
+ char *old_uri;
+ char *new_uri;
+ const char *new_name;
+ GFileInfo *new_info;
+ GError *error;
+ BatchRenameData *data;
+
+ data = callback_data;
+
+ op = data->op;
+ op->file = data->file;
+
+ error = NULL;
+ new_info = g_file_query_info_finish (G_FILE (source_object), res, &error);
+ if (new_info != NULL)
+ {
+ old_uri = nautilus_file_get_uri (op->file);
+
+ new_name = g_file_info_get_name (new_info);
+
+ directory = op->file->details->directory;
+
+ /* If there was another file by the same name in this
+ * directory and it is not the same file that we are
+ * renaming, mark it gone.
+ */
+ existing_file = nautilus_directory_find_file_by_name (directory, new_name);
+ if (existing_file != NULL && existing_file != op->file)
+ {
+ nautilus_file_mark_gone (existing_file);
+ nautilus_file_changed (existing_file);
+ }
+
+ update_info_and_name (op->file, new_info);
+
+ new_uri = nautilus_file_get_uri (op->file);
+ nautilus_directory_moved (old_uri, new_uri);
+ g_free (new_uri);
+ g_free (old_uri);
+ g_object_unref (new_info);
+ }
+
+ op->renamed_files++;
+
+ if (op->files == NULL ||
+ op->renamed_files + op->skipped_files == g_list_length (op->files))
+ {
+ nautilus_file_operation_complete (op, NULL, error);
+ }
+
+ g_free (data);
+
+ if (error)
+ {
+ g_error_free (error);
+ }
+}
+
+static void
+real_batch_rename (GList *files,
+ GList *new_names,
+ NautilusFileOperationCallback callback,
+ gpointer callback_data)
+{
+ GList *l1, *l2, *old_files, *new_files;
+ NautilusFileOperation *op;
+ GFile *location;
+ GString *new_name;
+ NautilusFile *file;
+ GError *error;
+ GFile *new_file;
+ BatchRenameData *data;
+
+ error = NULL;
+ old_files = NULL;
+ new_files = NULL;
+
+ /* Set up a batch renaming operation. */
+ op = nautilus_file_operation_new (files->data, callback, callback_data);
+ op->files = nautilus_file_list_copy (files);
+ op->renamed_files = 0;
+ op->skipped_files = 0;
+
+ for (l1 = files->next; l1 != NULL; l1 = l1->next)
+ {
+ file = NAUTILUS_FILE (l1->data);
+
+ file->details->operations_in_progress = g_list_prepend (file->details->operations_in_progress,
+ op);
+ }
+
+ for (l1 = files, l2 = new_names; l1 != NULL && l2 != NULL; l1 = l1->next, l2 = l2->next)
+ {
+ g_autofree gchar *new_file_name = NULL;
+ file = NAUTILUS_FILE (l1->data);
+ new_name = l2->data;
+
+ location = nautilus_file_get_location (file);
+
+ new_file_name = nautilus_file_can_rename_file (file,
+ new_name->str,
+ callback,
+ callback_data);
+
+ if (new_file_name == NULL)
+ {
+ op->skipped_files++;
+
+ continue;
+ }
+
+ g_assert (G_IS_FILE (location));
+
+ /* Do the renaming. */
+ new_file = g_file_set_display_name (location,
+ new_file_name,
+ op->cancellable,
+ &error);
+
+ data = g_new0 (BatchRenameData, 1);
+ data->op = op;
+ data->file = file;
+
+ g_file_query_info_async (new_file,
+ NAUTILUS_FILE_DEFAULT_ATTRIBUTES,
+ 0,
+ G_PRIORITY_DEFAULT,
+ op->cancellable,
+ batch_rename_get_info_callback,
+ data);
+
+ if (error != NULL)
+ {
+ g_warning ("Batch rename for file \"%s\" failed", nautilus_file_get_name (file));
+ g_error_free (error);
+ error = NULL;
+
+ op->skipped_files++;
+ }
+ else
+ {
+ old_files = g_list_append (old_files, location);
+ new_files = g_list_append (new_files, new_file);
+ }
+ }
+
+ /* Tell the undo manager a batch rename is taking place if at least
+ * a file has been renamed*/
+ if (!nautilus_file_undo_manager_is_operating () && op->skipped_files != g_list_length (files))
+ {
+ op->undo_info = nautilus_file_undo_info_batch_rename_new (g_list_length (new_files));
+
+ nautilus_file_undo_info_batch_rename_set_data_pre (NAUTILUS_FILE_UNDO_INFO_BATCH_RENAME (op->undo_info),
+ old_files);
+
+ nautilus_file_undo_info_batch_rename_set_data_post (NAUTILUS_FILE_UNDO_INFO_BATCH_RENAME (op->undo_info),
+ new_files);
+
+ nautilus_file_undo_manager_set_action (op->undo_info);
+ }
+
+ if (op->skipped_files == g_list_length (files))
+ {
+ nautilus_file_operation_complete (op, NULL, error);
+ }
+}
+
+void
+nautilus_file_batch_rename (GList *files,
+ GList *new_names,
+ NautilusFileOperationCallback callback,
+ gpointer callback_data)
+{
+ real_batch_rename (files,
+ new_names,
+ callback,
+ callback_data);
+}
+
+gboolean
+nautilus_file_rename_in_progress (NautilusFile *file)
+{
+ GList *node;
+ NautilusFileOperation *op;
+
+ for (node = file->details->operations_in_progress; node != NULL; node = node->next)
+ {
+ op = node->data;
+ if (op->is_rename)
+ {
+ return TRUE;
+ }
+ }
+ return FALSE;
+}
+
+void
+nautilus_file_cancel (NautilusFile *file,
+ NautilusFileOperationCallback callback,
+ gpointer callback_data)
+{
+ GList *node, *next;
+ NautilusFileOperation *op;
+
+ for (node = file->details->operations_in_progress; node != NULL; node = next)
+ {
+ next = node->next;
+ op = node->data;
+
+ g_assert (op->file == file);
+ if (op->callback == callback && op->callback_data == callback_data)
+ {
+ nautilus_file_operation_cancel (op);
+ }
+ }
+}
+
+gboolean
+nautilus_file_matches_uri (NautilusFile *file,
+ const char *match_uri)
+{
+ GFile *match_file, *location;
+ gboolean result;
+
+ g_return_val_if_fail (NAUTILUS_IS_FILE (file), FALSE);
+ g_return_val_if_fail (match_uri != NULL, FALSE);
+
+ location = nautilus_file_get_location (file);
+ match_file = g_file_new_for_uri (match_uri);
+ result = g_file_equal (location, match_file);
+ g_object_unref (location);
+ g_object_unref (match_file);
+
+ return result;
+}
+
+int
+nautilus_file_compare_location (NautilusFile *file_1,
+ NautilusFile *file_2)
+{
+ GFile *loc_a, *loc_b;
+ gboolean res;
+
+ loc_a = nautilus_file_get_location (file_1);
+ loc_b = nautilus_file_get_location (file_2);
+
+ res = !g_file_equal (loc_a, loc_b);
+
+ g_object_unref (loc_a);
+ g_object_unref (loc_b);
+
+ return (gint) res;
+}
+
+/**
+ * nautilus_file_has_local_path:
+ *
+ * @file: a #NautilusFile
+ *
+ * Checks whether this file has an obtainable local paths. Usually, this means
+ * the local path can be obtained by calling g_file_get_path(); this includes
+ * native and FUSE files. As an exception, the local URI for files in recent://
+ * can only be obtained from the G_FILE_ATTRIBUTE_STANDARD_TARGET_URI attribute.
+ *
+ * Returns: %TRUE if a local path is known to be obtainable for this file.
+ */
+gboolean
+nautilus_file_has_local_path (NautilusFile *file)
+{
+ g_return_val_if_fail (NAUTILUS_IS_FILE (file), FALSE);
+
+ return nautilus_directory_is_local_or_fuse (file->details->directory);
+}
+
+static void
+update_link (NautilusFile *link_file,
+ NautilusFile *target_file)
+{
+ g_assert (NAUTILUS_IS_FILE (link_file));
+ g_assert (NAUTILUS_IS_FILE (target_file));
+
+ /* FIXME bugzilla.gnome.org 42044: If we don't put any code
+ * here then the hash table is a waste of time.
+ */
+}
+
+static GList *
+get_link_files (NautilusFile *target_file)
+{
+ char *uri;
+ GList **link_files;
+
+ if (symbolic_links == NULL)
+ {
+ link_files = NULL;
+ }
+ else
+ {
+ uri = nautilus_file_get_uri (target_file);
+ link_files = g_hash_table_lookup (symbolic_links, uri);
+ g_free (uri);
+ }
+ if (link_files)
+ {
+ return nautilus_file_list_copy (*link_files);
+ }
+ return NULL;
+}
+
+static void
+update_links_if_target (NautilusFile *target_file)
+{
+ GList *link_files, *p;
+
+ link_files = get_link_files (target_file);
+ for (p = link_files; p != NULL; p = p->next)
+ {
+ update_link (NAUTILUS_FILE (p->data), target_file);
+ }
+ nautilus_file_list_free (link_files);
+}
+
+static gboolean
+update_info_internal (NautilusFile *file,
+ GFileInfo *info,
+ gboolean update_name)
+{
+ GList *node;
+ gboolean changed;
+ gboolean is_symlink, is_hidden, is_mountpoint;
+ gboolean has_permissions;
+ guint32 permissions;
+ gboolean can_read, can_write, can_execute, can_delete, can_trash, can_rename, can_mount, can_unmount, can_eject;
+ gboolean can_start, can_start_degraded, can_stop, can_poll_for_media, is_media_check_automatic;
+ GDriveStartStopType start_stop_type;
+ gboolean thumbnailing_failed;
+ int uid, gid;
+ goffset size;
+ int sort_order;
+ time_t atime, mtime, btime;
+ time_t trash_time;
+ time_t recency;
+ GTimeVal g_trash_time;
+ const char *time_string;
+ const char *symlink_name, *mime_type, *selinux_context, *name, *thumbnail_path;
+ GFileType file_type;
+ GIcon *icon;
+ char *old_activation_uri;
+ const char *activation_uri;
+ const char *description;
+ const char *filesystem_id;
+ const char *trash_orig_path;
+ const char *group, *owner, *owner_real;
+ gboolean free_owner, free_group;
+
+ if (file->details->is_gone)
+ {
+ return FALSE;
+ }
+
+ if (info == NULL)
+ {
+ nautilus_file_mark_gone (file);
+ return TRUE;
+ }
+
+ file->details->file_info_is_up_to_date = TRUE;
+
+ /* FIXME bugzilla.gnome.org 42044: Need to let links that
+ * point to the old name know that the file has been renamed.
+ */
+
+ remove_from_link_hash_table (file);
+
+ changed = FALSE;
+
+ if (!file->details->got_file_info)
+ {
+ changed = TRUE;
+ }
+ file->details->got_file_info = TRUE;
+
+ changed |= nautilus_file_set_display_name (file,
+ g_file_info_get_display_name (info),
+ g_file_info_get_edit_name (info),
+ FALSE);
+
+ file_type = g_file_info_get_file_type (info);
+ if (file->details->type != file_type)
+ {
+ changed = TRUE;
+ }
+ file->details->type = file_type;
+
+ if (!file->details->got_custom_activation_uri &&
+ (g_file_info_get_attribute_boolean (info, G_FILE_ATTRIBUTE_STANDARD_IS_VIRTUAL) ||
+ file_type == G_FILE_TYPE_SHORTCUT ||
+ nautilus_file_is_in_recent (file)))
+ {
+ activation_uri = g_file_info_get_attribute_string (info, G_FILE_ATTRIBUTE_STANDARD_TARGET_URI);
+ if (activation_uri == NULL)
+ {
+ if (file->details->activation_uri)
+ {
+ g_free (file->details->activation_uri);
+ file->details->activation_uri = NULL;
+ changed = TRUE;
+ }
+ }
+ else
+ {
+ old_activation_uri = file->details->activation_uri;
+ file->details->activation_uri = g_strdup (activation_uri);
+
+ if (old_activation_uri)
+ {
+ if (strcmp (old_activation_uri,
+ file->details->activation_uri) != 0)
+ {
+ changed = TRUE;
+ }
+ g_free (old_activation_uri);
+ }
+ else
+ {
+ changed = TRUE;
+ }
+ }
+ }
+
+ is_symlink = g_file_info_get_is_symlink (info);
+ if (file->details->is_symlink != is_symlink)
+ {
+ changed = TRUE;
+ }
+ file->details->is_symlink = is_symlink;
+
+ is_hidden = g_file_info_get_is_hidden (info) || g_file_info_get_is_backup (info);
+ if (file->details->is_hidden != is_hidden)
+ {
+ changed = TRUE;
+ }
+ file->details->is_hidden = is_hidden;
+
+ is_mountpoint = g_file_info_get_attribute_boolean (info, G_FILE_ATTRIBUTE_UNIX_IS_MOUNTPOINT);
+ if (file->details->is_mountpoint != is_mountpoint)
+ {
+ changed = TRUE;
+ }
+ file->details->is_mountpoint = is_mountpoint;
+
+ has_permissions = g_file_info_has_attribute (info, G_FILE_ATTRIBUTE_UNIX_MODE);
+ permissions = g_file_info_get_attribute_uint32 (info, G_FILE_ATTRIBUTE_UNIX_MODE);
+ if (file->details->has_permissions != has_permissions ||
+ file->details->permissions != permissions)
+ {
+ changed = TRUE;
+ }
+ file->details->has_permissions = has_permissions;
+ file->details->permissions = permissions;
+
+ /* We default to TRUE for this if we can't know */
+ can_read = TRUE;
+ can_write = TRUE;
+ can_execute = TRUE;
+ can_delete = TRUE;
+ can_rename = TRUE;
+ can_trash = FALSE;
+ can_mount = FALSE;
+ can_unmount = FALSE;
+ can_eject = FALSE;
+ can_start = FALSE;
+ can_start_degraded = FALSE;
+ can_stop = FALSE;
+ can_poll_for_media = FALSE;
+ is_media_check_automatic = FALSE;
+ start_stop_type = G_DRIVE_START_STOP_TYPE_UNKNOWN;
+ if (g_file_info_has_attribute (info, G_FILE_ATTRIBUTE_ACCESS_CAN_READ))
+ {
+ can_read = g_file_info_get_attribute_boolean (info,
+ G_FILE_ATTRIBUTE_ACCESS_CAN_READ);
+ }
+ if (g_file_info_has_attribute (info, G_FILE_ATTRIBUTE_ACCESS_CAN_WRITE))
+ {
+ can_write = g_file_info_get_attribute_boolean (info,
+ G_FILE_ATTRIBUTE_ACCESS_CAN_WRITE);
+ }
+ if (g_file_info_has_attribute (info, G_FILE_ATTRIBUTE_ACCESS_CAN_EXECUTE))
+ {
+ can_execute = g_file_info_get_attribute_boolean (info,
+ G_FILE_ATTRIBUTE_ACCESS_CAN_EXECUTE);
+ }
+ if (g_file_info_has_attribute (info, G_FILE_ATTRIBUTE_ACCESS_CAN_DELETE))
+ {
+ can_delete = g_file_info_get_attribute_boolean (info,
+ G_FILE_ATTRIBUTE_ACCESS_CAN_DELETE);
+ }
+ if (g_file_info_has_attribute (info, G_FILE_ATTRIBUTE_ACCESS_CAN_TRASH))
+ {
+ can_trash = g_file_info_get_attribute_boolean (info,
+ G_FILE_ATTRIBUTE_ACCESS_CAN_TRASH);
+ }
+ if (g_file_info_has_attribute (info, G_FILE_ATTRIBUTE_ACCESS_CAN_RENAME))
+ {
+ can_rename = g_file_info_get_attribute_boolean (info,
+ G_FILE_ATTRIBUTE_ACCESS_CAN_RENAME);
+ }
+ if (g_file_info_has_attribute (info, G_FILE_ATTRIBUTE_MOUNTABLE_CAN_MOUNT))
+ {
+ can_mount = g_file_info_get_attribute_boolean (info,
+ G_FILE_ATTRIBUTE_MOUNTABLE_CAN_MOUNT);
+ }
+ if (g_file_info_has_attribute (info, G_FILE_ATTRIBUTE_MOUNTABLE_CAN_UNMOUNT))
+ {
+ can_unmount = g_file_info_get_attribute_boolean (info,
+ G_FILE_ATTRIBUTE_MOUNTABLE_CAN_UNMOUNT);
+ }
+ if (g_file_info_has_attribute (info, G_FILE_ATTRIBUTE_MOUNTABLE_CAN_EJECT))
+ {
+ can_eject = g_file_info_get_attribute_boolean (info,
+ G_FILE_ATTRIBUTE_MOUNTABLE_CAN_EJECT);
+ }
+ if (g_file_info_has_attribute (info, G_FILE_ATTRIBUTE_MOUNTABLE_CAN_START))
+ {
+ can_start = g_file_info_get_attribute_boolean (info,
+ G_FILE_ATTRIBUTE_MOUNTABLE_CAN_START);
+ }
+ if (g_file_info_has_attribute (info, G_FILE_ATTRIBUTE_MOUNTABLE_CAN_START_DEGRADED))
+ {
+ can_start_degraded = g_file_info_get_attribute_boolean (info,
+ G_FILE_ATTRIBUTE_MOUNTABLE_CAN_START_DEGRADED);
+ }
+ if (g_file_info_has_attribute (info, G_FILE_ATTRIBUTE_MOUNTABLE_CAN_STOP))
+ {
+ can_stop = g_file_info_get_attribute_boolean (info,
+ G_FILE_ATTRIBUTE_MOUNTABLE_CAN_STOP);
+ }
+ if (g_file_info_has_attribute (info, G_FILE_ATTRIBUTE_MOUNTABLE_START_STOP_TYPE))
+ {
+ start_stop_type = g_file_info_get_attribute_uint32 (info,
+ G_FILE_ATTRIBUTE_MOUNTABLE_START_STOP_TYPE);
+ }
+ if (g_file_info_has_attribute (info, G_FILE_ATTRIBUTE_MOUNTABLE_CAN_POLL))
+ {
+ can_poll_for_media = g_file_info_get_attribute_boolean (info,
+ G_FILE_ATTRIBUTE_MOUNTABLE_CAN_POLL);
+ }
+ if (g_file_info_has_attribute (info, G_FILE_ATTRIBUTE_MOUNTABLE_IS_MEDIA_CHECK_AUTOMATIC))
+ {
+ is_media_check_automatic = g_file_info_get_attribute_boolean (info,
+ G_FILE_ATTRIBUTE_MOUNTABLE_IS_MEDIA_CHECK_AUTOMATIC);
+ }
+ if (file->details->can_read != can_read ||
+ file->details->can_write != can_write ||
+ file->details->can_execute != can_execute ||
+ file->details->can_delete != can_delete ||
+ file->details->can_trash != can_trash ||
+ file->details->can_rename != can_rename ||
+ file->details->can_mount != can_mount ||
+ file->details->can_unmount != can_unmount ||
+ file->details->can_eject != can_eject ||
+ file->details->can_start != can_start ||
+ file->details->can_start_degraded != can_start_degraded ||
+ file->details->can_stop != can_stop ||
+ file->details->start_stop_type != start_stop_type ||
+ file->details->can_poll_for_media != can_poll_for_media ||
+ file->details->is_media_check_automatic != is_media_check_automatic)
+ {
+ changed = TRUE;
+ }
+
+ file->details->can_read = can_read;
+ file->details->can_write = can_write;
+ file->details->can_execute = can_execute;
+ file->details->can_delete = can_delete;
+ file->details->can_trash = can_trash;
+ file->details->can_rename = can_rename;
+ file->details->can_mount = can_mount;
+ file->details->can_unmount = can_unmount;
+ file->details->can_eject = can_eject;
+ file->details->can_start = can_start;
+ file->details->can_start_degraded = can_start_degraded;
+ file->details->can_stop = can_stop;
+ file->details->start_stop_type = start_stop_type;
+ file->details->can_poll_for_media = can_poll_for_media;
+ file->details->is_media_check_automatic = is_media_check_automatic;
+
+ free_owner = FALSE;
+ owner = g_file_info_get_attribute_string (info, G_FILE_ATTRIBUTE_OWNER_USER);
+ owner_real = g_file_info_get_attribute_string (info, G_FILE_ATTRIBUTE_OWNER_USER_REAL);
+ free_group = FALSE;
+ group = g_file_info_get_attribute_string (info, G_FILE_ATTRIBUTE_OWNER_GROUP);
+
+ uid = -1;
+ gid = -1;
+ if (g_file_info_has_attribute (info, G_FILE_ATTRIBUTE_UNIX_UID))
+ {
+ uid = g_file_info_get_attribute_uint32 (info, G_FILE_ATTRIBUTE_UNIX_UID);
+ if (owner == NULL)
+ {
+ free_owner = TRUE;
+ owner = g_strdup_printf ("%d", uid);
+ }
+ }
+ if (g_file_info_has_attribute (info, G_FILE_ATTRIBUTE_UNIX_GID))
+ {
+ gid = g_file_info_get_attribute_uint32 (info, G_FILE_ATTRIBUTE_UNIX_GID);
+ if (group == NULL)
+ {
+ free_group = TRUE;
+ group = g_strdup_printf ("%d", gid);
+ }
+ }
+ if (file->details->uid != uid ||
+ file->details->gid != gid)
+ {
+ changed = TRUE;
+ }
+ file->details->uid = uid;
+ file->details->gid = gid;
+
+ if (g_strcmp0 (file->details->owner, owner) != 0)
+ {
+ changed = TRUE;
+ g_clear_pointer (&file->details->owner, g_ref_string_release);
+ file->details->owner = g_ref_string_new_intern (owner);
+ }
+
+ if (g_strcmp0 (file->details->owner_real, owner_real) != 0)
+ {
+ changed = TRUE;
+ g_clear_pointer (&file->details->owner_real, g_ref_string_release);
+ file->details->owner_real = g_ref_string_new_intern (owner_real);
+ }
+
+ if (g_strcmp0 (file->details->group, group) != 0)
+ {
+ changed = TRUE;
+ g_clear_pointer (&file->details->group, g_ref_string_release);
+ file->details->group = g_ref_string_new_intern (group);
+ }
+
+ if (free_owner)
+ {
+ g_free ((char *) owner);
+ }
+ if (free_group)
+ {
+ g_free ((char *) group);
+ }
+
+ size = -1;
+ if (g_file_info_has_attribute (info, G_FILE_ATTRIBUTE_STANDARD_SIZE))
+ {
+ size = g_file_info_get_size (info);
+ }
+ if (file->details->size != size)
+ {
+ changed = TRUE;
+ }
+ file->details->size = size;
+
+ sort_order = g_file_info_get_sort_order (info);
+ if (file->details->sort_order != sort_order)
+ {
+ changed = TRUE;
+ }
+ file->details->sort_order = sort_order;
+
+ atime = g_file_info_get_attribute_uint64 (info, G_FILE_ATTRIBUTE_TIME_ACCESS);
+ mtime = g_file_info_get_attribute_uint64 (info, G_FILE_ATTRIBUTE_TIME_MODIFIED);
+ btime = g_file_info_get_attribute_uint64 (info, G_FILE_ATTRIBUTE_TIME_CREATED);
+ if (file->details->atime != atime ||
+ file->details->mtime != mtime)
+ {
+ if (file->details->thumbnail == NULL)
+ {
+ file->details->thumbnail_is_up_to_date = FALSE;
+ }
+
+ changed = TRUE;
+ }
+ file->details->atime = atime;
+ file->details->mtime = mtime;
+ file->details->btime = btime;
+
+ if (file->details->thumbnail != NULL &&
+ file->details->thumbnail_mtime != 0 &&
+ file->details->thumbnail_mtime != mtime)
+ {
+ file->details->thumbnail_is_up_to_date = FALSE;
+ changed = TRUE;
+ }
+
+ icon = g_file_info_get_icon (info);
+ if (!g_icon_equal (icon, file->details->icon))
+ {
+ changed = TRUE;
+
+ if (file->details->icon)
+ {
+ g_object_unref (file->details->icon);
+ }
+ file->details->icon = g_object_ref (icon);
+ }
+
+ thumbnail_path = g_file_info_get_attribute_byte_string (info, G_FILE_ATTRIBUTE_THUMBNAIL_PATH);
+ if (g_strcmp0 (file->details->thumbnail_path, thumbnail_path) != 0)
+ {
+ changed = TRUE;
+ g_free (file->details->thumbnail_path);
+ file->details->thumbnail_path = g_strdup (thumbnail_path);
+ }
+
+ thumbnailing_failed = g_file_info_get_attribute_boolean (info, G_FILE_ATTRIBUTE_THUMBNAILING_FAILED);
+ if (file->details->thumbnailing_failed != thumbnailing_failed)
+ {
+ changed = TRUE;
+ file->details->thumbnailing_failed = thumbnailing_failed;
+ }
+
+ symlink_name = g_file_info_get_symlink_target (info);
+ if (g_strcmp0 (file->details->symlink_name, symlink_name) != 0)
+ {
+ changed = TRUE;
+ g_free (file->details->symlink_name);
+ file->details->symlink_name = g_strdup (symlink_name);
+ }
+
+ mime_type = g_file_info_get_content_type (info);
+ if (mime_type == NULL)
+ {
+ mime_type = g_file_info_get_attribute_string (info, G_FILE_ATTRIBUTE_STANDARD_FAST_CONTENT_TYPE);
+ }
+ if (g_strcmp0 (file->details->mime_type, mime_type) != 0)
+ {
+ changed = TRUE;
+ g_clear_pointer (&file->details->mime_type, g_ref_string_release);
+ file->details->mime_type = g_ref_string_new_intern (mime_type);
+ }
+
+ selinux_context = g_file_info_get_attribute_string (info, G_FILE_ATTRIBUTE_SELINUX_CONTEXT);
+ if (g_strcmp0 (file->details->selinux_context, selinux_context) != 0)
+ {
+ changed = TRUE;
+ g_free (file->details->selinux_context);
+ file->details->selinux_context = g_strdup (selinux_context);
+ }
+
+ description = g_file_info_get_attribute_string (info, G_FILE_ATTRIBUTE_STANDARD_DESCRIPTION);
+ if (g_strcmp0 (file->details->description, description) != 0)
+ {
+ changed = TRUE;
+ g_free (file->details->description);
+ file->details->description = g_strdup (description);
+ }
+
+ filesystem_id = g_file_info_get_attribute_string (info, G_FILE_ATTRIBUTE_ID_FILESYSTEM);
+ if (g_strcmp0 (file->details->filesystem_id, filesystem_id) != 0)
+ {
+ changed = TRUE;
+ g_clear_pointer (&file->details->filesystem_id, g_ref_string_release);
+ file->details->filesystem_id = g_ref_string_new_intern (filesystem_id);
+ }
+
+ trash_time = 0;
+ time_string = g_file_info_get_attribute_string (info, "trash::deletion-date");
+ if (time_string != NULL)
+ {
+ g_time_val_from_iso8601 (time_string, &g_trash_time);
+ trash_time = g_trash_time.tv_sec;
+ }
+ if (file->details->trash_time != trash_time)
+ {
+ changed = TRUE;
+ file->details->trash_time = trash_time;
+ }
+
+ recency = g_file_info_get_attribute_int64 (info, G_FILE_ATTRIBUTE_RECENT_MODIFIED);
+ if (file->details->recency != recency)
+ {
+ changed = TRUE;
+ file->details->recency = recency;
+ }
+
+ trash_orig_path = g_file_info_get_attribute_byte_string (info, "trash::orig-path");
+ if (g_strcmp0 (file->details->trash_orig_path, trash_orig_path) != 0)
+ {
+ changed = TRUE;
+ g_free (file->details->trash_orig_path);
+ file->details->trash_orig_path = g_strdup (trash_orig_path);
+ }
+
+ changed |=
+ nautilus_file_update_metadata_from_info (file, info);
+
+ if (update_name)
+ {
+ name = g_file_info_get_name (info);
+ if (file->details->name == NULL ||
+ strcmp (file->details->name, name) != 0)
+ {
+ changed = TRUE;
+
+ node = nautilus_directory_begin_file_name_change
+ (file->details->directory, file);
+
+ g_clear_pointer (&file->details->name, g_ref_string_release);
+ if (g_strcmp0 (file->details->display_name, name) == 0)
+ {
+ file->details->name = g_ref_string_acquire (file->details->display_name);
+ }
+ else
+ {
+ file->details->name = g_ref_string_new (name);
+ }
+
+ if (!file->details->got_custom_display_name &&
+ g_file_info_get_display_name (info) == NULL)
+ {
+ /* If the file info's display name is NULL,
+ * nautilus_file_set_display_name() did
+ * not unset the display name.
+ */
+ nautilus_file_clear_display_name (file);
+ }
+
+ nautilus_directory_end_file_name_change
+ (file->details->directory, file, node);
+ }
+ }
+
+ if (changed)
+ {
+ add_to_link_hash_table (file);
+
+ update_links_if_target (file);
+ }
+
+ return changed;
+}
+
+static gboolean
+update_info_and_name (NautilusFile *file,
+ GFileInfo *info)
+{
+ return update_info_internal (file, info, TRUE);
+}
+
+gboolean
+nautilus_file_update_info (NautilusFile *file,
+ GFileInfo *info)
+{
+ return update_info_internal (file, info, FALSE);
+}
+
+static gboolean
+update_name_internal (NautilusFile *file,
+ const char *name,
+ gboolean in_directory)
+{
+ GList *node;
+
+ g_assert (name != NULL);
+
+ if (file->details->is_gone)
+ {
+ return FALSE;
+ }
+
+ if (name_is (file, name))
+ {
+ return FALSE;
+ }
+
+ node = NULL;
+ if (in_directory)
+ {
+ node = nautilus_directory_begin_file_name_change
+ (file->details->directory, file);
+ }
+
+ g_clear_pointer (&file->details->name, g_ref_string_release);
+ file->details->name = g_ref_string_new (name);
+
+ if (!file->details->got_custom_display_name)
+ {
+ nautilus_file_clear_display_name (file);
+ }
+
+ if (in_directory)
+ {
+ nautilus_directory_end_file_name_change
+ (file->details->directory, file, node);
+ }
+
+ return TRUE;
+}
+
+gboolean
+nautilus_file_update_name (NautilusFile *file,
+ const char *name)
+{
+ gboolean ret;
+
+ ret = update_name_internal (file, name, TRUE);
+
+ if (ret)
+ {
+ update_links_if_target (file);
+ }
+
+ return ret;
+}
+
+gboolean
+nautilus_file_update_name_and_directory (NautilusFile *file,
+ const char *name,
+ NautilusDirectory *new_directory)
+{
+ NautilusDirectory *old_directory;
+ FileMonitors *monitors;
+
+ g_return_val_if_fail (NAUTILUS_IS_FILE (file), FALSE);
+ g_return_val_if_fail (NAUTILUS_IS_DIRECTORY (file->details->directory), FALSE);
+ g_return_val_if_fail (!file->details->is_gone, FALSE);
+ g_return_val_if_fail (!nautilus_file_is_self_owned (file), FALSE);
+ g_return_val_if_fail (NAUTILUS_IS_DIRECTORY (new_directory), FALSE);
+
+ old_directory = file->details->directory;
+ if (old_directory == new_directory)
+ {
+ if (name)
+ {
+ return update_name_internal (file, name, TRUE);
+ }
+ else
+ {
+ return FALSE;
+ }
+ }
+
+ nautilus_file_ref (file);
+
+ /* FIXME bugzilla.gnome.org 42044: Need to let links that
+ * point to the old name know that the file has been moved.
+ */
+
+ remove_from_link_hash_table (file);
+
+ monitors = nautilus_directory_remove_file_monitors (old_directory, file);
+ nautilus_directory_remove_file (old_directory, file);
+
+ nautilus_file_set_directory (file, new_directory);
+
+ if (name)
+ {
+ update_name_internal (file, name, FALSE);
+ }
+
+ nautilus_directory_add_file (new_directory, file);
+ nautilus_directory_add_file_monitors (new_directory, file, monitors);
+
+ add_to_link_hash_table (file);
+
+ update_links_if_target (file);
+
+ nautilus_file_unref (file);
+
+ return TRUE;
+}
+
+static Knowledge
+get_item_count (NautilusFile *file,
+ guint *count)
+{
+ gboolean known, unreadable;
+
+ known = nautilus_file_get_directory_item_count
+ (file, count, &unreadable);
+ if (!known)
+ {
+ return UNKNOWN;
+ }
+ if (unreadable)
+ {
+ return UNKNOWABLE;
+ }
+ return KNOWN;
+}
+
+static Knowledge
+get_size (NautilusFile *file,
+ goffset *size)
+{
+ /* If we tried and failed, then treat it like there is no size
+ * to know.
+ */
+ if (file->details->get_info_failed)
+ {
+ return UNKNOWABLE;
+ }
+
+ /* If the info is NULL that means we haven't even tried yet,
+ * so it's just unknown, not unknowable.
+ */
+ if (!file->details->got_file_info)
+ {
+ return UNKNOWN;
+ }
+
+ /* If we got info with no size in it, it means there is no
+ * such thing as a size as far as gnome-vfs is concerned,
+ * so "unknowable".
+ */
+ if (file->details->size == -1)
+ {
+ return UNKNOWABLE;
+ }
+
+ /* We have a size! */
+ *size = file->details->size;
+ return KNOWN;
+}
+
+static Knowledge
+get_time (NautilusFile *file,
+ time_t *time_out,
+ NautilusDateType type)
+{
+ time_t time;
+
+ /* If we tried and failed, then treat it like there is no size
+ * to know.
+ */
+ if (file->details->get_info_failed)
+ {
+ return UNKNOWABLE;
+ }
+
+ /* If the info is NULL that means we haven't even tried yet,
+ * so it's just unknown, not unknowable.
+ */
+ if (!file->details->got_file_info)
+ {
+ return UNKNOWN;
+ }
+
+ switch (type)
+ {
+ case NAUTILUS_DATE_TYPE_MODIFIED:
+ {
+ time = file->details->mtime;
+ }
+ break;
+
+ case NAUTILUS_DATE_TYPE_ACCESSED:
+ {
+ time = file->details->atime;
+ }
+ break;
+
+ case NAUTILUS_DATE_TYPE_CREATED:
+ {
+ time = file->details->btime;
+ }
+ break;
+
+ case NAUTILUS_DATE_TYPE_TRASHED:
+ {
+ time = file->details->trash_time;
+ }
+ break;
+
+ case NAUTILUS_DATE_TYPE_RECENCY:
+ {
+ time = file->details->recency;
+ }
+ break;
+
+ default:
+ {
+ g_assert_not_reached ();
+ }
+ break;
+ }
+
+ *time_out = time;
+
+ /* If we got info with no modification time in it, it means
+ * there is no such thing as a modification time as far as
+ * gnome-vfs is concerned, so "unknowable".
+ */
+ if (time == 0)
+ {
+ return UNKNOWABLE;
+ }
+ return KNOWN;
+}
+
+static int
+compare_directories_by_count (NautilusFile *file_1,
+ NautilusFile *file_2)
+{
+ /* Sort order:
+ * Directories with unknown # of items
+ * Directories with "unknowable" # of items
+ * Directories with 0 items
+ * Directories with n items
+ */
+
+ Knowledge count_known_1, count_known_2;
+ guint count_1, count_2;
+
+ count_known_1 = get_item_count (file_1, &count_1);
+ count_known_2 = get_item_count (file_2, &count_2);
+
+ if (count_known_1 > count_known_2)
+ {
+ return -1;
+ }
+ if (count_known_1 < count_known_2)
+ {
+ return +1;
+ }
+
+ /* count_known_1 and count_known_2 are equal now. Check if count
+ * details are UNKNOWABLE or UNKNOWN.
+ */
+ if (count_known_1 == UNKNOWABLE || count_known_1 == UNKNOWN)
+ {
+ return 0;
+ }
+
+ if (count_1 < count_2)
+ {
+ return -1;
+ }
+ if (count_1 > count_2)
+ {
+ return +1;
+ }
+
+ return 0;
+}
+
+static int
+compare_files_by_size (NautilusFile *file_1,
+ NautilusFile *file_2)
+{
+ /* Sort order:
+ * Files with unknown size.
+ * Files with "unknowable" size.
+ * Files with smaller sizes.
+ * Files with large sizes.
+ */
+
+ Knowledge size_known_1, size_known_2;
+ goffset size_1 = 0, size_2 = 0;
+
+ size_known_1 = get_size (file_1, &size_1);
+ size_known_2 = get_size (file_2, &size_2);
+
+ if (size_known_1 > size_known_2)
+ {
+ return -1;
+ }
+ if (size_known_1 < size_known_2)
+ {
+ return +1;
+ }
+
+ /* size_known_1 and size_known_2 are equal now. Check if size
+ * details are UNKNOWABLE or UNKNOWN
+ */
+ if (size_known_1 == UNKNOWABLE || size_known_1 == UNKNOWN)
+ {
+ return 0;
+ }
+
+ if (size_1 < size_2)
+ {
+ return -1;
+ }
+ if (size_1 > size_2)
+ {
+ return +1;
+ }
+
+ return 0;
+}
+
+static int
+compare_by_size (NautilusFile *file_1,
+ NautilusFile *file_2)
+{
+ /* Sort order:
+ * Directories with n items
+ * Directories with 0 items
+ * Directories with "unknowable" # of items
+ * Directories with unknown # of items
+ * Files with large sizes.
+ * Files with smaller sizes.
+ * Files with "unknowable" size.
+ * Files with unknown size.
+ */
+
+ gboolean is_directory_1, is_directory_2;
+
+ is_directory_1 = nautilus_file_is_directory (file_1);
+ is_directory_2 = nautilus_file_is_directory (file_2);
+
+ if (is_directory_1 && !is_directory_2)
+ {
+ return -1;
+ }
+ if (is_directory_2 && !is_directory_1)
+ {
+ return +1;
+ }
+
+ if (is_directory_1)
+ {
+ return compare_directories_by_count (file_1, file_2);
+ }
+ else
+ {
+ return compare_files_by_size (file_1, file_2);
+ }
+}
+
+static int
+compare_by_display_name (NautilusFile *file_1,
+ NautilusFile *file_2)
+{
+ const char *name_1, *name_2;
+ const char *key_1, *key_2;
+ gboolean sort_last_1, sort_last_2;
+ int compare;
+
+ name_1 = nautilus_file_peek_display_name (file_1);
+ name_2 = nautilus_file_peek_display_name (file_2);
+
+ sort_last_1 = name_1[0] == SORT_LAST_CHAR1 || name_1[0] == SORT_LAST_CHAR2;
+ sort_last_2 = name_2[0] == SORT_LAST_CHAR1 || name_2[0] == SORT_LAST_CHAR2;
+
+ if (sort_last_1 && !sort_last_2)
+ {
+ compare = +1;
+ }
+ else if (!sort_last_1 && sort_last_2)
+ {
+ compare = -1;
+ }
+ else
+ {
+ key_1 = nautilus_file_peek_display_name_collation_key (file_1);
+ key_2 = nautilus_file_peek_display_name_collation_key (file_2);
+ compare = strcmp (key_1, key_2);
+ }
+
+ return compare;
+}
+
+static int
+compare_by_directory_name (NautilusFile *file_1,
+ NautilusFile *file_2)
+{
+ return strcmp (file_1->details->directory_name_collation_key,
+ file_2->details->directory_name_collation_key);
+}
+
+static GList *
+prepend_automatic_keywords (NautilusFile *file,
+ GList *names)
+{
+ /* Prepend in reverse order. */
+ NautilusFile *parent;
+
+ parent = nautilus_file_get_parent (file);
+
+ /* Trash files are assumed to be read-only,
+ * so we want to ignore them here. */
+ if (!nautilus_file_can_write (file) &&
+ !nautilus_file_is_in_trash (file) &&
+ (parent == NULL || nautilus_file_can_write (parent)))
+ {
+ names = g_list_prepend
+ (names, g_strdup (NAUTILUS_FILE_EMBLEM_NAME_CANT_WRITE));
+ }
+ if (!nautilus_file_can_read (file))
+ {
+ names = g_list_prepend
+ (names, g_strdup (NAUTILUS_FILE_EMBLEM_NAME_CANT_READ));
+ }
+ if (nautilus_file_is_symbolic_link (file))
+ {
+ names = g_list_prepend
+ (names, g_strdup (NAUTILUS_FILE_EMBLEM_NAME_SYMBOLIC_LINK));
+ }
+
+ if (parent)
+ {
+ nautilus_file_unref (parent);
+ }
+
+
+ return names;
+}
+
+static int
+compare_by_type (NautilusFile *file_1,
+ NautilusFile *file_2)
+{
+ gboolean is_directory_1;
+ gboolean is_directory_2;
+ char *type_string_1;
+ char *type_string_2;
+ int result;
+
+ /* Directories go first. Then, if mime types are identical,
+ * don't bother getting strings (for speed). This assumes
+ * that the string is dependent entirely on the mime type,
+ * which is true now but might not be later.
+ */
+ is_directory_1 = nautilus_file_is_directory (file_1);
+ is_directory_2 = nautilus_file_is_directory (file_2);
+
+ if (is_directory_1 && is_directory_2)
+ {
+ return 0;
+ }
+
+ if (is_directory_1)
+ {
+ return -1;
+ }
+
+ if (is_directory_2)
+ {
+ return +1;
+ }
+
+ if (file_1->details->mime_type != NULL &&
+ file_2->details->mime_type != NULL &&
+ strcmp (file_1->details->mime_type,
+ file_2->details->mime_type) == 0)
+ {
+ return 0;
+ }
+
+ type_string_1 = nautilus_file_get_type_as_string_no_extra_text (file_1);
+ type_string_2 = nautilus_file_get_type_as_string_no_extra_text (file_2);
+
+ if (type_string_1 == NULL || type_string_2 == NULL)
+ {
+ if (type_string_1 != NULL)
+ {
+ return -1;
+ }
+
+ if (type_string_2 != NULL)
+ {
+ return 1;
+ }
+
+ return 0;
+ }
+
+ result = g_utf8_collate (type_string_1, type_string_2);
+ if (result == 0)
+ {
+ /* Among files of the same (generic) type, sort them by mime type. */
+ result = g_utf8_collate (file_1->details->mime_type, file_2->details->mime_type);
+ }
+
+ g_free (type_string_1);
+ g_free (type_string_2);
+
+ return result;
+}
+
+static int
+compare_by_starred (NautilusFile *file_1,
+ NautilusFile *file_2)
+{
+ NautilusTagManager *tag_manager = nautilus_tag_manager_get ();
+ g_autofree gchar *uri_1 = NULL;
+ g_autofree gchar *uri_2 = NULL;
+ gboolean file_1_is_starred;
+ gboolean file_2_is_starred;
+
+ uri_1 = nautilus_file_get_uri (file_1);
+ uri_2 = nautilus_file_get_uri (file_2);
+
+ file_1_is_starred = nautilus_tag_manager_file_is_starred (tag_manager,
+ uri_1);
+ file_2_is_starred = nautilus_tag_manager_file_is_starred (tag_manager,
+ uri_2);
+ if (!!file_1_is_starred == !!file_2_is_starred)
+ {
+ return 0;
+ }
+ else if (file_1_is_starred && !file_2_is_starred)
+ {
+ return -1;
+ }
+ else
+ {
+ return 1;
+ }
+}
+
+static Knowledge
+get_search_relevance (NautilusFile *file,
+ gdouble *relevance_out)
+{
+ /* we're only called in search directories, and in that
+ * case, the relevance is always known (or zero).
+ */
+ *relevance_out = file->details->search_relevance;
+ return KNOWN;
+}
+
+static int
+compare_by_search_relevance (NautilusFile *file_1,
+ NautilusFile *file_2)
+{
+ gdouble r_1, r_2;
+
+ get_search_relevance (file_1, &r_1);
+ get_search_relevance (file_2, &r_2);
+
+ if (r_1 < r_2)
+ {
+ return -1;
+ }
+ if (r_1 > r_2)
+ {
+ return +1;
+ }
+
+ return 0;
+}
+
+static int
+compare_by_time (NautilusFile *file_1,
+ NautilusFile *file_2,
+ NautilusDateType type)
+{
+ /* Sort order:
+ * Files with unknown times.
+ * Files with "unknowable" times.
+ * Files with older times.
+ * Files with newer times.
+ */
+
+ Knowledge time_known_1, time_known_2;
+ time_t time_1, time_2;
+
+ time_1 = 0;
+ time_2 = 0;
+
+ time_known_1 = get_time (file_1, &time_1, type);
+ time_known_2 = get_time (file_2, &time_2, type);
+
+ if (time_known_1 > time_known_2)
+ {
+ return -1;
+ }
+ if (time_known_1 < time_known_2)
+ {
+ return +1;
+ }
+
+ /* Now time_known_1 is equal to time_known_2. Check whether
+ * we failed to get modification times for files
+ */
+ if (time_known_1 == UNKNOWABLE || time_known_1 == UNKNOWN)
+ {
+ return 0;
+ }
+
+ if (time_1 < time_2)
+ {
+ return -1;
+ }
+ if (time_1 > time_2)
+ {
+ return +1;
+ }
+
+ return 0;
+}
+
+static int
+compare_by_full_path (NautilusFile *file_1,
+ NautilusFile *file_2)
+{
+ int compare;
+
+ compare = compare_by_directory_name (file_1, file_2);
+ if (compare != 0)
+ {
+ return compare;
+ }
+ return compare_by_display_name (file_1, file_2);
+}
+
+static int
+nautilus_file_compare_for_sort_internal (NautilusFile *file_1,
+ NautilusFile *file_2,
+ gboolean directories_first,
+ gboolean reversed)
+{
+ gboolean is_directory_1, is_directory_2;
+
+ if (directories_first)
+ {
+ is_directory_1 = nautilus_file_is_directory (file_1);
+ is_directory_2 = nautilus_file_is_directory (file_2);
+
+ if (is_directory_1 && !is_directory_2)
+ {
+ return -1;
+ }
+
+ if (is_directory_2 && !is_directory_1)
+ {
+ return +1;
+ }
+ }
+
+ if (file_1->details->sort_order < file_2->details->sort_order)
+ {
+ return reversed ? 1 : -1;
+ }
+ else if (file_1->details->sort_order > file_2->details->sort_order)
+ {
+ return reversed ? -1 : 1;
+ }
+
+ return 0;
+}
+
+/**
+ * nautilus_file_compare_for_sort:
+ * @file_1: A file object
+ * @file_2: Another file object
+ * @sort_type: Sort criterion
+ * @directories_first: Put all directories before any non-directories
+ * @reversed: Reverse the order of the items, except that
+ * the directories_first flag is still respected.
+ *
+ * Return value: int < 0 if @file_1 should come before file_2 in a
+ * sorted list; int > 0 if @file_2 should come before file_1 in a
+ * sorted list; 0 if @file_1 and @file_2 are equal for this sort criterion. Note
+ * that each named sort type may actually break ties several ways, with the name
+ * of the sort criterion being the primary but not only differentiator.
+ **/
+int
+nautilus_file_compare_for_sort (NautilusFile *file_1,
+ NautilusFile *file_2,
+ NautilusFileSortType sort_type,
+ gboolean directories_first,
+ gboolean reversed)
+{
+ int result;
+
+ if (file_1 == file_2)
+ {
+ return 0;
+ }
+
+ result = nautilus_file_compare_for_sort_internal (file_1, file_2, directories_first, reversed);
+
+ if (result == 0)
+ {
+ switch (sort_type)
+ {
+ case NAUTILUS_FILE_SORT_BY_DISPLAY_NAME:
+ {
+ result = compare_by_display_name (file_1, file_2);
+ if (result == 0)
+ {
+ result = compare_by_directory_name (file_1, file_2);
+ }
+ }
+ break;
+
+ case NAUTILUS_FILE_SORT_BY_SIZE:
+ {
+ /* Compare directory sizes ourselves, then if necessary
+ * use GnomeVFS to compare file sizes.
+ */
+ result = compare_by_size (file_1, file_2);
+ if (result == 0)
+ {
+ result = compare_by_full_path (file_1, file_2);
+ }
+ }
+ break;
+
+ case NAUTILUS_FILE_SORT_BY_TYPE:
+ {
+ /* GnomeVFS doesn't know about our special text for certain
+ * mime types, so we handle the mime-type sorting ourselves.
+ */
+ result = compare_by_type (file_1, file_2);
+ if (result == 0)
+ {
+ result = compare_by_full_path (file_1, file_2);
+ }
+ }
+ break;
+
+ case NAUTILUS_FILE_SORT_BY_STARRED:
+ {
+ result = compare_by_starred (file_1, file_2);
+ if (result == 0)
+ {
+ result = compare_by_full_path (file_1, file_2);
+ }
+ }
+ break;
+
+ case NAUTILUS_FILE_SORT_BY_MTIME:
+ {
+ result = compare_by_time (file_1, file_2, NAUTILUS_DATE_TYPE_MODIFIED);
+ if (result == 0)
+ {
+ result = compare_by_full_path (file_1, file_2);
+ }
+ }
+ break;
+
+ case NAUTILUS_FILE_SORT_BY_ATIME:
+ {
+ result = compare_by_time (file_1, file_2, NAUTILUS_DATE_TYPE_ACCESSED);
+ if (result == 0)
+ {
+ result = compare_by_full_path (file_1, file_2);
+ }
+ }
+ break;
+
+ case NAUTILUS_FILE_SORT_BY_BTIME:
+ {
+ result = compare_by_time (file_1, file_2, NAUTILUS_DATE_TYPE_CREATED);
+ if (result == 0)
+ {
+ result = compare_by_full_path (file_1, file_2);
+ }
+ }
+ break;
+
+ case NAUTILUS_FILE_SORT_BY_TRASHED_TIME:
+ {
+ result = compare_by_time (file_1, file_2, NAUTILUS_DATE_TYPE_TRASHED);
+ if (result == 0)
+ {
+ result = compare_by_full_path (file_1, file_2);
+ }
+ }
+ break;
+
+ case NAUTILUS_FILE_SORT_BY_SEARCH_RELEVANCE:
+ {
+ result = compare_by_search_relevance (file_1, file_2);
+ if (result == 0)
+ {
+ result = compare_by_full_path (file_1, file_2);
+
+ /* ensure alphabetical order for files of the same relevance */
+ reversed = FALSE;
+ }
+ }
+ break;
+
+ case NAUTILUS_FILE_SORT_BY_RECENCY:
+ {
+ result = compare_by_time (file_1, file_2, NAUTILUS_DATE_TYPE_RECENCY);
+ if (result == 0)
+ {
+ result = compare_by_full_path (file_1, file_2);
+ }
+ }
+ break;
+
+ default:
+ {
+ g_return_val_if_reached (0);
+ }
+ }
+
+ if (reversed)
+ {
+ result = -result;
+ }
+ }
+
+ return result;
+}
+
+int
+nautilus_file_compare_for_sort_by_attribute_q (NautilusFile *file_1,
+ NautilusFile *file_2,
+ GQuark attribute,
+ gboolean directories_first,
+ gboolean reversed)
+{
+ int result;
+
+ if (file_1 == file_2)
+ {
+ return 0;
+ }
+
+ /* Convert certain attributes into NautilusFileSortTypes and use
+ * nautilus_file_compare_for_sort()
+ */
+ if (attribute == 0 || attribute == attribute_name_q)
+ {
+ return nautilus_file_compare_for_sort (file_1, file_2,
+ NAUTILUS_FILE_SORT_BY_DISPLAY_NAME,
+ directories_first,
+ reversed);
+ }
+ else if (attribute == attribute_size_q)
+ {
+ return nautilus_file_compare_for_sort (file_1, file_2,
+ NAUTILUS_FILE_SORT_BY_SIZE,
+ directories_first,
+ reversed);
+ }
+ else if (attribute == attribute_type_q)
+ {
+ return nautilus_file_compare_for_sort (file_1, file_2,
+ NAUTILUS_FILE_SORT_BY_TYPE,
+ directories_first,
+ reversed);
+ }
+ else if (attribute == attribute_starred_q)
+ {
+ return nautilus_file_compare_for_sort (file_1, file_2,
+ NAUTILUS_FILE_SORT_BY_STARRED,
+ directories_first,
+ reversed);
+ }
+ else if (attribute == attribute_modification_date_q || attribute == attribute_date_modified_q || attribute == attribute_date_modified_with_time_q || attribute == attribute_date_modified_full_q)
+ {
+ return nautilus_file_compare_for_sort (file_1, file_2,
+ NAUTILUS_FILE_SORT_BY_MTIME,
+ directories_first,
+ reversed);
+ }
+ else if (attribute == attribute_accessed_date_q || attribute == attribute_date_accessed_q || attribute == attribute_date_accessed_full_q)
+ {
+ return nautilus_file_compare_for_sort (file_1, file_2,
+ NAUTILUS_FILE_SORT_BY_ATIME,
+ directories_first,
+ reversed);
+ }
+ else if (attribute == attribute_date_created_q || attribute == attribute_date_created_full_q)
+ {
+ return nautilus_file_compare_for_sort (file_1, file_2,
+ NAUTILUS_FILE_SORT_BY_BTIME,
+ directories_first,
+ reversed);
+ }
+ else if (attribute == attribute_trashed_on_q || attribute == attribute_trashed_on_full_q)
+ {
+ return nautilus_file_compare_for_sort (file_1, file_2,
+ NAUTILUS_FILE_SORT_BY_TRASHED_TIME,
+ directories_first,
+ reversed);
+ }
+ else if (attribute == attribute_search_relevance_q)
+ {
+ return nautilus_file_compare_for_sort (file_1, file_2,
+ NAUTILUS_FILE_SORT_BY_SEARCH_RELEVANCE,
+ directories_first,
+ reversed);
+ }
+ else if (attribute == attribute_recency_q)
+ {
+ return nautilus_file_compare_for_sort (file_1, file_2,
+ NAUTILUS_FILE_SORT_BY_RECENCY,
+ directories_first,
+ reversed);
+ }
+
+ /* it is a normal attribute, compare by strings */
+
+ result = nautilus_file_compare_for_sort_internal (file_1, file_2, directories_first, reversed);
+
+ if (result == 0)
+ {
+ char *value_1;
+ char *value_2;
+
+ value_1 = nautilus_file_get_string_attribute_q (file_1,
+ attribute);
+ value_2 = nautilus_file_get_string_attribute_q (file_2,
+ attribute);
+
+ if (value_1 != NULL && value_2 != NULL)
+ {
+ result = strcmp (value_1, value_2);
+ }
+
+ g_free (value_1);
+ g_free (value_2);
+
+ if (reversed)
+ {
+ result = -result;
+ }
+ }
+
+ return result;
+}
+
+int
+nautilus_file_compare_for_sort_by_attribute (NautilusFile *file_1,
+ NautilusFile *file_2,
+ const char *attribute,
+ gboolean directories_first,
+ gboolean reversed)
+{
+ return nautilus_file_compare_for_sort_by_attribute_q (file_1, file_2,
+ g_quark_from_string (attribute),
+ directories_first,
+ reversed);
+}
+
+
+/**
+ * nautilus_file_compare_name:
+ * @file: A file object
+ * @string: A string we are comparing it with
+ *
+ * Return value: result of a comparison of the file name and the given string.
+ **/
+int
+nautilus_file_compare_display_name (NautilusFile *file,
+ const char *string)
+{
+ const char *name;
+ int result;
+
+ g_return_val_if_fail (string != NULL, -1);
+
+ name = nautilus_file_peek_display_name (file);
+ result = g_strcmp0 (name, string);
+ return result;
+}
+
+
+gboolean
+nautilus_file_is_hidden_file (NautilusFile *file)
+{
+ return file->details->is_hidden;
+}
+
+/**
+ * nautilus_file_should_show:
+ * @file: the file to check
+ * @show_hidden: whether we want to show hidden files or not
+ *
+ * Determines if a #NautilusFile should be shown. Note that when browsing
+ * a trash directory, this function will always return %TRUE.
+ *
+ * Returns: %TRUE if the file should be shown, %FALSE if it shouldn't.
+ */
+gboolean
+nautilus_file_should_show (NautilusFile *file,
+ gboolean show_hidden)
+{
+ /* Never hide any files in trash. */
+ if (nautilus_file_is_in_trash (file))
+ {
+ return TRUE;
+ }
+
+ if (!show_hidden && nautilus_file_is_hidden_file (file))
+ {
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+gboolean
+nautilus_file_is_home (NautilusFile *file)
+{
+ g_autoptr (GFile) location = NULL;
+
+ g_return_val_if_fail (NAUTILUS_IS_FILE (file), FALSE);
+
+ location = nautilus_directory_get_location (file->details->directory);
+ if (location == NULL)
+ {
+ return FALSE;
+ }
+
+ return nautilus_is_home_directory_file (location, file->details->name);
+}
+
+gboolean
+nautilus_file_is_in_search (NautilusFile *file)
+{
+ char *uri;
+ gboolean ret;
+
+ uri = nautilus_file_get_uri (file);
+ ret = eel_uri_is_search (uri);
+ g_free (uri);
+
+ return ret;
+}
+
+static gboolean
+filter_hidden_partition_callback (NautilusFile *file,
+ gpointer callback_data)
+{
+ FilterOptions options;
+
+ options = GPOINTER_TO_INT (callback_data);
+
+ return nautilus_file_should_show (file,
+ options & SHOW_HIDDEN);
+}
+
+GList *
+nautilus_file_list_filter_hidden (GList *files,
+ gboolean show_hidden)
+{
+ GList *filtered_files;
+ GList *removed_files;
+
+ /* FIXME bugzilla.gnome.org 40653:
+ * Eventually this should become a generic filtering thingy.
+ */
+
+ filtered_files = nautilus_file_list_filter (files,
+ &removed_files,
+ filter_hidden_partition_callback,
+ GINT_TO_POINTER ((show_hidden ? SHOW_HIDDEN : 0)));
+ nautilus_file_list_free (removed_files);
+
+ return filtered_files;
+}
+
+/* This functions filters a file list when its items match a certain condition
+ * in the filter function. This function preserves the ordering.
+ */
+GList *
+nautilus_file_list_filter (GList *files,
+ GList **failed,
+ NautilusFileFilterFunc filter_function,
+ gpointer user_data)
+{
+ GList *filtered = NULL;
+ GList *l;
+ GList *reversed;
+
+ *failed = NULL;
+ /* Avoid using g_list_append since it's O(n) */
+ reversed = g_list_copy (files);
+ reversed = g_list_reverse (reversed);
+ for (l = reversed; l != NULL; l = l->next)
+ {
+ if (filter_function (l->data, user_data))
+ {
+ filtered = g_list_prepend (filtered, nautilus_file_ref (l->data));
+ }
+ else
+ {
+ *failed = g_list_prepend (*failed, nautilus_file_ref (l->data));
+ }
+ }
+
+ g_list_free (reversed);
+
+ return filtered;
+}
+
+gboolean
+nautilus_file_list_are_all_folders (const GList *files)
+{
+ const GList *l;
+
+ for (l = files; l != NULL; l = l->next)
+ {
+ if (!nautilus_file_is_directory (NAUTILUS_FILE (l->data)))
+ {
+ return FALSE;
+ }
+ }
+
+ return TRUE;
+}
+
+char *
+nautilus_file_get_metadata (NautilusFile *file,
+ const char *key,
+ const char *default_metadata)
+{
+ guint id;
+ char *value;
+
+ g_return_val_if_fail (key != NULL, g_strdup (default_metadata));
+ g_return_val_if_fail (key[0] != '\0', g_strdup (default_metadata));
+
+ if (file == NULL ||
+ file->details->metadata == NULL)
+ {
+ return g_strdup (default_metadata);
+ }
+
+ g_return_val_if_fail (NAUTILUS_IS_FILE (file), g_strdup (default_metadata));
+
+ id = nautilus_metadata_get_id (key);
+ value = g_hash_table_lookup (file->details->metadata, GUINT_TO_POINTER (id));
+
+ if (value)
+ {
+ return g_strdup (value);
+ }
+ return g_strdup (default_metadata);
+}
+
+/**
+ * nautilus_file_get_metadata_list:
+ * @file: A #NautilusFile to get metadata from.
+ * @key: A string representation of the metadata key (use macros when possible).
+ *
+ * Get the value of a metadata attribute which holds a list of strings.
+ *
+ * Returns: (transfer full): A zero-terminated array of newly allocated strings.
+ */
+gchar **
+nautilus_file_get_metadata_list (NautilusFile *file,
+ const char *key)
+{
+ guint id;
+ char **value;
+
+ g_return_val_if_fail (key != NULL, NULL);
+ g_return_val_if_fail (key[0] != '\0', NULL);
+
+ if (file == NULL ||
+ file->details->metadata == NULL)
+ {
+ return NULL;
+ }
+
+ g_return_val_if_fail (NAUTILUS_IS_FILE (file), NULL);
+
+ id = nautilus_metadata_get_id (key);
+ id |= METADATA_ID_IS_LIST_MASK;
+
+ value = g_hash_table_lookup (file->details->metadata, GUINT_TO_POINTER (id));
+
+ return g_strdupv (value);
+}
+
+void
+nautilus_file_set_metadata (NautilusFile *file,
+ const char *key,
+ const char *default_metadata,
+ const char *metadata)
+{
+ const char *val;
+
+ g_return_if_fail (NAUTILUS_IS_FILE (file));
+ g_return_if_fail (key != NULL);
+ g_return_if_fail (key[0] != '\0');
+
+ val = metadata;
+ if (val == NULL)
+ {
+ val = default_metadata;
+ }
+
+ NAUTILUS_FILE_CLASS (G_OBJECT_GET_CLASS (file))->set_metadata (file, key, val);
+}
+
+/**
+ * nautilus_file_set_metadata_list:
+ * @file: A #NautilusFile to set metadata into.
+ * @key: A string representation of the metadata key (use macros when possible).
+ * @list: (transfer none): A zero-terminated array of newly allocated strings.
+ *
+ * Set the value of a metadata attribute which takes a list of strings.
+ */
+void
+nautilus_file_set_metadata_list (NautilusFile *file,
+ const char *key,
+ gchar **list)
+{
+ g_return_if_fail (NAUTILUS_IS_FILE (file));
+ g_return_if_fail (key != NULL);
+ g_return_if_fail (key[0] != '\0');
+
+ NAUTILUS_FILE_CLASS (G_OBJECT_GET_CLASS (file))->set_metadata_as_list (file, key, list);
+}
+
+gboolean
+nautilus_file_get_boolean_metadata (NautilusFile *file,
+ const char *key,
+ gboolean default_metadata)
+{
+ char *result_as_string;
+ gboolean result;
+
+ g_return_val_if_fail (key != NULL, default_metadata);
+ g_return_val_if_fail (key[0] != '\0', default_metadata);
+
+ if (file == NULL)
+ {
+ return default_metadata;
+ }
+
+ g_return_val_if_fail (NAUTILUS_IS_FILE (file), default_metadata);
+
+ result_as_string = nautilus_file_get_metadata
+ (file, key, default_metadata ? "true" : "false");
+ g_assert (result_as_string != NULL);
+
+ if (g_ascii_strcasecmp (result_as_string, "true") == 0)
+ {
+ result = TRUE;
+ }
+ else if (g_ascii_strcasecmp (result_as_string, "false") == 0)
+ {
+ result = FALSE;
+ }
+ else
+ {
+ g_error ("boolean metadata with value other than true or false");
+ result = default_metadata;
+ }
+
+ g_free (result_as_string);
+ return result;
+}
+
+int
+nautilus_file_get_integer_metadata (NautilusFile *file,
+ const char *key,
+ int default_metadata)
+{
+ char *result_as_string;
+ char default_as_string[32];
+ int result;
+ char c;
+
+ g_return_val_if_fail (key != NULL, default_metadata);
+ g_return_val_if_fail (key[0] != '\0', default_metadata);
+
+ if (file == NULL)
+ {
+ return default_metadata;
+ }
+ g_return_val_if_fail (NAUTILUS_IS_FILE (file), default_metadata);
+
+ g_snprintf (default_as_string, sizeof (default_as_string), "%d", default_metadata);
+ result_as_string = nautilus_file_get_metadata
+ (file, key, default_as_string);
+
+ /* Normally we can't get a a NULL, but we check for it here to
+ * handle the oddball case of a non-existent directory.
+ */
+ if (result_as_string == NULL)
+ {
+ result = default_metadata;
+ }
+ else
+ {
+ if (sscanf (result_as_string, " %d %c", &result, &c) != 1)
+ {
+ result = default_metadata;
+ }
+ g_free (result_as_string);
+ }
+
+ return result;
+}
+
+static gboolean
+get_time_from_time_string (const char *time_string,
+ time_t *time)
+{
+ long scanned_time;
+ char c;
+
+ g_assert (time != NULL);
+
+ /* Only accept string if it has one integer with nothing
+ * afterwards.
+ */
+ if (time_string == NULL ||
+ sscanf (time_string, "%ld%c", &scanned_time, &c) != 1)
+ {
+ return FALSE;
+ }
+ *time = (time_t) scanned_time;
+ return TRUE;
+}
+
+time_t
+nautilus_file_get_time_metadata (NautilusFile *file,
+ const char *key)
+{
+ time_t time;
+ char *time_string;
+
+ time_string = nautilus_file_get_metadata (file, key, NULL);
+ if (!get_time_from_time_string (time_string, &time))
+ {
+ time = UNDEFINED_TIME;
+ }
+ g_free (time_string);
+
+ return time;
+}
+
+void
+nautilus_file_set_time_metadata (NautilusFile *file,
+ const char *key,
+ time_t time)
+{
+ char time_str[21];
+ char *metadata;
+
+ if (time != UNDEFINED_TIME)
+ {
+ /* 2^64 turns out to be 20 characters */
+ g_snprintf (time_str, 20, "%ld", (long int) time);
+ time_str[20] = '\0';
+ metadata = time_str;
+ }
+ else
+ {
+ metadata = NULL;
+ }
+
+ nautilus_file_set_metadata (file, key, NULL, metadata);
+}
+
+
+void
+nautilus_file_set_boolean_metadata (NautilusFile *file,
+ const char *key,
+ gboolean default_metadata,
+ gboolean metadata)
+{
+ g_return_if_fail (NAUTILUS_IS_FILE (file));
+ g_return_if_fail (key != NULL);
+ g_return_if_fail (key[0] != '\0');
+
+ nautilus_file_set_metadata (file, key,
+ default_metadata ? "true" : "false",
+ metadata ? "true" : "false");
+}
+
+void
+nautilus_file_set_integer_metadata (NautilusFile *file,
+ const char *key,
+ int default_metadata,
+ int metadata)
+{
+ char value_as_string[32];
+ char default_as_string[32];
+
+ g_return_if_fail (NAUTILUS_IS_FILE (file));
+ g_return_if_fail (key != NULL);
+ g_return_if_fail (key[0] != '\0');
+
+ g_snprintf (value_as_string, sizeof (value_as_string), "%d", metadata);
+ g_snprintf (default_as_string, sizeof (default_as_string), "%d", default_metadata);
+
+ nautilus_file_set_metadata (file, key,
+ default_as_string, value_as_string);
+}
+
+static const char *
+nautilus_file_peek_display_name_collation_key (NautilusFile *file)
+{
+ const char *res;
+
+ res = file->details->display_name_collation_key;
+ if (res == NULL)
+ {
+ res = "";
+ }
+
+ return res;
+}
+
+static const char *
+nautilus_file_peek_display_name (NautilusFile *file)
+{
+ const char *name;
+ char *escaped_name;
+
+ /* FIXME: for some reason we can get a NautilusFile instance which is
+ * no longer valid or could be freed somewhere else in the same time.
+ * There's race condition somewhere. See bug 602500.
+ */
+ if (file == NULL || nautilus_file_is_gone (file))
+ {
+ return "";
+ }
+
+ /* Default to display name based on filename if its not set yet */
+
+ if (file->details->display_name == NULL)
+ {
+ name = file->details->name;
+ if (g_utf8_validate (name, -1, NULL))
+ {
+ nautilus_file_set_display_name (file,
+ name,
+ NULL,
+ FALSE);
+ }
+ else
+ {
+ escaped_name = g_uri_escape_string (name, G_URI_RESERVED_CHARS_ALLOWED_IN_PATH, TRUE);
+ nautilus_file_set_display_name (file,
+ escaped_name,
+ NULL,
+ FALSE);
+ g_free (escaped_name);
+ }
+ }
+
+ return file->details->display_name ?
+ file->details->display_name : "";
+}
+
+char *
+nautilus_file_get_display_name (NautilusFile *file)
+{
+ if (nautilus_file_is_other_locations (file))
+ {
+ return g_strdup (_("Other Locations"));
+ }
+ if (nautilus_file_is_starred_location (file))
+ {
+ return g_strdup (_("Starred"));
+ }
+
+ return g_strdup (nautilus_file_peek_display_name (file));
+}
+
+char *
+nautilus_file_get_edit_name (NautilusFile *file)
+{
+ const char *res;
+
+ res = file->details->edit_name;
+ if (res == NULL)
+ {
+ res = "";
+ }
+
+ return g_strdup (res);
+}
+
+char *
+nautilus_file_get_name (NautilusFile *file)
+{
+ g_return_val_if_fail (NAUTILUS_IS_FILE (file), NULL);
+
+ return nautilus_file_info_get_name (NAUTILUS_FILE_INFO (file));
+}
+
+/**
+ * nautilus_file_get_description:
+ * @file: a #NautilusFile.
+ *
+ * Gets the standard::description key from @file, if
+ * it has been cached.
+ *
+ * Returns: a string containing the value of the standard::description
+ * key, or %NULL.
+ */
+char *
+nautilus_file_get_description (NautilusFile *file)
+{
+ return g_strdup (file->details->description);
+}
+
+void
+nautilus_file_monitor_add (NautilusFile *file,
+ gconstpointer client,
+ NautilusFileAttributes attributes)
+{
+ g_return_if_fail (NAUTILUS_IS_FILE (file));
+ g_return_if_fail (client != NULL);
+
+ NAUTILUS_FILE_CLASS (G_OBJECT_GET_CLASS (file))->monitor_add (file, client, attributes);
+}
+
+void
+nautilus_file_monitor_remove (NautilusFile *file,
+ gconstpointer client)
+{
+ g_return_if_fail (NAUTILUS_IS_FILE (file));
+ g_return_if_fail (client != NULL);
+
+ NAUTILUS_FILE_CLASS (G_OBJECT_GET_CLASS (file))->monitor_remove (file, client);
+}
+
+gboolean
+nautilus_file_has_activation_uri (NautilusFile *file)
+{
+ return file->details->activation_uri != NULL;
+}
+
+
+/* Return the uri associated with the passed-in file, which may not be
+ * the actual uri if the file is an desktop file or a nautilus
+ * xml link file.
+ */
+char *
+nautilus_file_get_activation_uri (NautilusFile *file)
+{
+ g_return_val_if_fail (NAUTILUS_IS_FILE (file), NULL);
+
+ return nautilus_file_info_get_activation_uri (NAUTILUS_FILE_INFO (file));
+}
+
+GFile *
+nautilus_file_get_activation_location (NautilusFile *file)
+{
+ g_return_val_if_fail (NAUTILUS_IS_FILE (file), NULL);
+
+ if (file->details->activation_uri != NULL)
+ {
+ return g_file_new_for_uri (file->details->activation_uri);
+ }
+
+ return nautilus_file_get_location (file);
+}
+
+static gboolean
+is_uri_relative (const char *uri)
+{
+ char *scheme;
+ gboolean ret;
+
+ scheme = g_uri_parse_scheme (uri);
+ ret = (scheme == NULL);
+ g_free (scheme);
+ return ret;
+}
+
+static char *
+get_custom_icon_metadata_uri (NautilusFile *file)
+{
+ char *custom_icon_uri;
+ char *uri;
+ char *dir_uri;
+
+ uri = nautilus_file_get_metadata (file, NAUTILUS_METADATA_KEY_CUSTOM_ICON, NULL);
+ if (uri != NULL &&
+ nautilus_file_is_directory (file) &&
+ is_uri_relative (uri))
+ {
+ dir_uri = nautilus_file_get_uri (file);
+ custom_icon_uri = g_build_filename (dir_uri, uri, NULL);
+ g_free (dir_uri);
+ g_free (uri);
+ }
+ else
+ {
+ custom_icon_uri = uri;
+ }
+ return custom_icon_uri;
+}
+
+static char *
+get_custom_icon_metadata_name (NautilusFile *file)
+{
+ char *icon_name;
+
+ icon_name = nautilus_file_get_metadata (file,
+ NAUTILUS_METADATA_KEY_CUSTOM_ICON_NAME, NULL);
+
+ return icon_name;
+}
+
+static GIcon *
+get_mount_icon (NautilusFile *file)
+{
+ GMount *mount;
+ GIcon *mount_icon;
+
+ mount = nautilus_file_get_mount (file);
+ mount_icon = NULL;
+
+ if (mount != NULL)
+ {
+ mount_icon = g_mount_get_icon (mount);
+ g_object_unref (mount);
+ }
+ else
+ {
+ g_autoptr (GFile) location = nautilus_file_get_location (file);
+
+ /* Root directory doesn't have a GMount, but for UI purposes we want
+ * it to be treated the same way. */
+ if (nautilus_is_root_directory (location))
+ {
+ mount_icon = g_themed_icon_new_with_default_fallbacks ("drive-harddisk");
+ }
+ }
+
+ return mount_icon;
+}
+
+static GIcon *
+get_custom_icon (NautilusFile *file)
+{
+ char *custom_icon_uri, *custom_icon_name;
+ GFile *icon_file;
+ GIcon *icon;
+
+ if (file == NULL)
+ {
+ return NULL;
+ }
+
+ icon = NULL;
+
+ /* Metadata takes precedence; first we look at the custom
+ * icon URI, then at the custom icon name.
+ */
+ custom_icon_uri = get_custom_icon_metadata_uri (file);
+
+ if (custom_icon_uri)
+ {
+ icon_file = g_file_new_for_uri (custom_icon_uri);
+ icon = g_file_icon_new (icon_file);
+ g_object_unref (icon_file);
+ g_free (custom_icon_uri);
+ }
+
+ if (icon == NULL)
+ {
+ custom_icon_name = get_custom_icon_metadata_name (file);
+
+ if (custom_icon_name != NULL)
+ {
+ icon = g_themed_icon_new_with_default_fallbacks (custom_icon_name);
+ g_free (custom_icon_name);
+ }
+ }
+
+ return icon;
+}
+
+static GIcon *
+get_default_file_icon (void)
+{
+ static GIcon *fallback_icon = NULL;
+ if (fallback_icon == NULL)
+ {
+ fallback_icon = g_themed_icon_new ("text-x-generic");
+ }
+
+ return fallback_icon;
+}
+
+GFilesystemPreviewType
+nautilus_file_get_filesystem_use_preview (NautilusFile *file)
+{
+ GFilesystemPreviewType use_preview;
+ NautilusFile *parent;
+
+ parent = nautilus_file_get_parent (file);
+ if (parent != NULL)
+ {
+ use_preview = parent->details->filesystem_use_preview;
+ g_object_unref (parent);
+ }
+ else
+ {
+ use_preview = 0;
+ }
+
+ return use_preview;
+}
+
+char *
+nautilus_file_get_filesystem_type (NautilusFile *file)
+{
+ char *filesystem_type = NULL;
+
+ g_assert (NAUTILUS_IS_FILE (file));
+
+ if (nautilus_file_is_directory (file))
+ {
+ filesystem_type = g_strdup (file->details->filesystem_type);
+ }
+ else
+ {
+ g_autoptr (NautilusFile) parent = NULL;
+
+ parent = nautilus_file_get_parent (file);
+ if (parent != NULL)
+ {
+ filesystem_type = g_strdup (parent->details->filesystem_type);
+ }
+ }
+
+ return filesystem_type;
+}
+
+gboolean
+nautilus_file_get_filesystem_remote (NautilusFile *file)
+{
+ g_assert (NAUTILUS_IS_FILE (file));
+
+ if (nautilus_file_is_directory (file) && file->details->filesystem_info_is_up_to_date)
+ {
+ return file->details->filesystem_remote;
+ }
+ else
+ {
+ g_autoptr (NautilusFile) parent = NULL;
+
+ parent = nautilus_file_get_parent (file);
+ if (parent != NULL)
+ {
+ return parent->details->filesystem_remote;
+ }
+ }
+
+ return FALSE;
+}
+
+static gboolean
+get_speed_tradeoff_preference_for_file (NautilusFile *file,
+ NautilusSpeedTradeoffValue value)
+{
+ GFilesystemPreviewType use_preview;
+
+ g_return_val_if_fail (NAUTILUS_IS_FILE (file), FALSE);
+
+ use_preview = nautilus_file_get_filesystem_use_preview (file);
+
+ if (value == NAUTILUS_SPEED_TRADEOFF_ALWAYS)
+ {
+ if (use_preview == G_FILESYSTEM_PREVIEW_TYPE_NEVER)
+ {
+ return FALSE;
+ }
+ else
+ {
+ return TRUE;
+ }
+ }
+ else if (value == NAUTILUS_SPEED_TRADEOFF_NEVER)
+ {
+ return FALSE;
+ }
+ else if (value == NAUTILUS_SPEED_TRADEOFF_LOCAL_ONLY)
+ {
+ if (use_preview == G_FILESYSTEM_PREVIEW_TYPE_NEVER)
+ {
+ /* file system says to never preview anything */
+ return FALSE;
+ }
+ else if (use_preview == G_FILESYSTEM_PREVIEW_TYPE_IF_LOCAL)
+ {
+ /* file system says we should treat file as if it's local */
+ return TRUE;
+ }
+ else
+ {
+ /* only local files */
+ return !nautilus_file_is_remote (file);
+ }
+ }
+
+ return FALSE;
+}
+
+gboolean
+nautilus_file_should_show_thumbnail (NautilusFile *file)
+{
+ const char *mime_type;
+
+ mime_type = file->details->mime_type;
+ if (mime_type == NULL)
+ {
+ mime_type = "application/octet-stream";
+ }
+
+ /* If the thumbnail has already been created, don't care about the size
+ * of the original file.
+ */
+ if (nautilus_thumbnail_is_mimetype_limited_by_size (mime_type) &&
+ file->details->thumbnail_path == NULL &&
+ nautilus_file_get_size (file) > cached_thumbnail_limit)
+ {
+ return FALSE;
+ }
+
+ return get_speed_tradeoff_preference_for_file (file, show_file_thumbs);
+}
+
+static gboolean
+nautilus_is_video_file (NautilusFile *file)
+{
+ const char *mime_type;
+ guint i;
+
+ mime_type = file->details->mime_type;
+ if (mime_type == NULL)
+ {
+ return FALSE;
+ }
+
+ for (i = 0; video_mime_types[i] != NULL; i++)
+ {
+ if (g_content_type_equals (video_mime_types[i], mime_type))
+ {
+ return TRUE;
+ }
+ }
+
+ return FALSE;
+}
+
+static GList *
+sort_keyword_list_and_remove_duplicates (GList *keywords)
+{
+ GList *p;
+ GList *duplicate_link;
+
+ if (keywords != NULL)
+ {
+ keywords = g_list_sort (keywords, (GCompareFunc) g_utf8_collate);
+
+ p = keywords;
+ while (p->next != NULL)
+ {
+ if (strcmp ((const char *) p->data, (const char *) p->next->data) == 0)
+ {
+ duplicate_link = p->next;
+ keywords = g_list_remove_link (keywords, duplicate_link);
+ g_list_free_full (duplicate_link, g_free);
+ }
+ else
+ {
+ p = p->next;
+ }
+ }
+ }
+
+ return keywords;
+}
+
+static void
+clean_up_metadata_keywords (NautilusFile *file,
+ GList **metadata_keywords)
+{
+ NautilusFile *parent_file;
+ GList *l, *res = NULL;
+ char *exclude[4];
+ char *keyword;
+ gboolean found;
+ gint i;
+
+ i = 0;
+ exclude[i++] = NAUTILUS_FILE_EMBLEM_NAME_TRASH;
+ exclude[i++] = NAUTILUS_FILE_EMBLEM_NAME_NOTE;
+
+ parent_file = nautilus_file_get_parent (file);
+ if (parent_file)
+ {
+ if (!nautilus_file_can_write (parent_file))
+ {
+ exclude[i++] = NAUTILUS_FILE_EMBLEM_NAME_CANT_WRITE;
+ }
+ nautilus_file_unref (parent_file);
+ }
+ exclude[i++] = NULL;
+
+ for (l = *metadata_keywords; l != NULL; l = l->next)
+ {
+ keyword = l->data;
+ found = FALSE;
+
+ for (i = 0; exclude[i] != NULL; i++)
+ {
+ if (strcmp (exclude[i], keyword) == 0)
+ {
+ found = TRUE;
+ break;
+ }
+ }
+
+ if (!found)
+ {
+ res = g_list_prepend (res, keyword);
+ }
+ }
+
+ g_list_free (*metadata_keywords);
+ *metadata_keywords = res;
+}
+
+/**
+ * nautilus_file_get_keywords
+ *
+ * Return this file's keywords.
+ * @file: NautilusFile representing the file in question.
+ *
+ * Returns: A list of keywords.
+ *
+ **/
+static GList *
+nautilus_file_get_keywords (NautilusFile *file)
+{
+ GList *keywords;
+ gchar **metadata_strv;
+ GList *metadata_keywords = NULL;
+
+ if (file == NULL)
+ {
+ return NULL;
+ }
+
+ g_return_val_if_fail (NAUTILUS_IS_FILE (file), NULL);
+
+ keywords = g_list_copy_deep (file->details->extension_emblems, (GCopyFunc) g_strdup, NULL);
+ keywords = g_list_concat (keywords, g_list_copy_deep (file->details->pending_extension_emblems, (GCopyFunc) g_strdup, NULL));
+
+ metadata_strv = nautilus_file_get_metadata_list (file, NAUTILUS_METADATA_KEY_EMBLEMS);
+ /* Convert array to list */
+ for (gint i = 0; metadata_strv != NULL && metadata_strv[i] != NULL; i++)
+ {
+ metadata_keywords = g_list_prepend (metadata_keywords, metadata_strv[i]);
+ }
+ /* Free only the container array. The strings are owned by the list now. */
+ g_free (metadata_strv);
+
+ clean_up_metadata_keywords (file, &metadata_keywords);
+ keywords = g_list_concat (keywords, metadata_keywords);
+
+ return sort_keyword_list_and_remove_duplicates (keywords);
+}
+
+/**
+ * nautilus_file_get_emblem_icons
+ *
+ * Return the list of names of emblems that this file should display,
+ * in canonical order.
+ * @file: NautilusFile representing the file in question.
+ *
+ * Returns: (transfer full) (element-type GIcon): A list of emblem names.
+ *
+ **/
+GList *
+nautilus_file_get_emblem_icons (NautilusFile *file)
+{
+ GList *keywords, *l;
+ GList *icons;
+ char *icon_names[2];
+ char *keyword;
+ GIcon *icon;
+
+ if (file == NULL)
+ {
+ return NULL;
+ }
+
+ g_return_val_if_fail (NAUTILUS_IS_FILE (file), NULL);
+
+ keywords = nautilus_file_get_keywords (file);
+ keywords = prepend_automatic_keywords (file, keywords);
+
+ icons = NULL;
+ for (l = keywords; l != NULL; l = l->next)
+ {
+ keyword = l->data;
+
+ icon_names[0] = g_strconcat ("emblem-", keyword, NULL);
+ icon_names[1] = keyword;
+ icon = g_themed_icon_new_from_names (icon_names, 2);
+ g_free (icon_names[0]);
+
+ icons = g_list_prepend (icons, icon);
+ }
+
+ icon = get_mount_icon (file);
+ if (icon != NULL)
+ {
+ icons = g_list_prepend (icons, icon);
+ }
+
+ g_list_free_full (keywords, g_free);
+
+ return icons;
+}
+
+GIcon *
+nautilus_file_get_gicon (NautilusFile *file,
+ NautilusFileIconFlags flags)
+{
+ GIcon *icon;
+
+ if (file == NULL)
+ {
+ return NULL;
+ }
+
+ icon = get_custom_icon (file);
+ if (icon != NULL)
+ {
+ return icon;
+ }
+
+ if (flags & NAUTILUS_FILE_ICON_FLAGS_USE_MOUNT_ICON)
+ {
+ icon = get_mount_icon (file);
+
+ if (icon != NULL)
+ {
+ goto out;
+ }
+ }
+
+ if (file->details->icon)
+ {
+ icon = g_object_ref (file->details->icon);
+ }
+
+out:
+ if (icon == NULL)
+ {
+ icon = g_object_ref (get_default_file_icon ());
+ }
+
+ return icon;
+}
+
+char *
+nautilus_file_get_thumbnail_path (NautilusFile *file)
+{
+ return g_strdup (file->details->thumbnail_path);
+}
+
+static NautilusIconInfo *
+nautilus_file_get_thumbnail_icon (NautilusFile *file,
+ int size,
+ int scale,
+ NautilusFileIconFlags flags)
+{
+ g_autoptr (GdkPaintable) paintable = NULL;
+ NautilusIconInfo *icon;
+
+ icon = NULL;
+
+ if (file->details->thumbnail != NULL)
+ {
+ GdkPixbuf *pixbuf = file->details->thumbnail;
+ double width = gdk_pixbuf_get_width (pixbuf) / scale;
+ double height = gdk_pixbuf_get_height (pixbuf) / scale;
+ g_autoptr (GdkTexture) texture = gdk_texture_new_for_pixbuf (pixbuf);
+ g_autoptr (GtkSnapshot) snapshot = gtk_snapshot_new ();
+ GskRoundedRect rounded_rect;
+
+ if (MAX (width, height) > size)
+ {
+ float scale_down_factor = MAX (width, height) / size;
+
+ width = width / scale_down_factor;
+ height = height / scale_down_factor;
+ }
+
+ gsk_rounded_rect_init_from_rect (&rounded_rect,
+ &GRAPHENE_RECT_INIT (0, 0, width, height),
+ 2 /* radius*/);
+ gtk_snapshot_push_rounded_clip (snapshot, &rounded_rect);
+
+ gdk_paintable_snapshot (GDK_PAINTABLE (texture),
+ GDK_SNAPSHOT (snapshot),
+ width, height);
+
+ if (size >= NAUTILUS_GRID_ICON_SIZE_SMALL &&
+ nautilus_is_video_file (file))
+ {
+ nautilus_ui_frame_video (snapshot, width, height);
+ }
+
+ gtk_snapshot_pop (snapshot); /* End rounded clip */
+
+ DEBUG ("Returning thumbnailed image, at size %d %d",
+ (int) (width), (int) (height));
+ paintable = gtk_snapshot_to_paintable (snapshot, NULL);
+ }
+ else if (file->details->thumbnail_path == NULL &&
+ file->details->can_read &&
+ !file->details->is_thumbnailing &&
+ !file->details->thumbnailing_failed &&
+ nautilus_can_thumbnail (file))
+ {
+ nautilus_create_thumbnail (file);
+ }
+
+ if (paintable != NULL)
+ {
+ icon = nautilus_icon_info_new_for_paintable (paintable, scale);
+ }
+ else if (file->details->is_thumbnailing)
+ {
+ g_autoptr (GIcon) gicon = g_themed_icon_new (ICON_NAME_THUMBNAIL_LOADING);
+ icon = nautilus_icon_info_lookup (gicon, size, scale);
+ }
+
+ return icon;
+}
+
+NautilusIconInfo *
+nautilus_file_get_icon (NautilusFile *file,
+ int size,
+ int scale,
+ NautilusFileIconFlags flags)
+{
+ NautilusIconInfo *icon;
+ GIcon *gicon;
+
+ icon = NULL;
+
+ if (file == NULL)
+ {
+ goto out;
+ }
+
+ gicon = get_custom_icon (file);
+ if (gicon != NULL)
+ {
+ icon = nautilus_icon_info_lookup (gicon, size, scale);
+ g_object_unref (gicon);
+
+ goto out;
+ }
+
+ DEBUG ("Called file_get_icon(), at size %d", size);
+
+ if (flags & NAUTILUS_FILE_ICON_FLAGS_USE_THUMBNAILS &&
+ nautilus_file_should_show_thumbnail (file) &&
+ size >= NAUTILUS_THUMBNAIL_MINIMUM_ICON_SIZE)
+ {
+ icon = nautilus_file_get_thumbnail_icon (file, size, scale, flags);
+ }
+
+ if (icon == NULL)
+ {
+ gicon = nautilus_file_get_gicon (file, flags);
+ icon = nautilus_icon_info_lookup (gicon, size, scale);
+ g_object_unref (gicon);
+
+ if (nautilus_icon_info_is_fallback (icon))
+ {
+ g_object_unref (icon);
+ icon = nautilus_icon_info_lookup (get_default_file_icon (), size, scale);
+ }
+ }
+
+out:
+ return icon;
+}
+
+GdkTexture *
+nautilus_file_get_icon_texture (NautilusFile *file,
+ int size,
+ int scale,
+ NautilusFileIconFlags flags)
+{
+ g_autoptr (NautilusIconInfo) info = NULL;
+
+ info = nautilus_file_get_icon (file, size, scale, flags);
+
+ return nautilus_icon_info_get_texture (info);
+}
+
+GdkPaintable *
+nautilus_file_get_icon_paintable (NautilusFile *file,
+ int size,
+ int scale,
+ NautilusFileIconFlags flags)
+{
+ g_autoptr (NautilusIconInfo) info = NULL;
+
+ info = nautilus_file_get_icon (file, size, scale, flags);
+
+ return nautilus_icon_info_get_paintable (info);
+}
+
+gboolean
+nautilus_file_get_date (NautilusFile *file,
+ NautilusDateType date_type,
+ time_t *date)
+{
+ if (date != NULL)
+ {
+ *date = 0;
+ }
+
+ g_return_val_if_fail (date_type == NAUTILUS_DATE_TYPE_ACCESSED
+ || date_type == NAUTILUS_DATE_TYPE_MODIFIED
+ || date_type == NAUTILUS_DATE_TYPE_CREATED
+ || date_type == NAUTILUS_DATE_TYPE_TRASHED
+ || date_type == NAUTILUS_DATE_TYPE_RECENCY,
+ FALSE);
+
+ if (file == NULL)
+ {
+ return FALSE;
+ }
+
+ g_return_val_if_fail (NAUTILUS_IS_FILE (file), FALSE);
+
+ return NAUTILUS_FILE_CLASS (G_OBJECT_GET_CLASS (file))->get_date (file, date_type, date);
+}
+
+static char *
+nautilus_file_get_where_string (NautilusFile *file)
+{
+ if (file == NULL)
+ {
+ return NULL;
+ }
+
+ g_return_val_if_fail (NAUTILUS_IS_FILE (file), NULL);
+
+ return NAUTILUS_FILE_CLASS (G_OBJECT_GET_CLASS (file))->get_where_string (file);
+}
+
+static char *
+nautilus_file_get_trash_original_file_parent_as_string (NautilusFile *file)
+{
+ NautilusFile *orig_file;
+ char *filename;
+
+ filename = NULL;
+ orig_file = nautilus_file_get_trash_original_file (file);
+ if (orig_file != NULL)
+ {
+ filename = nautilus_file_get_parent_uri_for_display (orig_file);
+
+ nautilus_file_unref (orig_file);
+ }
+
+ return filename;
+}
+
+/**
+ * nautilus_file_get_date_as_string:
+ *
+ * Get a user-displayable string representing a file modification date.
+ * The caller is responsible for g_free-ing this string.
+ * @file: NautilusFile representing the file in question.
+ *
+ * Returns: Newly allocated string ready to display to the user.
+ *
+ **/
+static char *
+nautilus_file_get_date_as_string (NautilusFile *file,
+ NautilusDateType date_type,
+ NautilusDateFormat date_format)
+{
+ time_t file_time_raw;
+ GDateTime *file_date_time, *now;
+ GDateTime *today_midnight;
+ gint days_ago;
+ gboolean use_24;
+ const gchar *format;
+ gchar *result;
+ gchar *result_with_ratio;
+
+ if (!nautilus_file_get_date (file, date_type, &file_time_raw))
+ {
+ return NULL;
+ }
+
+ file_date_time = g_date_time_new_from_unix_local (file_time_raw);
+ if (date_format != NAUTILUS_DATE_FORMAT_FULL)
+ {
+ GDateTime *file_date;
+
+ now = g_date_time_new_now_local ();
+ today_midnight = g_date_time_new_local (g_date_time_get_year (now),
+ g_date_time_get_month (now),
+ g_date_time_get_day_of_month (now),
+ 0, 0, 0);
+
+ file_date = g_date_time_new_local (g_date_time_get_year (file_date_time),
+ g_date_time_get_month (file_date_time),
+ g_date_time_get_day_of_month (file_date_time),
+ 0, 0, 0);
+
+ days_ago = g_date_time_difference (today_midnight, file_date) / G_TIME_SPAN_DAY;
+
+ use_24 = g_settings_get_enum (gnome_interface_preferences, "clock-format") ==
+ G_DESKTOP_CLOCK_FORMAT_24H;
+
+ /* Show only the time if date is on today */
+ if (days_ago == 0)
+ {
+ if (use_24)
+ {
+ /* Translators: Time in 24h format */
+ format = _("%H:%M");
+ }
+ else
+ {
+ /* Translators: Time in 12h format */
+ format = _("%l:%M %p");
+ }
+ }
+ /* Show the word "Yesterday" and time if date is on yesterday */
+ else if (days_ago == 1)
+ {
+ if (date_format == NAUTILUS_DATE_FORMAT_REGULAR)
+ {
+ /* xgettext:no-c-format */
+ format = _("Yesterday");
+ }
+ else
+ {
+ if (use_24)
+ {
+ /* Translators: this is the word Yesterday followed by
+ * a time in 24h format. i.e. "Yesterday 23:04" */
+ /* xgettext:no-c-format */
+ format = _("Yesterday %H:%M");
+ }
+ else
+ {
+ /* Translators: this is the word Yesterday followed by
+ * a time in 12h format. i.e. "Yesterday 9:04 PM" */
+ /* xgettext:no-c-format */
+ format = _("Yesterday %l:%M %p");
+ }
+ }
+ }
+ /* Show a week day and time if date is in the last week */
+ else if (days_ago > 1 && days_ago < 7)
+ {
+ if (date_format == NAUTILUS_DATE_FORMAT_REGULAR)
+ {
+ /* xgettext:no-c-format */
+ format = _("%a");
+ }
+ else
+ {
+ if (use_24)
+ {
+ /* Translators: this is the name of the week day followed by
+ * a time in 24h format. i.e. "Monday 23:04" */
+ /* xgettext:no-c-format */
+ format = _("%a %H:%M");
+ }
+ else
+ {
+ /* Translators: this is the week day name followed by
+ * a time in 12h format. i.e. "Monday 9:04 PM" */
+ /* xgettext:no-c-format */
+ format = _("%a %l:%M %p");
+ }
+ }
+ }
+ else if (g_date_time_get_year (file_date) == g_date_time_get_year (now))
+ {
+ if (date_format == NAUTILUS_DATE_FORMAT_REGULAR)
+ {
+ /* Translators: this is the day of the month followed
+ * by the abbreviated month name i.e. "3 Feb" */
+ /* xgettext:no-c-format */
+ format = _("%-e %b");
+ }
+ else
+ {
+ if (use_24)
+ {
+ /* Translators: this is the day of the month followed
+ * by the abbreviated month name followed by a time in
+ * 24h format i.e. "3 Feb 23:04" */
+ /* xgettext:no-c-format */
+ format = _("%-e %b %H:%M");
+ }
+ else
+ {
+ /* Translators: this is the day of the month followed
+ * by the abbreviated month name followed by a time in
+ * 12h format i.e. "3 Feb 9:04" */
+ /* xgettext:no-c-format */
+ format = _("%-e %b %l:%M %p");
+ }
+ }
+ }
+ else
+ {
+ if (date_format == NAUTILUS_DATE_FORMAT_REGULAR)
+ {
+ /* Translators: this is the day of the month followed by the abbreviated
+ * month name followed by the year i.e. "3 Feb 2015" */
+ /* xgettext:no-c-format */
+ format = _("%-e %b %Y");
+ }
+ else
+ {
+ if (use_24)
+ {
+ /* Translators: this is the day number followed
+ * by the abbreviated month name followed by the year followed
+ * by a time in 24h format i.e. "3 Feb 2015 23:04" */
+ /* xgettext:no-c-format */
+ format = _("%-e %b %Y %H:%M");
+ }
+ else
+ {
+ /* Translators: this is the day number followed
+ * by the abbreviated month name followed by the year followed
+ * by a time in 12h format i.e. "3 Feb 2015 9:04 PM" */
+ /* xgettext:no-c-format */
+ format = _("%-e %b %Y %l:%M %p");
+ }
+ }
+ }
+
+ g_date_time_unref (file_date);
+ g_date_time_unref (now);
+ g_date_time_unref (today_midnight);
+ }
+ else
+ {
+ /* xgettext:no-c-format */
+ format = _("%c");
+ }
+
+ result = g_date_time_format (file_date_time, format);
+ g_date_time_unref (file_date_time);
+
+ /* Replace ":" with ratio. Replacement is done afterward because g_date_time_format
+ * may fail with utf8 chars in some locales */
+ result_with_ratio = eel_str_replace_substring (result, ":", "∶");
+ g_free (result);
+
+ return result_with_ratio;
+}
+
+static void
+show_directory_item_count_changed_callback (gpointer callback_data)
+{
+ show_directory_item_count = g_settings_get_enum (nautilus_preferences, NAUTILUS_PREFERENCES_SHOW_DIRECTORY_ITEM_COUNTS);
+}
+
+gboolean
+nautilus_file_should_show_directory_item_count (NautilusFile *file)
+{
+ static gboolean show_directory_item_count_callback_added = FALSE;
+
+ g_return_val_if_fail (NAUTILUS_IS_FILE (file), FALSE);
+
+ if (file->details->mime_type &&
+ strcmp (file->details->mime_type, "x-directory/smb-share") == 0)
+ {
+ return FALSE;
+ }
+
+ /* Add the callback once for the life of our process */
+ if (!show_directory_item_count_callback_added)
+ {
+ g_signal_connect_swapped (nautilus_preferences,
+ "changed::" NAUTILUS_PREFERENCES_SHOW_DIRECTORY_ITEM_COUNTS,
+ G_CALLBACK (show_directory_item_count_changed_callback),
+ NULL);
+ show_directory_item_count_callback_added = TRUE;
+
+ /* Peek for the first time */
+ show_directory_item_count_changed_callback (NULL);
+ }
+
+ return get_speed_tradeoff_preference_for_file (file, show_directory_item_count);
+}
+
+/**
+ * nautilus_file_get_directory_item_count
+ *
+ * Get the number of items in a directory.
+ * @file: NautilusFile representing a directory.
+ * @count: Place to put count.
+ * @count_unreadable: Set to TRUE (if non-NULL) if permissions prevent
+ * the item count from being read on this directory. Otherwise set to FALSE.
+ *
+ * Returns: TRUE if count is available.
+ *
+ **/
+gboolean
+nautilus_file_get_directory_item_count (NautilusFile *file,
+ guint *count,
+ gboolean *count_unreadable)
+{
+ if (count != NULL)
+ {
+ *count = 0;
+ }
+ if (count_unreadable != NULL)
+ {
+ *count_unreadable = FALSE;
+ }
+
+ g_return_val_if_fail (NAUTILUS_IS_FILE (file), FALSE);
+
+ if (!nautilus_file_is_directory (file))
+ {
+ return FALSE;
+ }
+
+ if (!nautilus_file_should_show_directory_item_count (file))
+ {
+ return FALSE;
+ }
+
+ return NAUTILUS_FILE_CLASS (G_OBJECT_GET_CLASS (file))->get_item_count
+ (file, count, count_unreadable);
+}
+
+/**
+ * nautilus_file_get_deep_counts
+ *
+ * Get the statistics about items inside a directory.
+ * @file: NautilusFile representing a directory or file.
+ * @directory_count: Place to put count of directories inside.
+ * @files_count: Place to put count of files inside.
+ * @unreadable_directory_count: Number of directories encountered
+ * that were unreadable.
+ * @total_size: Total size of all files and directories visited.
+ * @force: Whether the deep counts should even be collected if
+ * nautilus_file_should_show_directory_item_count returns FALSE
+ * for this file.
+ *
+ * Returns: Status to indicate whether sizes are available.
+ *
+ **/
+NautilusRequestStatus
+nautilus_file_get_deep_counts (NautilusFile *file,
+ guint *directory_count,
+ guint *file_count,
+ guint *unreadable_directory_count,
+ goffset *total_size,
+ gboolean force)
+{
+ if (directory_count != NULL)
+ {
+ *directory_count = 0;
+ }
+ if (file_count != NULL)
+ {
+ *file_count = 0;
+ }
+ if (unreadable_directory_count != NULL)
+ {
+ *unreadable_directory_count = 0;
+ }
+ if (total_size != NULL)
+ {
+ *total_size = 0;
+ }
+
+ g_return_val_if_fail (NAUTILUS_IS_FILE (file), NAUTILUS_REQUEST_DONE);
+
+ if (!force && !nautilus_file_should_show_directory_item_count (file))
+ {
+ /* Set field so an existing value isn't treated as up-to-date
+ * when preference changes later.
+ */
+ file->details->deep_counts_status = NAUTILUS_REQUEST_NOT_STARTED;
+ return file->details->deep_counts_status;
+ }
+
+ return NAUTILUS_FILE_CLASS (G_OBJECT_GET_CLASS (file))->get_deep_counts
+ (file, directory_count, file_count,
+ unreadable_directory_count, total_size);
+}
+
+void
+nautilus_file_recompute_deep_counts (NautilusFile *file)
+{
+ if (file->details->deep_counts_status != NAUTILUS_REQUEST_IN_PROGRESS)
+ {
+ file->details->deep_counts_status = NAUTILUS_REQUEST_NOT_STARTED;
+ if (file->details->directory != NULL)
+ {
+ nautilus_directory_add_file_to_work_queue (file->details->directory, file);
+ nautilus_directory_async_state_changed (file->details->directory);
+ }
+ }
+}
+
+gboolean
+nautilus_file_can_get_size (NautilusFile *file)
+{
+ return file->details->size == -1;
+}
+
+
+/**
+ * nautilus_file_get_size
+ *
+ * Get the file size.
+ * @file: NautilusFile representing the file in question.
+ *
+ * Returns: Size in bytes.
+ *
+ **/
+goffset
+nautilus_file_get_size (NautilusFile *file)
+{
+ /* Before we have info on the file, we don't know the size. */
+ if (file->details->size == -1)
+ {
+ return 0;
+ }
+ return file->details->size;
+}
+
+time_t
+nautilus_file_get_mtime (NautilusFile *file)
+{
+ return file->details->mtime;
+}
+
+time_t
+nautilus_file_get_atime (NautilusFile *file)
+{
+ return file->details->atime;
+}
+
+time_t
+nautilus_file_get_btime (NautilusFile *file)
+{
+ return file->details->btime;
+}
+
+time_t
+nautilus_file_get_recency (NautilusFile *file)
+{
+ g_return_val_if_fail (NAUTILUS_IS_FILE (file), 0);
+
+ return file->details->recency;
+}
+
+time_t
+nautilus_file_get_trash_time (NautilusFile *file)
+{
+ g_return_val_if_fail (NAUTILUS_IS_FILE (file), 0);
+
+ return file->details->trash_time;
+}
+
+static void
+set_attributes_get_info_callback (GObject *source_object,
+ GAsyncResult *res,
+ gpointer callback_data)
+{
+ NautilusFileOperation *op;
+ GFileInfo *new_info;
+ GError *error;
+
+ op = callback_data;
+
+ error = NULL;
+ new_info = g_file_query_info_finish (G_FILE (source_object), res, &error);
+ if (new_info != NULL)
+ {
+ if (nautilus_file_update_info (op->file, new_info))
+ {
+ nautilus_file_changed (op->file);
+ }
+ g_object_unref (new_info);
+ }
+ nautilus_file_operation_complete (op, NULL, error);
+ if (error)
+ {
+ g_error_free (error);
+ }
+}
+
+
+static void
+set_attributes_callback (GObject *source_object,
+ GAsyncResult *result,
+ gpointer callback_data)
+{
+ NautilusFileOperation *op;
+ GError *error;
+ gboolean res;
+
+ op = callback_data;
+
+ error = NULL;
+ res = g_file_set_attributes_finish (G_FILE (source_object),
+ result,
+ NULL,
+ &error);
+
+ if (res)
+ {
+ g_file_query_info_async (G_FILE (source_object),
+ NAUTILUS_FILE_DEFAULT_ATTRIBUTES,
+ 0,
+ G_PRIORITY_DEFAULT,
+ op->cancellable,
+ set_attributes_get_info_callback, op);
+ }
+ else
+ {
+ nautilus_file_operation_complete (op, NULL, error);
+ g_error_free (error);
+ }
+}
+
+void
+nautilus_file_set_attributes (NautilusFile *file,
+ GFileInfo *attributes,
+ NautilusFileOperationCallback callback,
+ gpointer callback_data)
+{
+ NautilusFileOperation *op;
+ GFile *location;
+
+ op = nautilus_file_operation_new (file, callback, callback_data);
+
+ location = nautilus_file_get_location (file);
+ g_file_set_attributes_async (location,
+ attributes,
+ 0,
+ G_PRIORITY_DEFAULT,
+ op->cancellable,
+ set_attributes_callback,
+ op);
+ g_object_unref (location);
+}
+
+void
+nautilus_file_set_search_relevance (NautilusFile *file,
+ gdouble relevance)
+{
+ file->details->search_relevance = relevance;
+}
+
+void
+nautilus_file_set_search_fts_snippet (NautilusFile *file,
+ const gchar *fts_snippet)
+{
+ file->details->fts_snippet = g_strdup (fts_snippet);
+}
+
+const gchar *
+nautilus_file_get_search_fts_snippet (NautilusFile *file)
+{
+ return file->details->fts_snippet;
+}
+
+/**
+ * nautilus_file_can_get_permissions:
+ *
+ * Check whether the permissions for a file are determinable.
+ * This might not be the case for files on non-UNIX file systems.
+ *
+ * @file: The file in question.
+ *
+ * Return value: TRUE if the permissions are valid.
+ */
+gboolean
+nautilus_file_can_get_permissions (NautilusFile *file)
+{
+ return file->details->has_permissions;
+}
+
+/**
+ * nautilus_file_can_set_permissions:
+ *
+ * Check whether the current user is allowed to change
+ * the permissions of a file.
+ *
+ * @file: The file in question.
+ *
+ * Return value: TRUE if the current user can change the
+ * permissions of @file, FALSE otherwise. It's always possible
+ * that when you actually try to do it, you will fail.
+ */
+gboolean
+nautilus_file_can_set_permissions (NautilusFile *file)
+{
+ g_autoptr (GFile) location = NULL;
+ uid_t user_id;
+
+ location = nautilus_file_get_location (file);
+
+ if (file->details->uid != -1 &&
+ g_file_is_native (location))
+ {
+ /* Check the user. */
+ user_id = geteuid ();
+
+ /* Owner is allowed to set permissions. */
+ if (user_id == (uid_t) file->details->uid)
+ {
+ return TRUE;
+ }
+
+ /* Root is also allowed to set permissions. */
+ if (user_id == 0)
+ {
+ return TRUE;
+ }
+
+ /* Nobody else is allowed. */
+ return FALSE;
+ }
+
+ /* pretend to have full chmod rights when no info is available, relevant when
+ * the FS can't provide ownership info, for instance for FTP */
+ return TRUE;
+}
+
+guint
+nautilus_file_get_permissions (NautilusFile *file)
+{
+ g_return_val_if_fail (nautilus_file_can_get_permissions (file), 0);
+
+ return file->details->permissions;
+}
+
+/**
+ * nautilus_file_set_permissions:
+ *
+ * Change a file's permissions. This should only be called if
+ * nautilus_file_can_set_permissions returned TRUE.
+ *
+ * @file: NautilusFile representing the file in question.
+ * @new_permissions: New permissions value. This is the whole
+ * set of permissions, not a delta.
+ **/
+void
+nautilus_file_set_permissions (NautilusFile *file,
+ guint32 new_permissions,
+ NautilusFileOperationCallback callback,
+ gpointer callback_data)
+{
+ GFileInfo *info;
+ GError *error;
+
+ if (!nautilus_file_can_set_permissions (file))
+ {
+ /* Claim that something changed even if the permission change failed.
+ * This makes it easier for some clients who see the "reverting"
+ * to the old permissions as "changing back".
+ */
+ nautilus_file_changed (file);
+ error = g_error_new (G_IO_ERROR, G_IO_ERROR_PERMISSION_DENIED,
+ _("Not allowed to set permissions"));
+ (*callback)(file, NULL, error, callback_data);
+ g_error_free (error);
+ return;
+ }
+
+ /* Test the permissions-haven't-changed case explicitly
+ * because we don't want to send the file-changed signal if
+ * nothing changed.
+ */
+ if (new_permissions == file->details->permissions)
+ {
+ (*callback)(file, NULL, NULL, callback_data);
+ return;
+ }
+
+ if (!nautilus_file_undo_manager_is_operating ())
+ {
+ NautilusFileUndoInfo *undo_info;
+
+ undo_info = nautilus_file_undo_info_permissions_new (nautilus_file_get_location (file),
+ file->details->permissions,
+ new_permissions);
+ nautilus_file_undo_manager_set_action (undo_info);
+ }
+
+ info = g_file_info_new ();
+ g_file_info_set_attribute_uint32 (info, G_FILE_ATTRIBUTE_UNIX_MODE, new_permissions);
+ nautilus_file_set_attributes (file, info, callback, callback_data);
+
+ g_object_unref (info);
+}
+
+/**
+ * nautilus_file_can_get_selinux_context:
+ *
+ * Check whether the selinux context for a file are determinable.
+ * This might not be the case for files on non-UNIX file systems,
+ * files without a context or systems that don't support selinux.
+ *
+ * @file: The file in question.
+ *
+ * Return value: TRUE if the permissions are valid.
+ */
+gboolean
+nautilus_file_can_get_selinux_context (NautilusFile *file)
+{
+ return file->details->selinux_context != NULL;
+}
+
+
+/**
+ * nautilus_file_get_selinux_context:
+ *
+ * Get a user-displayable string representing a file's selinux
+ * context
+ * @file: NautilusFile representing the file in question.
+ *
+ * Returns: Newly allocated string ready to display to the user.
+ *
+ **/
+char *
+nautilus_file_get_selinux_context (NautilusFile *file)
+{
+ char *translated;
+ char *raw;
+
+ g_return_val_if_fail (NAUTILUS_IS_FILE (file), NULL);
+
+ if (!nautilus_file_can_get_selinux_context (file))
+ {
+ return NULL;
+ }
+
+ raw = file->details->selinux_context;
+
+#ifdef HAVE_SELINUX
+ if (selinux_raw_to_trans_context (raw, &translated) == 0)
+ {
+ char *tmp;
+ tmp = g_strdup (translated);
+ freecon (translated);
+ translated = tmp;
+ }
+ else
+#endif
+ {
+ translated = g_strdup (raw);
+ }
+
+ return translated;
+}
+
+static char *
+get_real_name (const char *name,
+ const char *gecos)
+{
+ char *locale_string, *part_before_comma, *capitalized_login_name, *real_name;
+
+ if (gecos == NULL)
+ {
+ return NULL;
+ }
+
+ locale_string = eel_str_strip_substring_and_after (gecos, ",");
+ if (!g_utf8_validate (locale_string, -1, NULL))
+ {
+ part_before_comma = g_locale_to_utf8 (locale_string, -1, NULL, NULL, NULL);
+ g_free (locale_string);
+ }
+ else
+ {
+ part_before_comma = locale_string;
+ }
+
+ if (!g_utf8_validate (name, -1, NULL))
+ {
+ locale_string = g_locale_to_utf8 (name, -1, NULL, NULL, NULL);
+ }
+ else
+ {
+ locale_string = g_strdup (name);
+ }
+
+ capitalized_login_name = eel_str_capitalize (locale_string);
+ g_free (locale_string);
+
+ if (capitalized_login_name == NULL)
+ {
+ real_name = part_before_comma;
+ }
+ else
+ {
+ real_name = eel_str_replace_substring
+ (part_before_comma, "&", capitalized_login_name);
+ g_free (part_before_comma);
+ }
+
+
+ if (g_strcmp0 (real_name, NULL) == 0
+ || g_strcmp0 (name, real_name) == 0
+ || g_strcmp0 (capitalized_login_name, real_name) == 0)
+ {
+ g_free (real_name);
+ real_name = NULL;
+ }
+
+ g_free (capitalized_login_name);
+
+ return real_name;
+}
+
+static gboolean
+get_group_id_from_group_name (const char *group_name,
+ uid_t *gid)
+{
+ struct group *group;
+
+ g_assert (gid != NULL);
+
+ group = getgrnam (group_name);
+
+ if (group == NULL)
+ {
+ return FALSE;
+ }
+
+ *gid = group->gr_gid;
+
+ return TRUE;
+}
+
+static gboolean
+get_ids_from_user_name (const char *user_name,
+ uid_t *uid,
+ uid_t *gid)
+{
+ struct passwd *password_info;
+
+ g_assert (uid != NULL || gid != NULL);
+
+ password_info = getpwnam (user_name);
+
+ if (password_info == NULL)
+ {
+ return FALSE;
+ }
+
+ if (uid != NULL)
+ {
+ *uid = password_info->pw_uid;
+ }
+
+ if (gid != NULL)
+ {
+ *gid = password_info->pw_gid;
+ }
+
+ return TRUE;
+}
+
+static gboolean
+get_user_id_from_user_name (const char *user_name,
+ uid_t *id)
+{
+ return get_ids_from_user_name (user_name, id, NULL);
+}
+
+static gboolean
+get_id_from_digit_string (const char *digit_string,
+ uid_t *id)
+{
+ long scanned_id;
+ char c;
+
+ g_assert (id != NULL);
+
+ /* Only accept string if it has one integer with nothing
+ * afterwards.
+ */
+ if (sscanf (digit_string, "%ld%c", &scanned_id, &c) != 1)
+ {
+ return FALSE;
+ }
+ *id = scanned_id;
+ return TRUE;
+}
+
+/**
+ * nautilus_file_can_get_owner:
+ *
+ * Check whether the owner a file is determinable.
+ * This might not be the case for files on non-UNIX file systems.
+ *
+ * @file: The file in question.
+ *
+ * Return value: TRUE if the owner is valid.
+ */
+gboolean
+nautilus_file_can_get_owner (NautilusFile *file)
+{
+ /* Before we have info on a file, the owner is unknown. */
+ return file->details->uid != -1;
+}
+
+/**
+ * nautilus_file_get_uid:
+ *
+ * Get the user id of the file's owner.
+ *
+ * @file: The file in question.
+ *
+ * Return value: (transfer none): the user id.
+ */
+const uid_t
+nautilus_file_get_uid (NautilusFile *file)
+{
+ return file->details->uid;
+}
+
+/**
+ * nautilus_file_get_owner_name:
+ *
+ * Get the user name of the file's owner. If the owner has no
+ * name, returns the userid as a string. The caller is responsible
+ * for g_free-ing this string.
+ *
+ * @file: The file in question.
+ *
+ * Return value: A newly-allocated string.
+ */
+char *
+nautilus_file_get_owner_name (NautilusFile *file)
+{
+ return nautilus_file_get_owner_as_string (file, FALSE);
+}
+
+/**
+ * nautilus_file_can_set_owner:
+ *
+ * Check whether the current user is allowed to change
+ * the owner of a file.
+ *
+ * @file: The file in question.
+ *
+ * Return value: TRUE if the current user can change the
+ * owner of @file, FALSE otherwise. It's always possible
+ * that when you actually try to do it, you will fail.
+ */
+gboolean
+nautilus_file_can_set_owner (NautilusFile *file)
+{
+ /* Not allowed to set the owner if we can't
+ * even read it. This can happen on non-UNIX file
+ * systems.
+ */
+ if (!nautilus_file_can_get_owner (file))
+ {
+ return FALSE;
+ }
+
+ /* Owner can be changed only in admin backend or by root */
+ return nautilus_file_is_in_admin (file) || geteuid () == 0;
+}
+
+/**
+ * nautilus_file_set_owner:
+ *
+ * Set the owner of a file. This will only have any effect if
+ * nautilus_file_can_set_owner returns TRUE.
+ *
+ * @file: The file in question.
+ * @user_name_or_id: The user name to set the owner to.
+ * If the string does not match any user name, and the
+ * string is an integer, the owner will be set to the
+ * userid represented by that integer.
+ * @callback: Function called when asynch owner change succeeds or fails.
+ * @callback_data: Parameter passed back with callback function.
+ */
+void
+nautilus_file_set_owner (NautilusFile *file,
+ const char *user_name_or_id,
+ NautilusFileOperationCallback callback,
+ gpointer callback_data)
+{
+ GError *error;
+ GFileInfo *info;
+ uid_t new_id;
+
+ if (!nautilus_file_can_set_owner (file))
+ {
+ /* Claim that something changed even if the permission
+ * change failed. This makes it easier for some
+ * clients who see the "reverting" to the old owner as
+ * "changing back".
+ */
+ nautilus_file_changed (file);
+ error = g_error_new (G_IO_ERROR, G_IO_ERROR_PERMISSION_DENIED,
+ _("Not allowed to set owner"));
+ (*callback)(file, NULL, error, callback_data);
+ g_error_free (error);
+ return;
+ }
+
+ /* If no match treating user_name_or_id as name, try treating
+ * it as id.
+ */
+ if (!get_user_id_from_user_name (user_name_or_id, &new_id)
+ && !get_id_from_digit_string (user_name_or_id, &new_id))
+ {
+ /* Claim that something changed even if the permission
+ * change failed. This makes it easier for some
+ * clients who see the "reverting" to the old owner as
+ * "changing back".
+ */
+ nautilus_file_changed (file);
+ error = g_error_new (G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT,
+ _("Specified owner “%s” doesn’t exist"), user_name_or_id);
+ (*callback)(file, NULL, error, callback_data);
+ g_error_free (error);
+ return;
+ }
+
+ /* Test the owner-hasn't-changed case explicitly because we
+ * don't want to send the file-changed signal if nothing
+ * changed.
+ */
+ if (new_id == (uid_t) file->details->uid)
+ {
+ (*callback)(file, NULL, NULL, callback_data);
+ return;
+ }
+
+ if (!nautilus_file_undo_manager_is_operating ())
+ {
+ NautilusFileUndoInfo *undo_info;
+ char *current_owner;
+
+ current_owner = nautilus_file_get_owner_as_string (file, FALSE);
+
+ undo_info = nautilus_file_undo_info_ownership_new (NAUTILUS_FILE_UNDO_OP_CHANGE_OWNER,
+ nautilus_file_get_location (file),
+ current_owner,
+ user_name_or_id);
+ nautilus_file_undo_manager_set_action (undo_info);
+
+ g_free (current_owner);
+ }
+
+ info = g_file_info_new ();
+ g_file_info_set_attribute_uint32 (info, G_FILE_ATTRIBUTE_UNIX_UID, new_id);
+ nautilus_file_set_attributes (file, info, callback, callback_data);
+ g_object_unref (info);
+}
+
+/**
+ * nautilus_get_user_names:
+ *
+ * Get a list of user names. For users with a different associated
+ * "real name", the real name follows the standard user name, separated
+ * by a dash surrounded by spaces. The caller is responsible for freeing
+ * this list and its contents.
+ */
+GList *
+nautilus_get_user_names (void)
+{
+ GList *list;
+ char *real_name, *name;
+ struct passwd *user;
+
+ list = NULL;
+
+ setpwent ();
+
+ while ((user = getpwent ()) != NULL)
+ {
+ real_name = get_real_name (user->pw_name, user->pw_gecos);
+ if (real_name != NULL && !g_str_equal (real_name, ""))
+ {
+ name = g_strconcat (user->pw_name, " – ", real_name, NULL);
+ }
+ else
+ {
+ name = g_strdup (user->pw_name);
+ }
+ g_free (real_name);
+ list = g_list_prepend (list, name);
+ }
+
+ endpwent ();
+
+ return g_list_sort (list, (GCompareFunc) g_utf8_collate);
+}
+
+/**
+ * nautilus_file_can_get_group:
+ *
+ * Check whether the group a file is determinable.
+ * This might not be the case for files on non-UNIX file systems.
+ *
+ * @file: The file in question.
+ *
+ * Return value: TRUE if the group is valid.
+ */
+gboolean
+nautilus_file_can_get_group (NautilusFile *file)
+{
+ /* Before we have info on a file, the group is unknown. */
+ return file->details->gid != -1;
+}
+
+/**
+ * nautilus_file_get_gid:
+ *
+ * Get the group id of the file's group.
+ *
+ * @file: The file in question.
+ *
+ * Return value: (transfer none): the group id.
+ */
+const gid_t
+nautilus_file_get_gid (NautilusFile *file)
+{
+ return file->details->gid;
+}
+
+/**
+ * nautilus_file_get_group_name:
+ *
+ * Get the name of the file's group. If the group has no
+ * name, returns the groupid as a string. The caller is responsible
+ * for g_free-ing this string.
+ *
+ * @file: The file in question.
+ *
+ * Return value: A newly-allocated string.
+ **/
+char *
+nautilus_file_get_group_name (NautilusFile *file)
+{
+ return g_strdup (file->details->group);
+}
+
+/**
+ * nautilus_file_can_set_group:
+ *
+ * Check whether the current user is allowed to change
+ * the group of a file.
+ *
+ * @file: The file in question.
+ *
+ * Return value: TRUE if the current user can change the
+ * group of @file, FALSE otherwise. It's always possible
+ * that when you actually try to do it, you will fail.
+ */
+gboolean
+nautilus_file_can_set_group (NautilusFile *file)
+{
+ uid_t user_id;
+
+ /* Not allowed to set the permissions if we can't
+ * even read them. This can happen on non-UNIX file
+ * systems.
+ */
+ if (!nautilus_file_can_get_group (file))
+ {
+ return FALSE;
+ }
+
+ /* Check the user. */
+ user_id = geteuid ();
+
+ /* Owner is allowed to set group (with restrictions). */
+ if (user_id == (uid_t) file->details->uid)
+ {
+ return TRUE;
+ }
+
+ /* Root is also allowed to set group. */
+ if (user_id == 0)
+ {
+ return TRUE;
+ }
+
+ /* Nobody else is allowed. */
+ return FALSE;
+}
+
+/* Get a list of group names, filtered to only the ones
+ * that contain the given username. If the username is
+ * NULL, returns a list of all group names.
+ */
+static GList *
+nautilus_get_group_names_for_user (void)
+{
+ GList *list;
+ struct group *group;
+ int count, i;
+ gid_t gid_list[NGROUPS_MAX + 1];
+
+
+ list = NULL;
+
+ count = getgroups (NGROUPS_MAX + 1, gid_list);
+ for (i = 0; i < count; i++)
+ {
+ group = getgrgid (gid_list[i]);
+ if (group == NULL)
+ {
+ break;
+ }
+
+ list = g_list_prepend (list, g_strdup (group->gr_name));
+ }
+
+ return g_list_sort (list, (GCompareFunc) g_utf8_collate);
+}
+
+/**
+ * nautilus_get_group_names:
+ *
+ * Get a list of all group names.
+ */
+GList *
+nautilus_get_all_group_names (void)
+{
+ GList *list;
+ struct group *group;
+
+ list = NULL;
+
+ setgrent ();
+
+ while ((group = getgrent ()) != NULL)
+ {
+ list = g_list_prepend (list, g_strdup (group->gr_name));
+ }
+
+ endgrent ();
+
+ return g_list_sort (list, (GCompareFunc) g_utf8_collate);
+}
+
+/**
+ * nautilus_file_get_settable_group_names:
+ *
+ * Get a list of all group names that the current user
+ * can set the group of a specific file to.
+ *
+ * @file: The NautilusFile in question.
+ */
+GList *
+nautilus_file_get_settable_group_names (NautilusFile *file)
+{
+ uid_t user_id;
+ GList *result;
+
+ if (!nautilus_file_can_set_group (file))
+ {
+ return NULL;
+ }
+
+ /* Check the user. */
+ user_id = geteuid ();
+
+ if (user_id == 0)
+ {
+ /* Root is allowed to set group to anything. */
+ result = nautilus_get_all_group_names ();
+ }
+ else if (user_id == (uid_t) file->details->uid)
+ {
+ /* Owner is allowed to set group to any that owner is member of. */
+ result = nautilus_get_group_names_for_user ();
+ }
+ else
+ {
+ g_warning ("unhandled case in nautilus_get_settable_group_names");
+ result = NULL;
+ }
+
+ return result;
+}
+
+/**
+ * nautilus_file_set_group:
+ *
+ * Set the group of a file. This will only have any effect if
+ * nautilus_file_can_set_group returns TRUE.
+ *
+ * @file: The file in question.
+ * @group_name_or_id: The group name to set the owner to.
+ * If the string does not match any group name, and the
+ * string is an integer, the group will be set to the
+ * group id represented by that integer.
+ * @callback: Function called when asynch group change succeeds or fails.
+ * @callback_data: Parameter passed back with callback function.
+ */
+void
+nautilus_file_set_group (NautilusFile *file,
+ const char *group_name_or_id,
+ NautilusFileOperationCallback callback,
+ gpointer callback_data)
+{
+ GError *error;
+ GFileInfo *info;
+ uid_t new_id;
+
+ if (!nautilus_file_can_set_group (file))
+ {
+ /* Claim that something changed even if the group
+ * change failed. This makes it easier for some
+ * clients who see the "reverting" to the old group as
+ * "changing back".
+ */
+ nautilus_file_changed (file);
+ error = g_error_new (G_IO_ERROR, G_IO_ERROR_PERMISSION_DENIED,
+ _("Not allowed to set group"));
+ (*callback)(file, NULL, error, callback_data);
+ g_error_free (error);
+ return;
+ }
+
+ /* If no match treating group_name_or_id as name, try treating
+ * it as id.
+ */
+ if (!get_group_id_from_group_name (group_name_or_id, &new_id)
+ && !get_id_from_digit_string (group_name_or_id, &new_id))
+ {
+ /* Claim that something changed even if the group
+ * change failed. This makes it easier for some
+ * clients who see the "reverting" to the old group as
+ * "changing back".
+ */
+ nautilus_file_changed (file);
+ error = g_error_new (G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT,
+ _("Specified group “%s” doesn’t exist"), group_name_or_id);
+ (*callback)(file, NULL, error, callback_data);
+ g_error_free (error);
+ return;
+ }
+
+ if (new_id == (gid_t) file->details->gid)
+ {
+ (*callback)(file, NULL, NULL, callback_data);
+ return;
+ }
+
+ if (!nautilus_file_undo_manager_is_operating ())
+ {
+ NautilusFileUndoInfo *undo_info;
+ char *current_group;
+
+ current_group = nautilus_file_get_group_name (file);
+ undo_info = nautilus_file_undo_info_ownership_new (NAUTILUS_FILE_UNDO_OP_CHANGE_GROUP,
+ nautilus_file_get_location (file),
+ current_group,
+ group_name_or_id);
+ nautilus_file_undo_manager_set_action (undo_info);
+
+ g_free (current_group);
+ }
+
+ info = g_file_info_new ();
+ g_file_info_set_attribute_uint32 (info, G_FILE_ATTRIBUTE_UNIX_GID, new_id);
+ nautilus_file_set_attributes (file, info, callback, callback_data);
+ g_object_unref (info);
+}
+
+/**
+ * nautilus_file_get_octal_permissions_as_string:
+ *
+ * Get a user-displayable string representing a file's permissions
+ * as an octal number. The caller
+ * is responsible for g_free-ing this string.
+ * @file: NautilusFile representing the file in question.
+ *
+ * Returns: Newly allocated string ready to display to the user.
+ *
+ **/
+static char *
+nautilus_file_get_octal_permissions_as_string (NautilusFile *file)
+{
+ guint32 permissions;
+
+ g_assert (NAUTILUS_IS_FILE (file));
+
+ if (!nautilus_file_can_get_permissions (file))
+ {
+ return NULL;
+ }
+
+ permissions = file->details->permissions;
+ return g_strdup_printf ("%03o", permissions);
+}
+
+/**
+ * nautilus_file_get_permissions_as_string:
+ *
+ * Get a user-displayable string representing a file's permissions. The caller
+ * is responsible for g_free-ing this string.
+ * @file: NautilusFile representing the file in question.
+ *
+ * Returns: Newly allocated string ready to display to the user.
+ *
+ **/
+static char *
+nautilus_file_get_permissions_as_string (NautilusFile *file)
+{
+ guint32 permissions;
+ gboolean is_directory;
+ gboolean is_link;
+ gboolean suid, sgid, sticky;
+
+ if (!nautilus_file_can_get_permissions (file))
+ {
+ return NULL;
+ }
+
+ g_assert (NAUTILUS_IS_FILE (file));
+
+ permissions = file->details->permissions;
+ is_directory = nautilus_file_is_directory (file);
+ is_link = nautilus_file_is_symbolic_link (file);
+
+ /* We use ls conventions for displaying these three obscure flags */
+ suid = permissions & S_ISUID;
+ sgid = permissions & S_ISGID;
+ sticky = permissions & S_ISVTX;
+
+ return g_strdup_printf ("%c%c%c%c%c%c%c%c%c%c",
+ is_link ? 'l' : is_directory ? 'd' : '-',
+ permissions & S_IRUSR ? 'r' : '-',
+ permissions & S_IWUSR ? 'w' : '-',
+ permissions & S_IXUSR
+ ? (suid ? 's' : 'x')
+ : (suid ? 'S' : '-'),
+ permissions & S_IRGRP ? 'r' : '-',
+ permissions & S_IWGRP ? 'w' : '-',
+ permissions & S_IXGRP
+ ? (sgid ? 's' : 'x')
+ : (sgid ? 'S' : '-'),
+ permissions & S_IROTH ? 'r' : '-',
+ permissions & S_IWOTH ? 'w' : '-',
+ permissions & S_IXOTH
+ ? (sticky ? 't' : 'x')
+ : (sticky ? 'T' : '-'));
+}
+
+/**
+ * nautilus_file_get_owner_as_string:
+ *
+ * Get a user-displayable string representing a file's owner. The caller
+ * is responsible for g_free-ing this string.
+ * @file: NautilusFile representing the file in question.
+ * @include_real_name: Whether or not to append the real name (if any)
+ * for this user after the user name.
+ *
+ * Returns: Newly allocated string ready to display to the user.
+ *
+ **/
+static char *
+nautilus_file_get_owner_as_string (NautilusFile *file,
+ gboolean include_real_name)
+{
+ char *user_name;
+
+ /* Before we have info on a file, the owner is unknown. */
+ if (file->details->owner == NULL &&
+ file->details->owner_real == NULL)
+ {
+ return NULL;
+ }
+
+ if (include_real_name &&
+ file->details->uid == getuid ())
+ {
+ /* Translators: This is a username followed by "(You)" to indicate the file is owned by the current user */
+ user_name = g_strdup_printf (_("%s (You)"), file->details->owner);
+ }
+ else if (file->details->owner_real == NULL)
+ {
+ user_name = g_strdup (file->details->owner);
+ }
+ else if (file->details->owner == NULL)
+ {
+ user_name = g_strdup (file->details->owner_real);
+ }
+ else if (include_real_name &&
+ strcmp (file->details->owner, file->details->owner_real) != 0)
+ {
+ user_name = g_strdup (file->details->owner_real);
+ }
+ else
+ {
+ user_name = g_strdup (file->details->owner);
+ }
+
+ return user_name;
+}
+
+static char *
+format_item_count_for_display (guint item_count,
+ gboolean includes_directories,
+ gboolean includes_files)
+{
+ g_assert (includes_directories || includes_files);
+
+ return g_strdup_printf (includes_directories
+ ? (includes_files
+ ? ngettext ("%'u item", "%'u items", item_count)
+ : ngettext ("%'u folder", "%'u folders", item_count))
+ : ngettext ("%'u file", "%'u files", item_count), item_count);
+}
+
+/**
+ * nautilus_file_get_size_as_string:
+ *
+ * Get a user-displayable string representing a file size. The caller
+ * is responsible for g_free-ing this string. The string is an item
+ * count for directories.
+ * @file: NautilusFile representing the file in question.
+ *
+ * Returns: Newly allocated string ready to display to the user.
+ *
+ **/
+static char *
+nautilus_file_get_size_as_string (NautilusFile *file)
+{
+ guint item_count;
+ gboolean count_unreadable;
+
+ if (file == NULL)
+ {
+ return NULL;
+ }
+
+ g_assert (NAUTILUS_IS_FILE (file));
+
+ if (nautilus_file_is_directory (file))
+ {
+ if (!nautilus_file_get_directory_item_count (file, &item_count, &count_unreadable))
+ {
+ return NULL;
+ }
+ return format_item_count_for_display (item_count, TRUE, TRUE);
+ }
+
+ if (file->details->size == -1)
+ {
+ return NULL;
+ }
+ return g_format_size (file->details->size);
+}
+
+/**
+ * nautilus_file_get_size_as_string_with_real_size:
+ *
+ * Get a user-displayable string representing a file size. The caller
+ * is responsible for g_free-ing this string. The string is an item
+ * count for directories.
+ * This function adds the real size in the string.
+ * @file: NautilusFile representing the file in question.
+ *
+ * Returns: Newly allocated string ready to display to the user.
+ *
+ **/
+static char *
+nautilus_file_get_size_as_string_with_real_size (NautilusFile *file)
+{
+ guint item_count;
+ gboolean count_unreadable;
+ g_autofree char *size_str = NULL;
+
+ if (file == NULL)
+ {
+ return NULL;
+ }
+
+ g_assert (NAUTILUS_IS_FILE (file));
+
+ if (nautilus_file_is_directory (file))
+ {
+ if (!nautilus_file_get_directory_item_count (file, &item_count, &count_unreadable))
+ {
+ return NULL;
+ }
+ return format_item_count_for_display (item_count, TRUE, TRUE);
+ }
+
+ if (file->details->size == -1)
+ {
+ return NULL;
+ }
+
+ size_str = g_strdup_printf ("%'" G_GOFFSET_FORMAT, file->details->size);
+ return g_strdup_printf (ngettext ("%s byte",
+ "%s bytes",
+ file->details->size),
+ size_str);
+}
+
+
+static char *
+nautilus_file_get_deep_count_as_string_internal (NautilusFile *file,
+ gboolean report_size,
+ gboolean report_directory_count,
+ gboolean report_file_count)
+{
+ NautilusRequestStatus status;
+ guint directory_count;
+ guint file_count;
+ guint unreadable_count;
+ guint total_count;
+ goffset total_size;
+
+ /* Must ask for size or some kind of count, but not both. */
+ g_assert (!report_size || (!report_directory_count && !report_file_count));
+ g_assert (report_size || report_directory_count || report_file_count);
+
+ if (file == NULL)
+ {
+ return NULL;
+ }
+
+ g_assert (NAUTILUS_IS_FILE (file));
+ g_assert (nautilus_file_is_directory (file));
+
+ status = nautilus_file_get_deep_counts
+ (file, &directory_count, &file_count, &unreadable_count, &total_size, FALSE);
+
+ /* Check whether any info is available. */
+ if (status == NAUTILUS_REQUEST_NOT_STARTED)
+ {
+ return NULL;
+ }
+
+ total_count = file_count + directory_count;
+
+ if (total_count == 0)
+ {
+ switch (status)
+ {
+ case NAUTILUS_REQUEST_IN_PROGRESS:
+ {
+ /* Don't return confident "zero" until we're finished looking,
+ * because of next case.
+ */
+ return NULL;
+ }
+
+ case NAUTILUS_REQUEST_DONE:
+ {
+ /* Don't return "zero" if we there were contents but we couldn't read them. */
+ if (unreadable_count != 0)
+ {
+ return NULL;
+ }
+ }
+
+ default:
+ {}
+ break;
+ }
+ }
+
+ /* Note that we don't distinguish the "everything was readable" case
+ * from the "some things but not everything was readable" case here.
+ * Callers can distinguish them using nautilus_file_get_deep_counts
+ * directly if desired.
+ */
+ if (report_size)
+ {
+ return g_format_size (total_size);
+ }
+
+ return format_item_count_for_display (report_directory_count
+ ? (report_file_count ? total_count : directory_count)
+ : file_count,
+ report_directory_count, report_file_count);
+}
+
+/**
+ * nautilus_file_get_deep_size_as_string:
+ *
+ * Get a user-displayable string representing the size of all contained
+ * items (only makes sense for directories). The caller
+ * is responsible for g_free-ing this string.
+ * @file: NautilusFile representing the file in question.
+ *
+ * Returns: Newly allocated string ready to display to the user.
+ *
+ **/
+static char *
+nautilus_file_get_deep_size_as_string (NautilusFile *file)
+{
+ return nautilus_file_get_deep_count_as_string_internal (file, TRUE, FALSE, FALSE);
+}
+
+/**
+ * nautilus_file_get_deep_total_count_as_string:
+ *
+ * Get a user-displayable string representing the count of all contained
+ * items (only makes sense for directories). The caller
+ * is responsible for g_free-ing this string.
+ * @file: NautilusFile representing the file in question.
+ *
+ * Returns: Newly allocated string ready to display to the user.
+ *
+ **/
+static char *
+nautilus_file_get_deep_total_count_as_string (NautilusFile *file)
+{
+ return nautilus_file_get_deep_count_as_string_internal (file, FALSE, TRUE, TRUE);
+}
+
+/**
+ * nautilus_file_get_deep_file_count_as_string:
+ *
+ * Get a user-displayable string representing the count of all contained
+ * items, not including directories. It only makes sense to call this
+ * function on a directory. The caller
+ * is responsible for g_free-ing this string.
+ * @file: NautilusFile representing the file in question.
+ *
+ * Returns: Newly allocated string ready to display to the user.
+ *
+ **/
+static char *
+nautilus_file_get_deep_file_count_as_string (NautilusFile *file)
+{
+ return nautilus_file_get_deep_count_as_string_internal (file, FALSE, FALSE, TRUE);
+}
+
+/**
+ * nautilus_file_get_deep_directory_count_as_string:
+ *
+ * Get a user-displayable string representing the count of all contained
+ * directories. It only makes sense to call this
+ * function on a directory. The caller
+ * is responsible for g_free-ing this string.
+ * @file: NautilusFile representing the file in question.
+ *
+ * Returns: Newly allocated string ready to display to the user.
+ *
+ **/
+static char *
+nautilus_file_get_deep_directory_count_as_string (NautilusFile *file)
+{
+ return nautilus_file_get_deep_count_as_string_internal (file, FALSE, TRUE, FALSE);
+}
+
+/**
+ * nautilus_file_get_string_attribute:
+ *
+ * Get a user-displayable string from a named attribute. Use g_free to
+ * free this string. If the value is unknown, returns NULL. You can call
+ * nautilus_file_get_string_attribute_with_default if you want a non-NULL
+ * default.
+ *
+ * @file: NautilusFile representing the file in question.
+ * @attribute_name: The name of the desired attribute. The currently supported
+ * set includes "name", "type", "detailed_type", "mime_type", "size", "deep_size", "deep_directory_count",
+ * "deep_file_count", "deep_total_count", "date_modified", "date_accessed", "date_created",
+ * "date_modified_full", "date_accessed_full", "date_created_full",
+ * "owner", "group", "permissions", "octal_permissions", "uri", "where",
+ * "link_target", "volume", "free_space", "selinux_context", "trashed_on", "trashed_on_full", "trashed_orig_path",
+ * "recency"
+ *
+ * Returns: Newly allocated string ready to display to the user, or NULL
+ * if the value is unknown or @attribute_name is not supported.
+ *
+ **/
+char *
+nautilus_file_get_string_attribute_q (NautilusFile *file,
+ GQuark attribute_q)
+{
+ char *extension_attribute;
+
+ if (attribute_q == attribute_name_q)
+ {
+ return nautilus_file_get_display_name (file);
+ }
+ if (attribute_q == attribute_type_q)
+ {
+ return nautilus_file_get_type_as_string (file);
+ }
+ if (attribute_q == attribute_detailed_type_q)
+ {
+ return nautilus_file_get_detailed_type_as_string (file);
+ }
+ if (attribute_q == attribute_mime_type_q)
+ {
+ return nautilus_file_get_mime_type (file);
+ }
+ if (attribute_q == attribute_size_q)
+ {
+ return nautilus_file_get_size_as_string (file);
+ }
+ if (attribute_q == attribute_size_detail_q)
+ {
+ return nautilus_file_get_size_as_string_with_real_size (file);
+ }
+ if (attribute_q == attribute_deep_size_q)
+ {
+ return nautilus_file_get_deep_size_as_string (file);
+ }
+ if (attribute_q == attribute_deep_file_count_q)
+ {
+ return nautilus_file_get_deep_file_count_as_string (file);
+ }
+ if (attribute_q == attribute_deep_directory_count_q)
+ {
+ return nautilus_file_get_deep_directory_count_as_string (file);
+ }
+ if (attribute_q == attribute_deep_total_count_q)
+ {
+ return nautilus_file_get_deep_total_count_as_string (file);
+ }
+ if (attribute_q == attribute_trash_orig_path_q)
+ {
+ return nautilus_file_get_trash_original_file_parent_as_string (file);
+ }
+ if (attribute_q == attribute_date_modified_q)
+ {
+ return nautilus_file_get_date_as_string (file,
+ NAUTILUS_DATE_TYPE_MODIFIED,
+ NAUTILUS_DATE_FORMAT_REGULAR);
+ }
+ if (attribute_q == attribute_date_modified_full_q)
+ {
+ return nautilus_file_get_date_as_string (file,
+ NAUTILUS_DATE_TYPE_MODIFIED,
+ NAUTILUS_DATE_FORMAT_FULL);
+ }
+ if (attribute_q == attribute_date_modified_with_time_q)
+ {
+ return nautilus_file_get_date_as_string (file,
+ NAUTILUS_DATE_TYPE_MODIFIED,
+ NAUTILUS_DATE_FORMAT_REGULAR_WITH_TIME);
+ }
+ if (attribute_q == attribute_date_accessed_q)
+ {
+ return nautilus_file_get_date_as_string (file,
+ NAUTILUS_DATE_TYPE_ACCESSED,
+ NAUTILUS_DATE_FORMAT_REGULAR);
+ }
+ if (attribute_q == attribute_date_accessed_full_q)
+ {
+ return nautilus_file_get_date_as_string (file,
+ NAUTILUS_DATE_TYPE_ACCESSED,
+ NAUTILUS_DATE_FORMAT_FULL);
+ }
+ if (attribute_q == attribute_date_created_q)
+ {
+ return nautilus_file_get_date_as_string (file,
+ NAUTILUS_DATE_TYPE_CREATED,
+ NAUTILUS_DATE_FORMAT_REGULAR);
+ }
+ if (attribute_q == attribute_date_created_full_q)
+ {
+ return nautilus_file_get_date_as_string (file,
+ NAUTILUS_DATE_TYPE_CREATED,
+ NAUTILUS_DATE_FORMAT_FULL);
+ }
+ if (attribute_q == attribute_trashed_on_q)
+ {
+ return nautilus_file_get_date_as_string (file,
+ NAUTILUS_DATE_TYPE_TRASHED,
+ NAUTILUS_DATE_FORMAT_REGULAR);
+ }
+ if (attribute_q == attribute_trashed_on_full_q)
+ {
+ return nautilus_file_get_date_as_string (file,
+ NAUTILUS_DATE_TYPE_TRASHED,
+ NAUTILUS_DATE_FORMAT_FULL);
+ }
+ if (attribute_q == attribute_recency_q)
+ {
+ return nautilus_file_get_date_as_string (file,
+ NAUTILUS_DATE_TYPE_RECENCY,
+ NAUTILUS_DATE_FORMAT_REGULAR);
+ }
+ if (attribute_q == attribute_permissions_q)
+ {
+ return nautilus_file_get_permissions_as_string (file);
+ }
+ if (attribute_q == attribute_selinux_context_q)
+ {
+ return nautilus_file_get_selinux_context (file);
+ }
+ if (attribute_q == attribute_octal_permissions_q)
+ {
+ return nautilus_file_get_octal_permissions_as_string (file);
+ }
+ if (attribute_q == attribute_owner_q)
+ {
+ return nautilus_file_get_owner_as_string (file, TRUE);
+ }
+ if (attribute_q == attribute_group_q)
+ {
+ return nautilus_file_get_group_name (file);
+ }
+ if (attribute_q == attribute_uri_q)
+ {
+ return nautilus_file_get_uri (file);
+ }
+ if (attribute_q == attribute_where_q)
+ {
+ return nautilus_file_get_where_string (file);
+ }
+ if (attribute_q == attribute_link_target_q)
+ {
+ return nautilus_file_get_symbolic_link_target_path (file);
+ }
+ if (attribute_q == attribute_volume_q)
+ {
+ return nautilus_file_get_volume_name (file);
+ }
+ if (attribute_q == attribute_free_space_q)
+ {
+ return nautilus_file_get_volume_free_space (file);
+ }
+
+ extension_attribute = NULL;
+
+ if (file->details->pending_extension_attributes)
+ {
+ extension_attribute = g_hash_table_lookup (file->details->pending_extension_attributes,
+ GINT_TO_POINTER (attribute_q));
+ }
+
+ if (extension_attribute == NULL && file->details->extension_attributes)
+ {
+ extension_attribute = g_hash_table_lookup (file->details->extension_attributes,
+ GINT_TO_POINTER (attribute_q));
+ }
+
+ return g_strdup (extension_attribute);
+}
+
+char *
+nautilus_file_get_string_attribute (NautilusFile *file,
+ const char *attribute_name)
+{
+ g_return_val_if_fail (NAUTILUS_IS_FILE (file), NULL);
+
+ return nautilus_file_info_get_string_attribute (NAUTILUS_FILE_INFO (file), attribute_name);
+}
+
+
+/**
+ * nautilus_file_get_string_attribute_with_default:
+ *
+ * Get a user-displayable string from a named attribute. Use g_free to
+ * free this string. If the value is unknown, returns a string representing
+ * the unknown value, which varies with attribute. You can call
+ * nautilus_file_get_string_attribute if you want NULL instead of a default
+ * result.
+ *
+ * @file: NautilusFile representing the file in question.
+ * @attribute_name: The name of the desired attribute. See the description of
+ * nautilus_file_get_string for the set of available attributes.
+ *
+ * Returns: Newly allocated string ready to display to the user, or a string
+ * such as "unknown" if the value is unknown or @attribute_name is not supported.
+ *
+ **/
+char *
+nautilus_file_get_string_attribute_with_default_q (NautilusFile *file,
+ GQuark attribute_q)
+{
+ char *result;
+ guint item_count;
+ gboolean count_unreadable;
+ NautilusRequestStatus status;
+
+ result = nautilus_file_get_string_attribute_q (file, attribute_q);
+ if (result != NULL)
+ {
+ return result;
+ }
+
+ /* Supply default values for the ones we know about. */
+ /* FIXME bugzilla.gnome.org 40646:
+ * Use hash table and switch statement or function pointers for speed?
+ */
+ if (attribute_q == attribute_size_q)
+ {
+ if (!nautilus_file_should_show_directory_item_count (file))
+ {
+ return g_strdup ("—");
+ }
+ count_unreadable = FALSE;
+ if (nautilus_file_is_directory (file))
+ {
+ nautilus_file_get_directory_item_count (file, &item_count, &count_unreadable);
+ }
+ return g_strdup (count_unreadable ? "—" : "…");
+ }
+ if (attribute_q == attribute_deep_size_q)
+ {
+ status = nautilus_file_get_deep_counts (file, NULL, NULL, NULL, NULL, FALSE);
+ if (status == NAUTILUS_REQUEST_DONE)
+ {
+ /* This means no contents at all were readable */
+ return g_strdup (_("? bytes"));
+ }
+ return g_strdup ("…");
+ }
+ if (attribute_q == attribute_deep_file_count_q
+ || attribute_q == attribute_deep_directory_count_q
+ || attribute_q == attribute_deep_total_count_q)
+ {
+ status = nautilus_file_get_deep_counts (file, NULL, NULL, NULL, NULL, FALSE);
+ if (status == NAUTILUS_REQUEST_DONE)
+ {
+ /* This means no contents at all were readable */
+ return g_strdup (_("? items"));
+ }
+ return g_strdup ("…");
+ }
+ if (attribute_q == attribute_type_q
+ || attribute_q == attribute_detailed_type_q
+ || attribute_q == attribute_mime_type_q)
+ {
+ /* Translators: This about a file type. */
+ return g_strdup (_("Unknown type"));
+ }
+ if (attribute_q == attribute_trashed_on_q)
+ {
+ /* If n/a */
+ return g_strdup ("");
+ }
+ if (attribute_q == attribute_trash_orig_path_q)
+ {
+ /* If n/a */
+ return g_strdup ("");
+ }
+ if (attribute_q == attribute_recency_q)
+ {
+ /* If n/a */
+ return g_strdup ("");
+ }
+ if (attribute_q == attribute_starred_q)
+ {
+ /* If n/a */
+ return g_strdup ("");
+ }
+ if (attribute_q == attribute_date_created_full_q)
+ {
+ /* If n/a */
+ return g_strdup ("—");
+ }
+
+ /* Fallback, use for both unknown attributes and attributes
+ * for which we have no more appropriate default.
+ */
+ return g_strdup (_("Unknown"));
+}
+
+char *
+nautilus_file_get_string_attribute_with_default (NautilusFile *file,
+ const char *attribute_name)
+{
+ return nautilus_file_get_string_attribute_with_default_q (file, g_quark_from_string (attribute_name));
+}
+
+gboolean
+nautilus_file_is_date_sort_attribute_q (GQuark attribute_q)
+{
+ if (attribute_q == attribute_modification_date_q ||
+ attribute_q == attribute_date_modified_q ||
+ attribute_q == attribute_date_modified_full_q ||
+ attribute_q == attribute_date_modified_with_time_q ||
+ attribute_q == attribute_accessed_date_q ||
+ attribute_q == attribute_date_accessed_q ||
+ attribute_q == attribute_date_accessed_full_q ||
+ attribute_q == attribute_date_created_q ||
+ attribute_q == attribute_date_created_full_q ||
+ attribute_q == attribute_trashed_on_q ||
+ attribute_q == attribute_trashed_on_full_q ||
+ attribute_q == attribute_recency_q)
+ {
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+struct
+{
+ const char *icon_name;
+ const char *display_name;
+} mime_type_map[] =
+{
+ { "application-x-executable", N_("Program") },
+ { "audio-x-generic", N_("Audio") },
+ { "font-x-generic", N_("Font") },
+ { "image-x-generic", N_("Image") },
+ { "package-x-generic", N_("Archive") },
+ { "text-html", N_("Markup") },
+ { "text-x-generic", N_("Text") },
+ { "text-x-generic-template", N_("Text") },
+ { "text-x-script", N_("Program") },
+ { "video-x-generic", N_("Video") },
+ { "x-office-address-book", N_("Contacts") },
+ { "x-office-calendar", N_("Calendar") },
+ { "x-office-document", N_("Document") },
+ { "x-office-presentation", N_("Presentation") },
+ { "x-office-spreadsheet", N_("Spreadsheet") },
+};
+
+static char *
+get_basic_type_for_mime_type (const char *mime_type)
+{
+ char *icon_name;
+ char *basic_type = NULL;
+
+ icon_name = g_content_type_get_generic_icon_name (mime_type);
+ if (icon_name != NULL)
+ {
+ int i;
+
+ for (i = 0; i < G_N_ELEMENTS (mime_type_map); i++)
+ {
+ if (strcmp (mime_type_map[i].icon_name, icon_name) == 0)
+ {
+ basic_type = g_strdup (gettext (mime_type_map[i].display_name));
+ break;
+ }
+ }
+ }
+
+ if (basic_type == NULL)
+ {
+ /* Refers to a file type which is known but not one of the basic types */
+ basic_type = g_strdup (_("Other"));
+ }
+
+ g_free (icon_name);
+
+ return basic_type;
+}
+
+static char *
+get_description (NautilusFile *file,
+ gboolean detailed)
+{
+ const char *mime_type;
+
+ g_assert (NAUTILUS_IS_FILE (file));
+
+ mime_type = file->details->mime_type;
+ if (mime_type == NULL)
+ {
+ return NULL;
+ }
+
+ if (g_content_type_is_unknown (mime_type))
+ {
+ if (nautilus_file_is_executable (file))
+ {
+ return g_strdup (_("Program"));
+ }
+ return g_strdup (_("Binary"));
+ }
+
+ if (strcmp (mime_type, "inode/directory") == 0)
+ {
+ return g_strdup (_("Folder"));
+ }
+
+ if (detailed)
+ {
+ char *description;
+
+ description = g_content_type_get_description (mime_type);
+ if (description != NULL)
+ {
+ return description;
+ }
+ }
+ else
+ {
+ char *category;
+
+ category = get_basic_type_for_mime_type (mime_type);
+ if (category != NULL)
+ {
+ return category;
+ }
+ }
+
+ return g_strdup (mime_type);
+}
+
+/* Takes ownership of string */
+static char *
+update_description_for_link (NautilusFile *file,
+ char *string)
+{
+ char *res;
+
+ if (nautilus_file_is_symbolic_link (file))
+ {
+ g_assert (!nautilus_file_is_broken_symbolic_link (file));
+ if (string == NULL)
+ {
+ return g_strdup (_("Link"));
+ }
+ /* Note to localizers: convert file type string for file
+ * (e.g. "folder", "plain text") to file type for symbolic link
+ * to that kind of file (e.g. "link to folder").
+ */
+ res = g_strdup_printf (_("Link to %s"), string);
+ g_free (string);
+ return res;
+ }
+
+ return string;
+}
+
+static char *
+nautilus_file_get_type_as_string (NautilusFile *file)
+{
+ if (file == NULL)
+ {
+ return NULL;
+ }
+
+ if (nautilus_file_is_broken_symbolic_link (file))
+ {
+ return g_strdup (_("Link (broken)"));
+ }
+
+ return update_description_for_link (file, get_description (file, FALSE));
+}
+
+static char *
+nautilus_file_get_type_as_string_no_extra_text (NautilusFile *file)
+{
+ if (file == NULL)
+ {
+ return NULL;
+ }
+
+ if (nautilus_file_is_broken_symbolic_link (file))
+ {
+ return g_strdup (_("Link (broken)"));
+ }
+
+ return get_description (file, FALSE);
+}
+
+static char *
+nautilus_file_get_detailed_type_as_string (NautilusFile *file)
+{
+ if (file == NULL)
+ {
+ return NULL;
+ }
+
+ if (nautilus_file_is_broken_symbolic_link (file))
+ {
+ return g_strdup (_("Link (broken)"));
+ }
+
+ return update_description_for_link (file, get_description (file, TRUE));
+}
+
+/**
+ * nautilus_file_get_file_type
+ *
+ * Return this file's type.
+ * @file: NautilusFile representing the file in question.
+ *
+ * Returns: The type.
+ *
+ **/
+GFileType
+nautilus_file_get_file_type (NautilusFile *file)
+{
+ g_return_val_if_fail (NAUTILUS_IS_FILE (file), G_FILE_TYPE_UNKNOWN);
+
+ return nautilus_file_info_get_file_type (NAUTILUS_FILE_INFO (file));
+}
+
+/**
+ * nautilus_file_get_mime_type
+ *
+ * Return this file's default mime type.
+ * @file: NautilusFile representing the file in question.
+ *
+ * Returns: The mime type.
+ *
+ **/
+char *
+nautilus_file_get_mime_type (NautilusFile *file)
+{
+ g_return_val_if_fail (NAUTILUS_IS_FILE (file), NULL);
+
+ return nautilus_file_info_get_mime_type (NAUTILUS_FILE_INFO (file));
+}
+
+/**
+ * nautilus_file_is_mime_type
+ *
+ * Check whether a file is of a particular MIME type, or inherited
+ * from it.
+ * @file: NautilusFile representing the file in question.
+ * @mime_type: The MIME-type string to test (e.g. "text/plain")
+ *
+ * Return value: TRUE if @mime_type exactly matches the
+ * file's MIME type.
+ *
+ **/
+gboolean
+nautilus_file_is_mime_type (NautilusFile *file,
+ const char *mime_type)
+{
+ g_return_val_if_fail (NAUTILUS_IS_FILE (file), FALSE);
+ g_return_val_if_fail (mime_type != NULL, FALSE);
+
+ return nautilus_file_info_is_mime_type (NAUTILUS_FILE_INFO (file), mime_type);
+}
+
+char *
+nautilus_file_get_extension (NautilusFile *file)
+{
+ char *name;
+ char *extension = NULL;
+
+ name = nautilus_file_get_name (file);
+ if (name != NULL)
+ {
+ extension = g_strdup (eel_filename_get_extension_offset (name));
+ g_free (name);
+ }
+
+ return extension;
+}
+
+gboolean
+nautilus_file_is_launchable (NautilusFile *file)
+{
+ gboolean type_can_be_executable;
+
+ type_can_be_executable = FALSE;
+ if (file->details->mime_type != NULL)
+ {
+ type_can_be_executable =
+ g_content_type_can_be_executable (file->details->mime_type);
+ }
+
+ return type_can_be_executable &&
+ nautilus_file_can_get_permissions (file) &&
+ nautilus_file_can_execute (file) &&
+ nautilus_file_is_executable (file) &&
+ nautilus_file_is_regular_file (file);
+}
+
+/**
+ * nautilus_file_is_symbolic_link
+ *
+ * Check if this file is a symbolic link.
+ * @file: NautilusFile representing the file in question.
+ *
+ * Returns: True if the file is a symbolic link.
+ *
+ **/
+gboolean
+nautilus_file_is_symbolic_link (NautilusFile *file)
+{
+ return file->details->is_symlink;
+}
+
+GMount *
+nautilus_file_get_mount (NautilusFile *file)
+{
+ g_return_val_if_fail (NAUTILUS_IS_FILE (file), NULL);
+
+ return nautilus_file_info_get_mount (NAUTILUS_FILE_INFO (file));
+}
+
+static void
+file_mount_unmounted (GMount *mount,
+ gpointer data)
+{
+ NautilusFile *file;
+
+ file = NAUTILUS_FILE (data);
+
+ nautilus_file_invalidate_attributes (file, NAUTILUS_FILE_ATTRIBUTE_MOUNT);
+}
+
+void
+nautilus_file_set_mount (NautilusFile *file,
+ GMount *mount)
+{
+ if (file->details->mount)
+ {
+ g_signal_handlers_disconnect_by_func (file->details->mount, file_mount_unmounted, file);
+ g_object_unref (file->details->mount);
+ file->details->mount = NULL;
+ }
+
+ if (mount)
+ {
+ file->details->mount = g_object_ref (mount);
+ g_signal_connect (mount, "unmounted",
+ G_CALLBACK (file_mount_unmounted), file);
+ }
+}
+
+/**
+ * nautilus_file_is_broken_symbolic_link
+ *
+ * Check if this file is a symbolic link with a missing target.
+ * @file: NautilusFile representing the file in question.
+ *
+ * Returns: True if the file is a symbolic link with a missing target.
+ *
+ **/
+gboolean
+nautilus_file_is_broken_symbolic_link (NautilusFile *file)
+{
+ if (file == NULL)
+ {
+ return FALSE;
+ }
+
+ g_return_val_if_fail (NAUTILUS_IS_FILE (file), FALSE);
+
+ /* Non-broken symbolic links return the target's type for get_file_type. */
+ return nautilus_file_get_file_type (file) == G_FILE_TYPE_SYMBOLIC_LINK;
+}
+
+static void
+get_fs_free_cb (GObject *source_object,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ NautilusFile *file;
+ guint64 free_space;
+ GFileInfo *info;
+
+ file = NAUTILUS_FILE (user_data);
+
+ free_space = (guint64) - 1;
+ info = g_file_query_filesystem_info_finish (G_FILE (source_object),
+ res, NULL);
+ if (info)
+ {
+ if (g_file_info_has_attribute (info, G_FILE_ATTRIBUTE_FILESYSTEM_FREE))
+ {
+ free_space = g_file_info_get_attribute_uint64 (info, G_FILE_ATTRIBUTE_FILESYSTEM_FREE);
+ }
+ g_object_unref (info);
+ }
+
+ if (file->details->free_space != free_space)
+ {
+ file->details->free_space = free_space;
+ nautilus_file_emit_changed (file);
+ }
+
+ nautilus_file_unref (file);
+}
+
+/**
+ * nautilus_file_get_volume_free_space
+ * Get a nicely formatted char with free space on the file's volume
+ * @file: NautilusFile representing the file in question.
+ *
+ * Returns: newly-allocated copy of file size in a formatted string
+ */
+char *
+nautilus_file_get_volume_free_space (NautilusFile *file)
+{
+ GFile *location;
+ char *res;
+ time_t now;
+
+ now = time (NULL);
+ /* Update first time and then every 2 seconds */
+ if (file->details->free_space_read == 0 ||
+ (now - file->details->free_space_read) > 2)
+ {
+ file->details->free_space_read = now;
+ location = nautilus_file_get_location (file);
+ g_file_query_filesystem_info_async (location,
+ G_FILE_ATTRIBUTE_FILESYSTEM_FREE,
+ 0, NULL,
+ get_fs_free_cb,
+ nautilus_file_ref (file));
+ g_object_unref (location);
+ }
+
+ res = NULL;
+ if (file->details->free_space != (guint64) - 1)
+ {
+ g_autofree gchar *size_string = g_format_size (file->details->free_space);
+
+ /* Translators: This refers to available space in a folder; e.g.: 100 MB Free */
+ res = g_strdup_printf (_("%s Free"), size_string);
+ }
+
+ return res;
+}
+
+/**
+ * nautilus_file_get_volume_name
+ * Get the path of the volume the file resides on
+ * @file: NautilusFile representing the file in question.
+ *
+ * Returns: newly-allocated copy of the volume name of the target file,
+ * if the volume name isn't set, it returns the mount path of the volume
+ */
+char *
+nautilus_file_get_volume_name (NautilusFile *file)
+{
+ GFile *location;
+ char *res;
+ GMount *mount;
+
+ res = NULL;
+
+ location = nautilus_file_get_location (file);
+ mount = g_file_find_enclosing_mount (location, NULL, NULL);
+ if (mount)
+ {
+ res = g_strdup (g_mount_get_name (mount));
+ g_object_unref (mount);
+ }
+ g_object_unref (location);
+
+ return res;
+}
+
+/**
+ * nautilus_file_get_symbolic_link_target_path
+ *
+ * Get the file path of the target of a symbolic link. It is an error
+ * to call this function on a file that isn't a symbolic link.
+ * @file: NautilusFile representing the symbolic link in question.
+ *
+ * Returns: newly-allocated copy of the file path of the target of the symbolic link.
+ */
+char *
+nautilus_file_get_symbolic_link_target_path (NautilusFile *file)
+{
+ if (!nautilus_file_is_symbolic_link (file))
+ {
+ g_warning ("File has symlink target, but is not marked as symlink");
+ }
+
+ return g_strdup (file->details->symlink_name);
+}
+
+/**
+ * nautilus_file_get_symbolic_link_target_uri
+ *
+ * Get the uri of the target of a symbolic link. It is an error
+ * to call this function on a file that isn't a symbolic link.
+ * @file: NautilusFile representing the symbolic link in question.
+ *
+ * Returns: newly-allocated copy of the uri of the target of the symbolic link.
+ */
+char *
+nautilus_file_get_symbolic_link_target_uri (NautilusFile *file)
+{
+ GFile *location, *parent, *target;
+ char *target_uri;
+
+ if (!nautilus_file_is_symbolic_link (file))
+ {
+ g_warning ("File has symlink target, but is not marked as symlink");
+ }
+
+ if (file->details->symlink_name == NULL)
+ {
+ return NULL;
+ }
+ else
+ {
+ target = NULL;
+
+ location = nautilus_file_get_location (file);
+ parent = g_file_get_parent (location);
+ g_object_unref (location);
+ if (parent)
+ {
+ target = g_file_resolve_relative_path (parent, file->details->symlink_name);
+ g_object_unref (parent);
+ }
+
+ target_uri = NULL;
+ if (target)
+ {
+ target_uri = g_file_get_uri (target);
+ g_object_unref (target);
+ }
+ return target_uri;
+ }
+}
+
+/**
+ * nautilus_file_is_regular_file
+ *
+ * Check if this file is a regular file.
+ * @file: NautilusFile representing the file in question.
+ *
+ * Returns: TRUE if @file is a regular file.
+ *
+ **/
+gboolean
+nautilus_file_is_regular_file (NautilusFile *file)
+{
+ return nautilus_file_get_file_type (file) == G_FILE_TYPE_REGULAR;
+}
+
+/**
+ * nautilus_file_is_directory
+ *
+ * Check if this file is a directory.
+ * @file: NautilusFile representing the file in question.
+ *
+ * Returns: TRUE if @file is a directory.
+ *
+ **/
+gboolean
+nautilus_file_is_directory (NautilusFile *file)
+{
+ g_return_val_if_fail (NAUTILUS_IS_FILE (file), FALSE);
+
+ return nautilus_file_info_is_directory (NAUTILUS_FILE_INFO (file));
+}
+
+/**
+ * nautilus_file_is_user_special_directory
+ *
+ * Check if this file is a special platform directory.
+ * @file: NautilusFile representing the file in question.
+ * @special_directory: GUserDirectory representing the type to test for
+ *
+ * Returns: TRUE if @file is a special directory of the given kind.
+ */
+gboolean
+nautilus_file_is_user_special_directory (NautilusFile *file,
+ GUserDirectory special_directory)
+{
+ gboolean is_special_dir;
+ const gchar *special_dir;
+
+ if (nautilus_file_is_home (file))
+ {
+ /* A xdg-user-dir is disabled by setting it to the home directory */
+ return FALSE;
+ }
+
+ special_dir = g_get_user_special_dir (special_directory);
+ is_special_dir = FALSE;
+
+ if (special_dir)
+ {
+ GFile *loc;
+ GFile *special_gfile;
+
+ loc = nautilus_file_get_location (file);
+ special_gfile = g_file_new_for_path (special_dir);
+ is_special_dir = g_file_equal (loc, special_gfile);
+ g_object_unref (special_gfile);
+ g_object_unref (loc);
+ }
+
+ return is_special_dir;
+}
+
+gboolean
+nautilus_file_is_archive (NautilusFile *file)
+{
+ g_autofree char *mime_type = NULL;
+
+ mime_type = nautilus_file_get_mime_type (file);
+
+ return autoar_check_mime_type_supported (mime_type);
+}
+
+
+/**
+ * nautilus_file_is_in_trash
+ *
+ * Check if this file is a file in trash.
+ * @file: NautilusFile representing the file in question.
+ *
+ * Returns: TRUE if @file is in a trash.
+ *
+ **/
+gboolean
+nautilus_file_is_in_trash (NautilusFile *file)
+{
+ g_assert (NAUTILUS_IS_FILE (file));
+
+ return nautilus_directory_is_in_trash (file->details->directory);
+}
+
+/**
+ * nautilus_file_is_in_recent
+ *
+ * Check if this file is a file in Recent.
+ * @file: NautilusFile representing the file in question.
+ *
+ * Returns: TRUE if @file is in Recent.
+ *
+ **/
+gboolean
+nautilus_file_is_in_recent (NautilusFile *file)
+{
+ g_assert (NAUTILUS_IS_FILE (file));
+
+ return nautilus_directory_is_in_recent (file->details->directory);
+}
+
+/**
+ * nautilus_file_is_in_starred
+ *
+ * Check if this file is a file in Starred.
+ * @file: NautilusFile representing the file in question.
+ *
+ * Returns: TRUE if @file is in Starred.
+ *
+ **/
+gboolean
+nautilus_file_is_in_starred (NautilusFile *file)
+{
+ g_assert (NAUTILUS_IS_FILE (file));
+
+ return nautilus_directory_is_in_starred (file->details->directory);
+}
+
+/**
+ * nautilus_file_is_remote
+ *
+ * Check if this file is a file in a remote filesystem.
+ * @file: NautilusFile representing the file in question.
+ *
+ * Returns: TRUE if @file is in a remote filesystem.
+ *
+ **/
+gboolean
+nautilus_file_is_remote (NautilusFile *file)
+{
+ g_return_val_if_fail (NAUTILUS_IS_FILE (file), FALSE);
+
+ return nautilus_file_get_filesystem_remote (file);
+}
+
+/**
+ * nautilus_file_is_other_locations
+ *
+ * Check if this file is Other Locations.
+ * @file: NautilusFile representing the file in question.
+ *
+ * Returns: TRUE if @file is Other Locations.
+ *
+ **/
+gboolean
+nautilus_file_is_other_locations (NautilusFile *file)
+{
+ gboolean is_other_locations;
+ gchar *uri;
+
+ g_return_val_if_fail (NAUTILUS_IS_FILE (file), FALSE);
+
+ uri = nautilus_file_get_uri (file);
+ is_other_locations = g_strcmp0 (uri, "other-locations:///") == 0;
+
+ g_free (uri);
+
+ return is_other_locations;
+}
+
+/**
+ * nautilus_file_is_starred_location
+ *
+ * Check if this file is the Favorite location.
+ * @file: NautilusFile representing the file in question.
+ *
+ * Returns: TRUE if @file is the Favorite location.
+ *
+ **/
+gboolean
+nautilus_file_is_starred_location (NautilusFile *file)
+{
+ g_autofree gchar *uri = NULL;
+
+ g_return_val_if_fail (NAUTILUS_IS_FILE (file), FALSE);
+
+ uri = nautilus_file_get_uri (file);
+
+ return eel_uri_is_starred (uri);
+}
+
+/**
+ * nautilus_file_is_in_admin
+ *
+ * Check if this file is using admin backend.
+ * @file: NautilusFile representing the file in question.
+ *
+ * Returns: TRUE if @file is using admin backend.
+ *
+ **/
+gboolean
+nautilus_file_is_in_admin (NautilusFile *file)
+{
+ g_return_val_if_fail (NAUTILUS_IS_FILE (file), FALSE);
+
+ return nautilus_directory_is_in_admin (file->details->directory);
+}
+
+GError *
+nautilus_file_get_file_info_error (NautilusFile *file)
+{
+ if (!file->details->get_info_failed)
+ {
+ return NULL;
+ }
+
+ return file->details->get_info_error;
+}
+
+/**
+ * nautilus_file_contains_text
+ *
+ * Check if this file contains text.
+ * This is private and is used to decide whether or not to read the top left text.
+ * @file: NautilusFile representing the file in question.
+ *
+ * Returns: TRUE if @file has a text MIME type.
+ *
+ **/
+gboolean
+nautilus_file_contains_text (NautilusFile *file)
+{
+ if (file == NULL)
+ {
+ return FALSE;
+ }
+
+ /* All text files inherit from text/plain */
+ return nautilus_file_is_mime_type (file, "text/plain");
+}
+
+/**
+ * nautilus_file_is_executable
+ *
+ * Check if this file is executable at all.
+ * @file: NautilusFile representing the file in question.
+ *
+ * Returns: TRUE if any of the execute bits are set. FALSE if
+ * not, or if the permissions are unknown.
+ *
+ **/
+gboolean
+nautilus_file_is_executable (NautilusFile *file)
+{
+ if (!file->details->has_permissions)
+ {
+ /* File's permissions field is not valid.
+ * Can't access specific permissions, so return FALSE.
+ */
+ return FALSE;
+ }
+
+ return file->details->can_execute;
+}
+
+char *
+nautilus_file_get_filesystem_id (NautilusFile *file)
+{
+ return g_strdup (file->details->filesystem_id);
+}
+
+NautilusFile *
+nautilus_file_get_trash_original_file (NautilusFile *file)
+{
+ GFile *location;
+ NautilusFile *original_file;
+
+ original_file = NULL;
+
+ if (file->details->trash_orig_path != NULL)
+ {
+ location = g_file_new_for_path (file->details->trash_orig_path);
+ original_file = nautilus_file_get (location);
+ g_object_unref (location);
+ }
+
+ return original_file;
+}
+
+void
+nautilus_file_mark_gone (NautilusFile *file)
+{
+ NautilusDirectory *directory;
+
+ if (file->details->is_gone)
+ {
+ return;
+ }
+
+ file->details->is_gone = TRUE;
+
+ update_links_if_target (file);
+
+ /* Drop it from the symlink hash ! */
+ remove_from_link_hash_table (file);
+
+ /* Removing the file from the directory can result in dropping the last
+ * reference, and so clearing the info then will result in a crash.
+ */
+ nautilus_file_clear_info (file);
+
+ /* Let the directory know it's gone. */
+ directory = file->details->directory;
+ if (!nautilus_file_is_self_owned (file))
+ {
+ nautilus_directory_remove_file (directory, file);
+ }
+
+ /* FIXME bugzilla.gnome.org 42429:
+ * Maybe we can get rid of the name too eventually, but
+ * for now that would probably require too many if statements
+ * everywhere anyone deals with the name. Maybe we can give it
+ * a hard-coded "<deleted>" name or something.
+ */
+}
+
+/**
+ * nautilus_file_changed
+ *
+ * Notify the user that this file has changed.
+ * @file: NautilusFile representing the file in question.
+ **/
+void
+nautilus_file_changed (NautilusFile *file)
+{
+ GList fake_list;
+
+ g_return_if_fail (NAUTILUS_IS_FILE (file));
+
+ if (nautilus_file_is_self_owned (file))
+ {
+ nautilus_file_emit_changed (file);
+ }
+ else
+ {
+ fake_list.data = file;
+ fake_list.next = NULL;
+ fake_list.prev = NULL;
+ nautilus_directory_emit_change_signals
+ (file->details->directory, &fake_list);
+ }
+}
+
+/**
+ * nautilus_file_updated_deep_count_in_progress
+ *
+ * Notify clients that a newer deep count is available for
+ * the directory in question.
+ */
+void
+nautilus_file_updated_deep_count_in_progress (NautilusFile *file)
+{
+ GList *link_files, *node;
+
+ g_assert (NAUTILUS_IS_FILE (file));
+ g_assert (nautilus_file_is_directory (file));
+
+ /* Send out a signal. */
+ g_signal_emit (file, signals[UPDATED_DEEP_COUNT_IN_PROGRESS], 0, file);
+
+ /* Tell link files pointing to this object about the change. */
+ link_files = get_link_files (file);
+ for (node = link_files; node != NULL; node = node->next)
+ {
+ nautilus_file_updated_deep_count_in_progress (NAUTILUS_FILE (node->data));
+ }
+ nautilus_file_list_free (link_files);
+}
+
+/**
+ * nautilus_file_emit_changed
+ *
+ * Emit a file changed signal.
+ * This can only be called by the directory, since the directory
+ * also has to emit a files_changed signal.
+ *
+ * @file: NautilusFile representing the file in question.
+ **/
+void
+nautilus_file_emit_changed (NautilusFile *file)
+{
+ GList *link_files, *p;
+
+ g_assert (NAUTILUS_IS_FILE (file));
+
+ /* Send out a signal. */
+ g_signal_emit (file, signals[CHANGED], 0, file);
+
+ /* Tell link files pointing to this object about the change. */
+ link_files = get_link_files (file);
+ for (p = link_files; p != NULL; p = p->next)
+ {
+ /* Looking for directly recursive links. */
+ g_autolist (NautilusFile) link_targets = NULL;
+ NautilusDirectory *directory;
+
+ /* Files can be links to themselves. */
+ if (p->data == file)
+ {
+ continue;
+ }
+
+ link_targets = get_link_files (p->data);
+ directory = nautilus_file_get_directory (p->data);
+
+ /* Reiterating (heh) that this will break with more complex cycles.
+ * Users, stop trying to break things on purpose.
+ */
+ if (g_list_find (link_targets, file) != NULL &&
+ directory == nautilus_file_get_directory (file))
+ {
+ g_signal_emit (p->data, signals[CHANGED], 0, p->data);
+ continue;
+ }
+
+ nautilus_file_changed (NAUTILUS_FILE (p->data));
+ }
+ nautilus_file_list_free (link_files);
+}
+
+/**
+ * nautilus_file_is_gone
+ *
+ * Check if a file has already been deleted.
+ * @file: NautilusFile representing the file in question.
+ *
+ * Returns: TRUE if the file is already gone.
+ **/
+gboolean
+nautilus_file_is_gone (NautilusFile *file)
+{
+ g_return_val_if_fail (NAUTILUS_IS_FILE (file), FALSE);
+
+ return nautilus_file_info_is_gone (NAUTILUS_FILE_INFO (file));
+}
+
+/**
+ * nautilus_file_is_not_yet_confirmed
+ *
+ * Check if we're in a state where we don't know if a file really
+ * exists or not, before the initial I/O is complete.
+ * @file: NautilusFile representing the file in question.
+ *
+ * Returns: TRUE if the file is already gone.
+ **/
+gboolean
+nautilus_file_is_not_yet_confirmed (NautilusFile *file)
+{
+ g_return_val_if_fail (NAUTILUS_IS_FILE (file), FALSE);
+
+ return !file->details->got_file_info;
+}
+
+/**
+ * nautilus_file_check_if_ready
+ *
+ * Check whether the values for a set of file attributes are
+ * currently available, without doing any additional work. This
+ * is useful for callers that want to reflect updated information
+ * when it is ready but don't want to force the work required to
+ * obtain the information, which might be slow network calls, e.g.
+ *
+ * @file: The file being queried.
+ * @file_attributes: A bit-mask with the desired information.
+ *
+ * Return value: TRUE if all of the specified attributes are currently readable.
+ */
+gboolean
+nautilus_file_check_if_ready (NautilusFile *file,
+ NautilusFileAttributes file_attributes)
+{
+ /* To be parallel with call_when_ready, return
+ * TRUE for NULL file.
+ */
+ if (file == NULL)
+ {
+ return TRUE;
+ }
+
+ g_return_val_if_fail (NAUTILUS_IS_FILE (file), FALSE);
+
+ return NAUTILUS_FILE_CLASS (G_OBJECT_GET_CLASS (file))->check_if_ready (file, file_attributes);
+}
+
+void
+nautilus_file_call_when_ready (NautilusFile *file,
+ NautilusFileAttributes file_attributes,
+ NautilusFileCallback callback,
+ gpointer callback_data)
+{
+ if (file == NULL)
+ {
+ (*callback)(file, callback_data);
+ return;
+ }
+
+ g_return_if_fail (NAUTILUS_IS_FILE (file));
+
+ NAUTILUS_FILE_CLASS (G_OBJECT_GET_CLASS (file))->call_when_ready
+ (file, file_attributes, callback, callback_data);
+}
+
+void
+nautilus_file_cancel_call_when_ready (NautilusFile *file,
+ NautilusFileCallback callback,
+ gpointer callback_data)
+{
+ g_return_if_fail (callback != NULL);
+
+ if (file == NULL)
+ {
+ return;
+ }
+
+ g_return_if_fail (NAUTILUS_IS_FILE (file));
+
+ NAUTILUS_FILE_CLASS (G_OBJECT_GET_CLASS (file))->cancel_call_when_ready
+ (file, callback, callback_data);
+}
+
+static void
+invalidate_directory_count (NautilusFile *file)
+{
+ file->details->directory_count_is_up_to_date = FALSE;
+}
+
+static void
+invalidate_deep_counts (NautilusFile *file)
+{
+ file->details->deep_counts_status = NAUTILUS_REQUEST_NOT_STARTED;
+}
+
+static void
+invalidate_mime_list (NautilusFile *file)
+{
+ file->details->mime_list_is_up_to_date = FALSE;
+}
+
+static void
+invalidate_file_info (NautilusFile *file)
+{
+ file->details->file_info_is_up_to_date = FALSE;
+}
+
+static void
+invalidate_thumbnail (NautilusFile *file)
+{
+ file->details->thumbnail_is_up_to_date = FALSE;
+}
+
+static void
+invalidate_mount (NautilusFile *file)
+{
+ file->details->mount_is_up_to_date = FALSE;
+}
+
+void
+nautilus_file_invalidate_extension_info_internal (NautilusFile *file)
+{
+ if (file->details->pending_info_providers)
+ {
+ g_list_free_full (file->details->pending_info_providers, g_object_unref);
+ }
+
+ file->details->pending_info_providers =
+ nautilus_module_get_extensions_for_type (NAUTILUS_TYPE_INFO_PROVIDER);
+}
+
+void
+nautilus_file_invalidate_attributes_internal (NautilusFile *file,
+ NautilusFileAttributes file_attributes)
+{
+ Request request;
+
+ if (file == NULL)
+ {
+ return;
+ }
+
+ request = nautilus_directory_set_up_request (file_attributes);
+
+ if (REQUEST_WANTS_TYPE (request, REQUEST_DIRECTORY_COUNT))
+ {
+ invalidate_directory_count (file);
+ }
+ if (REQUEST_WANTS_TYPE (request, REQUEST_DEEP_COUNT))
+ {
+ invalidate_deep_counts (file);
+ }
+ if (REQUEST_WANTS_TYPE (request, REQUEST_MIME_LIST))
+ {
+ invalidate_mime_list (file);
+ }
+ if (REQUEST_WANTS_TYPE (request, REQUEST_FILE_INFO))
+ {
+ invalidate_file_info (file);
+ }
+ if (REQUEST_WANTS_TYPE (request, REQUEST_EXTENSION_INFO))
+ {
+ nautilus_file_invalidate_extension_info_internal (file);
+ }
+ if (REQUEST_WANTS_TYPE (request, REQUEST_THUMBNAIL))
+ {
+ invalidate_thumbnail (file);
+ }
+ if (REQUEST_WANTS_TYPE (request, REQUEST_MOUNT))
+ {
+ invalidate_mount (file);
+ }
+
+ /* FIXME bugzilla.gnome.org 45075: implement invalidating metadata */
+}
+
+gboolean
+nautilus_file_is_thumbnailing (NautilusFile *file)
+{
+ g_return_val_if_fail (NAUTILUS_IS_FILE (file), FALSE);
+
+ return file->details->is_thumbnailing;
+}
+
+void
+nautilus_file_set_is_thumbnailing (NautilusFile *file,
+ gboolean is_thumbnailing)
+{
+ g_return_if_fail (NAUTILUS_IS_FILE (file));
+
+ file->details->is_thumbnailing = is_thumbnailing;
+}
+
+
+/**
+ * nautilus_file_invalidate_attributes
+ *
+ * Invalidate the specified attributes and force a reload.
+ * @file: NautilusFile representing the file in question.
+ * @file_attributes: attributes to froget.
+ **/
+
+void
+nautilus_file_invalidate_attributes (NautilusFile *file,
+ NautilusFileAttributes file_attributes)
+{
+ /* Cancel possible in-progress loads of any of these attributes */
+ nautilus_directory_cancel_loading_file_attributes (file->details->directory,
+ file,
+ file_attributes);
+
+ /* Actually invalidate the values */
+ nautilus_file_invalidate_attributes_internal (file, file_attributes);
+
+ nautilus_directory_add_file_to_work_queue (file->details->directory, file);
+
+ /* Kick off I/O if necessary */
+ nautilus_directory_async_state_changed (file->details->directory);
+}
+
+NautilusFileAttributes
+nautilus_file_get_all_attributes (void)
+{
+ return NAUTILUS_FILE_ATTRIBUTE_INFO |
+ NAUTILUS_FILE_ATTRIBUTE_DEEP_COUNTS |
+ NAUTILUS_FILE_ATTRIBUTE_DIRECTORY_ITEM_COUNT |
+ NAUTILUS_FILE_ATTRIBUTE_DIRECTORY_ITEM_MIME_TYPES |
+ NAUTILUS_FILE_ATTRIBUTE_EXTENSION_INFO |
+ NAUTILUS_FILE_ATTRIBUTE_THUMBNAIL |
+ NAUTILUS_FILE_ATTRIBUTE_MOUNT;
+}
+
+void
+nautilus_file_invalidate_all_attributes (NautilusFile *file)
+{
+ NautilusFileAttributes all_attributes;
+
+ all_attributes = nautilus_file_get_all_attributes ();
+ nautilus_file_invalidate_attributes (file, all_attributes);
+}
+
+
+/**
+ * nautilus_file_dump
+ *
+ * Debugging call, prints out the contents of the file
+ * fields.
+ *
+ * @file: file to dump.
+ **/
+void
+nautilus_file_dump (NautilusFile *file)
+{
+ long size = file->details->deep_size;
+ char *uri;
+ const char *file_kind;
+
+ uri = nautilus_file_get_uri (file);
+ g_print ("uri: %s \n", uri);
+ if (!file->details->got_file_info)
+ {
+ g_print ("no file info \n");
+ }
+ else if (file->details->get_info_failed)
+ {
+ g_print ("failed to get file info \n");
+ }
+ else
+ {
+ g_print ("size: %ld \n", size);
+ switch (file->details->type)
+ {
+ case G_FILE_TYPE_REGULAR:
+ {
+ file_kind = "regular file";
+ }
+ break;
+
+ case G_FILE_TYPE_DIRECTORY:
+ {
+ file_kind = "folder";
+ }
+ break;
+
+ case G_FILE_TYPE_SPECIAL:
+ {
+ file_kind = "special";
+ }
+ break;
+
+ case G_FILE_TYPE_SYMBOLIC_LINK:
+ {
+ file_kind = "symbolic link";
+ }
+ break;
+
+ case G_FILE_TYPE_UNKNOWN:
+ default:
+ {
+ file_kind = "unknown";
+ }
+ break;
+ }
+ g_print ("kind: %s \n", file_kind);
+ if (file->details->type == G_FILE_TYPE_SYMBOLIC_LINK)
+ {
+ g_print ("link to %s \n", file->details->symlink_name);
+ /* FIXME bugzilla.gnome.org 42430: add following of symlinks here */
+ }
+ /* FIXME bugzilla.gnome.org 42431: add permissions and other useful stuff here */
+ }
+ g_free (uri);
+}
+
+/**
+ * nautilus_file_list_ref
+ *
+ * Ref all the files in a list.
+ * @list: GList of files.
+ **/
+GList *
+nautilus_file_list_ref (GList *list)
+{
+ g_list_foreach (list, (GFunc) nautilus_file_ref, NULL);
+ return list;
+}
+
+/**
+ * nautilus_file_list_unref
+ *
+ * Unref all the files in a list.
+ * @list: GList of files.
+ **/
+void
+nautilus_file_list_unref (GList *list)
+{
+ g_list_foreach (list, (GFunc) nautilus_file_unref, NULL);
+}
+
+/**
+ * nautilus_file_list_free
+ *
+ * Free a list of files after unrefing them.
+ * @list: GList of files.
+ **/
+void
+nautilus_file_list_free (GList *list)
+{
+ nautilus_file_list_unref (list);
+ g_list_free (list);
+}
+
+/**
+ * nautilus_file_list_copy
+ *
+ * Copy the list of files, making a new ref of each,
+ * @list: GList of files.
+ **/
+GList *
+nautilus_file_list_copy (GList *list)
+{
+ return g_list_copy (nautilus_file_list_ref (list));
+}
+
+static gboolean
+get_attributes_for_default_sort_type (NautilusFile *file,
+ gboolean *is_recent,
+ gboolean *is_download,
+ gboolean *is_trash,
+ gboolean *is_search)
+{
+ gboolean is_recent_dir, is_download_dir, is_trash_dir, is_search_dir, retval;
+
+ *is_recent = FALSE;
+ *is_download = FALSE;
+ *is_trash = FALSE;
+ *is_search = FALSE;
+ retval = FALSE;
+
+ /* special handling for certain directories */
+ if (file && nautilus_file_is_directory (file))
+ {
+ is_recent_dir =
+ nautilus_file_is_in_recent (file);
+ is_download_dir =
+ nautilus_file_is_user_special_directory (file, G_USER_DIRECTORY_DOWNLOAD);
+ is_trash_dir =
+ nautilus_file_is_in_trash (file);
+ is_search_dir =
+ nautilus_file_is_in_search (file);
+
+ if (is_download_dir)
+ {
+ *is_download = TRUE;
+ retval = TRUE;
+ }
+ else if (is_trash_dir)
+ {
+ *is_trash = TRUE;
+ retval = TRUE;
+ }
+ else if (is_recent_dir)
+ {
+ *is_recent = TRUE;
+ retval = TRUE;
+ }
+ else if (is_search_dir)
+ {
+ *is_search = TRUE;
+ retval = TRUE;
+ }
+ }
+
+ return retval;
+}
+/**
+ * nautilus_file_get_default_sort_type:
+ * @file: A #NautilusFile representing a location
+ * @reversed: (out): Location to store whether the order is reversed by default.
+ *
+ * Gets which sort order applies by default for the provided locations.
+ *
+ * If @file is a location with special needs (e.g. Trash or Recent), the return
+ * value and @reversed flag are dictated by design. Otherwise, they stem from
+ * the "default-sort-order" and "default-sort-in-reverse-order" preference keys.
+ *
+ * Returns: The default #NautilusFileSortType for this @file.
+ */
+NautilusFileSortType
+nautilus_file_get_default_sort_type (NautilusFile *file,
+ gboolean *reversed)
+{
+ NautilusFileSortType retval;
+ gboolean is_recent;
+ gboolean is_download;
+ gboolean is_trash;
+ gboolean is_search;
+ gboolean res;
+
+ retval = g_settings_get_enum (nautilus_preferences,
+ NAUTILUS_PREFERENCES_DEFAULT_SORT_ORDER);
+ is_recent = FALSE;
+ is_download = FALSE;
+ is_trash = FALSE;
+ is_search = FALSE;
+ res = get_attributes_for_default_sort_type (file, &is_recent, &is_download, &is_trash, &is_search);
+
+ if (res)
+ {
+ if (is_recent)
+ {
+ retval = NAUTILUS_FILE_SORT_BY_RECENCY;
+ }
+ else if (is_download)
+ {
+ retval = NAUTILUS_FILE_SORT_BY_MTIME;
+ }
+ else if (is_trash)
+ {
+ retval = NAUTILUS_FILE_SORT_BY_TRASHED_TIME;
+ }
+ else if (is_search)
+ {
+ retval = NAUTILUS_FILE_SORT_BY_SEARCH_RELEVANCE;
+ }
+ else
+ {
+ g_assert_not_reached ();
+ }
+
+ if (reversed != NULL)
+ {
+ *reversed = res;
+ }
+ }
+ else
+ {
+ if (reversed != NULL)
+ {
+ *reversed = g_settings_get_boolean (nautilus_preferences,
+ NAUTILUS_PREFERENCES_DEFAULT_SORT_IN_REVERSE_ORDER);
+ }
+ }
+
+ return retval;
+}
+
+static int
+compare_by_display_name_cover (gconstpointer a,
+ gconstpointer b)
+{
+ return compare_by_display_name (NAUTILUS_FILE (a), NAUTILUS_FILE (b));
+}
+
+/**
+ * nautilus_file_list_sort_by_display_name
+ *
+ * Sort the list of files by file name.
+ * @list: GList of files.
+ **/
+GList *
+nautilus_file_list_sort_by_display_name (GList *list)
+{
+ return g_list_sort (list, compare_by_display_name_cover);
+}
+
+static GList *ready_data_list = NULL;
+
+typedef struct
+{
+ GList *file_list;
+ GList *remaining_files;
+ NautilusFileListCallback callback;
+ gpointer callback_data;
+} FileListReadyData;
+
+static void
+file_list_ready_data_free (FileListReadyData *data)
+{
+ GList *l;
+
+ l = g_list_find (ready_data_list, data);
+ if (l != NULL)
+ {
+ ready_data_list = g_list_delete_link (ready_data_list, l);
+
+ nautilus_file_list_free (data->file_list);
+ g_list_free (data->remaining_files);
+ g_free (data);
+ }
+}
+
+static FileListReadyData *
+file_list_ready_data_new (GList *file_list,
+ NautilusFileListCallback callback,
+ gpointer callback_data)
+{
+ FileListReadyData *data;
+
+ data = g_new0 (FileListReadyData, 1);
+ data->file_list = nautilus_file_list_copy (file_list);
+ data->remaining_files = g_list_copy (file_list);
+ data->callback = callback;
+ data->callback_data = callback_data;
+
+ ready_data_list = g_list_prepend (ready_data_list, data);
+
+ return data;
+}
+
+static void
+file_list_file_ready_callback (NautilusFile *file,
+ gpointer user_data)
+{
+ FileListReadyData *data;
+
+ data = user_data;
+ data->remaining_files = g_list_remove (data->remaining_files, file);
+
+ if (data->remaining_files == NULL)
+ {
+ if (data->callback)
+ {
+ (*data->callback)(data->file_list, data->callback_data);
+ }
+
+ file_list_ready_data_free (data);
+ }
+}
+
+void
+nautilus_file_list_call_when_ready (GList *file_list,
+ NautilusFileAttributes attributes,
+ NautilusFileListHandle **handle,
+ NautilusFileListCallback callback,
+ gpointer callback_data)
+{
+ GList *l;
+ FileListReadyData *data;
+ NautilusFile *file;
+
+ g_return_if_fail (file_list != NULL);
+
+ data = file_list_ready_data_new
+ (file_list, callback, callback_data);
+
+ if (handle)
+ {
+ *handle = (NautilusFileListHandle *) data;
+ }
+
+
+ l = file_list;
+ while (l != NULL)
+ {
+ file = NAUTILUS_FILE (l->data);
+ /* Need to do this here, as the list can be modified by this call */
+ l = l->next;
+ nautilus_file_call_when_ready (file,
+ attributes,
+ file_list_file_ready_callback,
+ data);
+ }
+}
+
+void
+nautilus_file_list_cancel_call_when_ready (NautilusFileListHandle *handle)
+{
+ GList *l;
+ NautilusFile *file;
+ FileListReadyData *data;
+
+ g_return_if_fail (handle != NULL);
+
+ data = (FileListReadyData *) handle;
+
+ l = g_list_find (ready_data_list, data);
+ if (l != NULL)
+ {
+ for (l = data->remaining_files; l != NULL; l = l->next)
+ {
+ file = NAUTILUS_FILE (l->data);
+
+ NAUTILUS_FILE_CLASS (G_OBJECT_GET_CLASS (file))->cancel_call_when_ready
+ (file, file_list_file_ready_callback, data);
+ }
+
+ file_list_ready_data_free (data);
+ }
+}
+
+static void
+thumbnail_limit_changed_callback (gpointer user_data)
+{
+ cached_thumbnail_limit = g_settings_get_uint64 (nautilus_preferences,
+ NAUTILUS_PREFERENCES_FILE_THUMBNAIL_LIMIT);
+
+ /*Converts the obtained limit in MB to bytes */
+ cached_thumbnail_limit *= MEGA_TO_BASE_RATE;
+
+ /* Tell the world that icons might have changed. We could invent a narrower-scope
+ * signal to mean only "thumbnails might have changed" if this ends up being slow
+ * for some reason.
+ */
+ emit_change_signals_for_all_files_in_all_directories ();
+}
+
+static void
+show_thumbnails_changed_callback (gpointer user_data)
+{
+ show_file_thumbs = g_settings_get_enum (nautilus_preferences, NAUTILUS_PREFERENCES_SHOW_FILE_THUMBNAILS);
+
+ /* Tell the world that icons might have changed. We could invent a narrower-scope
+ * signal to mean only "thumbnails might have changed" if this ends up being slow
+ * for some reason.
+ */
+ emit_change_signals_for_all_files_in_all_directories ();
+}
+
+static void
+mime_type_data_changed_callback (GObject *signaller,
+ gpointer user_data)
+{
+ /* Tell the world that icons might have changed. We could invent a narrower-scope
+ * signal to mean only "thumbnails might have changed" if this ends up being slow
+ * for some reason.
+ */
+ emit_change_signals_for_all_files_in_all_directories ();
+}
+
+static gboolean
+real_get_item_count (NautilusFile *file,
+ guint *count,
+ gboolean *count_unreadable)
+{
+ if (count_unreadable != NULL)
+ {
+ *count_unreadable = file->details->directory_count_failed;
+ }
+
+ if (!file->details->got_directory_count)
+ {
+ if (count != NULL)
+ {
+ *count = 0;
+ }
+ return FALSE;
+ }
+
+ if (count != NULL)
+ {
+ *count = file->details->directory_count;
+ }
+
+ return TRUE;
+}
+
+static NautilusRequestStatus
+real_get_deep_counts (NautilusFile *file,
+ guint *directory_count,
+ guint *file_count,
+ guint *unreadable_directory_count,
+ goffset *total_size)
+{
+ GFileType type;
+
+ type = nautilus_file_get_file_type (file);
+
+ if (directory_count != NULL)
+ {
+ *directory_count = 0;
+ }
+ if (file_count != NULL)
+ {
+ *file_count = 0;
+ }
+ if (unreadable_directory_count != NULL)
+ {
+ *unreadable_directory_count = 0;
+ }
+ if (total_size != NULL)
+ {
+ *total_size = 0;
+ }
+
+ if (type != G_FILE_TYPE_DIRECTORY)
+ {
+ return NAUTILUS_REQUEST_DONE;
+ }
+
+ if (file->details->deep_counts_status != NAUTILUS_REQUEST_NOT_STARTED)
+ {
+ if (directory_count != NULL)
+ {
+ *directory_count = file->details->deep_directory_count;
+ }
+ if (file_count != NULL)
+ {
+ *file_count = file->details->deep_file_count;
+ }
+ if (unreadable_directory_count != NULL)
+ {
+ *unreadable_directory_count = file->details->deep_unreadable_count;
+ }
+ if (total_size != NULL)
+ {
+ *total_size = file->details->deep_size;
+ }
+ return file->details->deep_counts_status;
+ }
+
+ /* For directories, or before we know the type, we haven't started. */
+ if (type == G_FILE_TYPE_UNKNOWN || type == G_FILE_TYPE_DIRECTORY)
+ {
+ return NAUTILUS_REQUEST_NOT_STARTED;
+ }
+
+ /* For other types, we are done, and the zeros are permanent. */
+ return NAUTILUS_REQUEST_DONE;
+}
+
+static void
+real_set_metadata (NautilusFile *file,
+ const char *key,
+ const char *value)
+{
+ /* Dummy default impl */
+}
+
+static void
+real_set_metadata_as_list (NautilusFile *file,
+ const char *key,
+ char **value)
+{
+ /* Dummy default impl */
+}
+
+static void
+nautilus_file_class_init (NautilusFileClass *class)
+{
+ nautilus_file_info_getter = nautilus_file_get_internal;
+
+ attribute_name_q = g_quark_from_static_string ("name");
+ attribute_size_q = g_quark_from_static_string ("size");
+ attribute_type_q = g_quark_from_static_string ("type");
+ attribute_detailed_type_q = g_quark_from_static_string ("detailed_type");
+ attribute_modification_date_q = g_quark_from_static_string ("modification_date");
+ attribute_date_modified_q = g_quark_from_static_string ("date_modified");
+ attribute_date_modified_full_q = g_quark_from_static_string ("date_modified_full");
+ attribute_date_modified_with_time_q = g_quark_from_static_string ("date_modified_with_time");
+ attribute_recency_q = g_quark_from_static_string ("recency");
+ attribute_accessed_date_q = g_quark_from_static_string ("accessed_date");
+ attribute_date_accessed_q = g_quark_from_static_string ("date_accessed");
+ attribute_date_accessed_full_q = g_quark_from_static_string ("date_accessed_full");
+ attribute_date_created_q = g_quark_from_static_string ("date_created");
+ attribute_date_created_full_q = g_quark_from_static_string ("date_created_full");
+ attribute_mime_type_q = g_quark_from_static_string ("mime_type");
+ attribute_size_detail_q = g_quark_from_static_string ("size_detail");
+ attribute_deep_size_q = g_quark_from_static_string ("deep_size");
+ attribute_deep_file_count_q = g_quark_from_static_string ("deep_file_count");
+ attribute_deep_directory_count_q = g_quark_from_static_string ("deep_directory_count");
+ attribute_deep_total_count_q = g_quark_from_static_string ("deep_total_count");
+ attribute_search_relevance_q = g_quark_from_static_string ("search_relevance");
+ attribute_trashed_on_q = g_quark_from_static_string ("trashed_on");
+ attribute_trashed_on_full_q = g_quark_from_static_string ("trashed_on_full");
+ attribute_trash_orig_path_q = g_quark_from_static_string ("trash_orig_path");
+ attribute_permissions_q = g_quark_from_static_string ("permissions");
+ attribute_selinux_context_q = g_quark_from_static_string ("selinux_context");
+ attribute_octal_permissions_q = g_quark_from_static_string ("octal_permissions");
+ attribute_owner_q = g_quark_from_static_string ("owner");
+ attribute_group_q = g_quark_from_static_string ("group");
+ attribute_uri_q = g_quark_from_static_string ("uri");
+ attribute_where_q = g_quark_from_static_string ("where");
+ attribute_link_target_q = g_quark_from_static_string ("link_target");
+ attribute_volume_q = g_quark_from_static_string ("volume");
+ attribute_free_space_q = g_quark_from_static_string ("free_space");
+ attribute_starred_q = g_quark_from_static_string ("starred");
+
+ G_OBJECT_CLASS (class)->finalize = finalize;
+ G_OBJECT_CLASS (class)->constructor = nautilus_file_constructor;
+
+ class->get_item_count = real_get_item_count;
+ class->get_deep_counts = real_get_deep_counts;
+ class->set_metadata = real_set_metadata;
+ class->set_metadata_as_list = real_set_metadata_as_list;
+
+ signals[CHANGED] =
+ g_signal_new ("changed",
+ G_TYPE_FROM_CLASS (class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (NautilusFileClass, changed),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+
+ signals[UPDATED_DEEP_COUNT_IN_PROGRESS] =
+ g_signal_new ("updated-deep-count-in-progress",
+ G_TYPE_FROM_CLASS (class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (NautilusFileClass, updated_deep_count_in_progress),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+
+ g_type_class_add_private (class, sizeof (NautilusFileDetails));
+
+ thumbnail_limit_changed_callback (NULL);
+ g_signal_connect_swapped (nautilus_preferences,
+ "changed::" NAUTILUS_PREFERENCES_FILE_THUMBNAIL_LIMIT,
+ G_CALLBACK (thumbnail_limit_changed_callback),
+ NULL);
+ show_thumbnails_changed_callback (NULL);
+ g_signal_connect_swapped (nautilus_preferences,
+ "changed::" NAUTILUS_PREFERENCES_SHOW_FILE_THUMBNAILS,
+ G_CALLBACK (show_thumbnails_changed_callback),
+ NULL);
+
+ g_signal_connect (nautilus_signaller_get_current (),
+ "mime-data-changed",
+ G_CALLBACK (mime_type_data_changed_callback),
+ NULL);
+}
+
+void
+nautilus_file_info_providers_done (NautilusFile *file)
+{
+ g_list_free_full (file->details->extension_emblems, g_free);
+ file->details->extension_emblems = file->details->pending_extension_emblems;
+ file->details->pending_extension_emblems = NULL;
+
+ if (file->details->extension_attributes)
+ {
+ g_hash_table_destroy (file->details->extension_attributes);
+ }
+
+ file->details->extension_attributes = file->details->pending_extension_attributes;
+ file->details->pending_extension_attributes = NULL;
+
+ nautilus_file_changed (file);
+}
+
+static gboolean
+is_gone (NautilusFileInfo *file_info)
+{
+ NautilusFile *file;
+
+ file = NAUTILUS_FILE (file_info);
+
+ return file->details->is_gone;
+}
+
+static char *
+get_name (NautilusFileInfo *file_info)
+{
+ NautilusFile *file;
+
+ file = NAUTILUS_FILE (file_info);
+
+ return g_strdup (file->details->name);
+}
+
+static char *
+get_uri (NautilusFileInfo *file_info)
+{
+ NautilusFile *file;
+ g_autoptr (GFile) location = NULL;
+
+ file = NAUTILUS_FILE (file_info);
+ location = nautilus_file_get_location (file);
+
+ return g_file_get_uri (location);
+}
+
+static char *
+get_parent_uri (NautilusFileInfo *file_info)
+{
+ NautilusFile *file;
+
+ file = NAUTILUS_FILE (file_info);
+
+ if (nautilus_file_is_self_owned (file))
+ {
+ /* Callers expect an empty string, not a NULL. */
+ return g_strdup ("");
+ }
+
+ return nautilus_directory_get_uri (file->details->directory);
+}
+
+static char *
+get_uri_scheme (NautilusFileInfo *file_info)
+{
+ NautilusFile *file;
+ g_autoptr (GFile) location = NULL;
+
+ file = NAUTILUS_FILE (file_info);
+
+ if (file->details->directory == NULL)
+ {
+ return NULL;
+ }
+
+ location = nautilus_directory_get_location (file->details->directory);
+ if (location == NULL)
+ {
+ return NULL;
+ }
+
+ return g_file_get_uri_scheme (location);
+}
+
+static char *
+get_mime_type (NautilusFileInfo *file_info)
+{
+ NautilusFile *file;
+
+ file = NAUTILUS_FILE (file_info);
+
+ if (file->details->mime_type != NULL)
+ {
+ return g_strdup (file->details->mime_type);
+ }
+
+ return g_strdup ("application/octet-stream");
+}
+
+static gboolean
+is_mime_type (NautilusFileInfo *file_info,
+ const char *mime_type)
+{
+ NautilusFile *file;
+
+ file = NAUTILUS_FILE (file_info);
+
+ if (file->details->mime_type == NULL)
+ {
+ return FALSE;
+ }
+
+ return g_content_type_is_a (file->details->mime_type, mime_type);
+}
+
+static gboolean
+is_directory (NautilusFileInfo *file_info)
+{
+ NautilusFile *file;
+
+ file = NAUTILUS_FILE (file_info);
+
+ return nautilus_file_get_file_type (file) == G_FILE_TYPE_DIRECTORY;
+}
+
+static void
+add_emblem (NautilusFileInfo *file_info,
+ const char *emblem_name)
+{
+ NautilusFile *file;
+
+ file = NAUTILUS_FILE (file_info);
+
+ if (file->details->pending_info_providers)
+ {
+ file->details->pending_extension_emblems = g_list_prepend (file->details->pending_extension_emblems,
+ g_strdup (emblem_name));
+ }
+ else
+ {
+ file->details->extension_emblems = g_list_prepend (file->details->extension_emblems,
+ g_strdup (emblem_name));
+ }
+
+ nautilus_file_changed (file);
+}
+
+static char *
+get_string_attribute (NautilusFileInfo *file_info,
+ const char *attribute_name)
+{
+ NautilusFile *file;
+
+ file = NAUTILUS_FILE (file_info);
+
+ return nautilus_file_get_string_attribute_q (file, g_quark_from_string (attribute_name));
+}
+
+static void
+add_string_attribute (NautilusFileInfo *file_info,
+ const char *attribute_name,
+ const char *value)
+{
+ NautilusFile *file;
+
+ file = NAUTILUS_FILE (file_info);
+
+ if (file->details->pending_info_providers != NULL)
+ {
+ /* Lazily create hashtable */
+ if (file->details->pending_extension_attributes == NULL)
+ {
+ file->details->pending_extension_attributes =
+ g_hash_table_new_full (g_direct_hash, g_direct_equal,
+ NULL,
+ (GDestroyNotify) g_free);
+ }
+ g_hash_table_insert (file->details->pending_extension_attributes,
+ GINT_TO_POINTER (g_quark_from_string (attribute_name)),
+ g_strdup (value));
+ }
+ else
+ {
+ if (file->details->extension_attributes == NULL)
+ {
+ file->details->extension_attributes =
+ g_hash_table_new_full (g_direct_hash, g_direct_equal,
+ NULL,
+ (GDestroyNotify) g_free);
+ }
+ g_hash_table_insert (file->details->extension_attributes,
+ GINT_TO_POINTER (g_quark_from_string (attribute_name)),
+ g_strdup (value));
+ }
+
+ nautilus_file_changed (file);
+}
+
+static void
+invalidate_extension_info (NautilusFileInfo *file_info)
+{
+ NautilusFile *file;
+
+ file = NAUTILUS_FILE (file_info);
+
+ nautilus_file_invalidate_attributes (file, NAUTILUS_FILE_ATTRIBUTE_EXTENSION_INFO);
+}
+
+static char *
+get_activation_uri (NautilusFileInfo *file_info)
+{
+ NautilusFile *file;
+
+ file = NAUTILUS_FILE (file_info);
+
+ if (file->details->activation_uri != NULL)
+ {
+ return g_strdup (file->details->activation_uri);
+ }
+
+ return nautilus_file_get_uri (file);
+}
+
+static GFileType
+get_file_type (NautilusFileInfo *file_info)
+{
+ NautilusFile *file;
+
+ file = NAUTILUS_FILE (file_info);
+
+ return file->details->type;
+}
+
+static GFile *
+get_location (NautilusFileInfo *file_info)
+{
+ NautilusFile *file;
+ g_autoptr (GFile) location = NULL;
+
+ file = NAUTILUS_FILE (file_info);
+ location = nautilus_directory_get_location (file->details->directory);
+
+ if (nautilus_file_is_self_owned (file))
+ {
+ return g_object_ref (location);
+ }
+
+ return g_file_get_child (location, file->details->name);
+}
+
+static GFile *
+get_parent_location (NautilusFileInfo *file_info)
+{
+ NautilusFile *file;
+
+ file = NAUTILUS_FILE (file_info);
+
+ if (nautilus_file_is_self_owned (file))
+ {
+ return NULL;
+ }
+
+ return nautilus_directory_get_location (file->details->directory);
+}
+
+static NautilusFileInfo *
+get_parent_info (NautilusFileInfo *file_info)
+{
+ NautilusFile *file;
+ NautilusFile *parent_file;
+
+ file = NAUTILUS_FILE (file_info);
+
+ if (nautilus_file_is_self_owned (file))
+ {
+ return NULL;
+ }
+
+ parent_file = nautilus_directory_get_corresponding_file (file->details->directory);
+
+ return NAUTILUS_FILE_INFO (parent_file);
+}
+
+static GMount *
+get_mount (NautilusFileInfo *file_info)
+{
+ NautilusFile *file;
+
+ file = NAUTILUS_FILE (file_info);
+
+ if (file->details->mount)
+ {
+ return g_object_ref (file->details->mount);
+ }
+
+ return NULL;
+}
+
+static gboolean
+can_write (NautilusFileInfo *file_info)
+{
+ NautilusFile *file;
+
+ file = NAUTILUS_FILE (file_info);
+
+ return file->details->can_write;
+}
+
+static void
+nautilus_file_info_iface_init (NautilusFileInfoInterface *iface)
+{
+ iface->is_gone = is_gone;
+
+ iface->get_name = get_name;
+ iface->get_uri = get_uri;
+ iface->get_parent_uri = get_parent_uri;
+ iface->get_uri_scheme = get_uri_scheme;
+
+ iface->get_mime_type = get_mime_type;
+ iface->is_mime_type = is_mime_type;
+ iface->is_directory = is_directory;
+
+ iface->add_emblem = add_emblem;
+ iface->get_string_attribute = get_string_attribute;
+ iface->add_string_attribute = add_string_attribute;
+ iface->invalidate_extension_info = invalidate_extension_info;
+
+ iface->get_activation_uri = get_activation_uri;
+
+ iface->get_file_type = get_file_type;
+ iface->get_location = get_location;
+ iface->get_parent_location = get_parent_location;
+ iface->get_parent_info = get_parent_info;
+ iface->get_mount = get_mount;
+ iface->can_write = can_write;
+}
+
+#if !defined (NAUTILUS_OMIT_SELF_CHECK)
+
+void
+nautilus_self_check_file (void)
+{
+ NautilusFile *file_1;
+ NautilusFile *file_2;
+ GList *list;
+
+ /* refcount checks */
+
+ EEL_CHECK_INTEGER_RESULT (nautilus_directory_number_outstanding (), 0);
+
+ file_1 = nautilus_file_get_by_uri ("file:///home/");
+
+ EEL_CHECK_INTEGER_RESULT (G_OBJECT (file_1)->ref_count, 1);
+ EEL_CHECK_INTEGER_RESULT (G_OBJECT (file_1->details->directory)->ref_count, 1);
+ EEL_CHECK_INTEGER_RESULT (nautilus_directory_number_outstanding (), 1);
+
+ nautilus_file_unref (file_1);
+
+ EEL_CHECK_INTEGER_RESULT (nautilus_directory_number_outstanding (), 0);
+
+ file_1 = nautilus_file_get_by_uri ("file:///etc");
+ file_2 = nautilus_file_get_by_uri ("file:///usr");
+
+ list = NULL;
+ list = g_list_prepend (list, file_1);
+ list = g_list_prepend (list, file_2);
+
+ nautilus_file_list_ref (list);
+
+ EEL_CHECK_INTEGER_RESULT (G_OBJECT (file_1)->ref_count, 2);
+ EEL_CHECK_INTEGER_RESULT (G_OBJECT (file_2)->ref_count, 2);
+
+ nautilus_file_list_unref (list);
+
+ EEL_CHECK_INTEGER_RESULT (G_OBJECT (file_1)->ref_count, 1);
+ EEL_CHECK_INTEGER_RESULT (G_OBJECT (file_2)->ref_count, 1);
+
+ nautilus_file_list_free (list);
+
+ EEL_CHECK_INTEGER_RESULT (nautilus_directory_number_outstanding (), 0);
+
+
+ /* name checks */
+ file_1 = nautilus_file_get_by_uri ("file:///home/");
+
+ EEL_CHECK_STRING_RESULT (nautilus_file_get_name (file_1), "home");
+
+ EEL_CHECK_BOOLEAN_RESULT (nautilus_file_get_by_uri ("file:///home/") == file_1, TRUE);
+ nautilus_file_unref (file_1);
+
+ EEL_CHECK_BOOLEAN_RESULT (nautilus_file_get_by_uri ("file:///home") == file_1, TRUE);
+ nautilus_file_unref (file_1);
+
+ nautilus_file_unref (file_1);
+
+ file_1 = nautilus_file_get_by_uri ("file:///home");
+ EEL_CHECK_STRING_RESULT (nautilus_file_get_name (file_1), "home");
+ nautilus_file_unref (file_1);
+
+ /* sorting */
+ file_1 = nautilus_file_get_by_uri ("file:///etc");
+ file_2 = nautilus_file_get_by_uri ("file:///usr");
+
+ EEL_CHECK_INTEGER_RESULT (G_OBJECT (file_1)->ref_count, 1);
+ EEL_CHECK_INTEGER_RESULT (G_OBJECT (file_2)->ref_count, 1);
+
+ EEL_CHECK_BOOLEAN_RESULT (nautilus_file_compare_for_sort (file_1, file_2, NAUTILUS_FILE_SORT_BY_DISPLAY_NAME, FALSE, FALSE) < 0, TRUE);
+ EEL_CHECK_BOOLEAN_RESULT (nautilus_file_compare_for_sort (file_1, file_2, NAUTILUS_FILE_SORT_BY_DISPLAY_NAME, FALSE, TRUE) > 0, TRUE);
+ EEL_CHECK_BOOLEAN_RESULT (nautilus_file_compare_for_sort (file_1, file_1, NAUTILUS_FILE_SORT_BY_DISPLAY_NAME, FALSE, FALSE) == 0, TRUE);
+ EEL_CHECK_BOOLEAN_RESULT (nautilus_file_compare_for_sort (file_1, file_1, NAUTILUS_FILE_SORT_BY_DISPLAY_NAME, TRUE, FALSE) == 0, TRUE);
+ EEL_CHECK_BOOLEAN_RESULT (nautilus_file_compare_for_sort (file_1, file_1, NAUTILUS_FILE_SORT_BY_DISPLAY_NAME, FALSE, TRUE) == 0, TRUE);
+ EEL_CHECK_BOOLEAN_RESULT (nautilus_file_compare_for_sort (file_1, file_1, NAUTILUS_FILE_SORT_BY_DISPLAY_NAME, TRUE, TRUE) == 0, TRUE);
+
+ nautilus_file_unref (file_1);
+ nautilus_file_unref (file_2);
+}
+
+#endif /* !NAUTILUS_OMIT_SELF_CHECK */
diff --git a/src/nautilus-file.h b/src/nautilus-file.h
new file mode 100644
index 0000000..57cca17
--- /dev/null
+++ b/src/nautilus-file.h
@@ -0,0 +1,590 @@
+/*
+ nautilus-file.h: Nautilus file model.
+
+ Copyright (C) 1999, 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/>.
+
+ Author: Darin Adler <darin@bentspoon.com>
+*/
+
+#pragma once
+
+#include <gtk/gtk.h>
+#include <gio/gio.h>
+
+#include "nautilus-types.h"
+
+/* NautilusFile is an object used to represent a single element of a
+ * NautilusDirectory. It's lightweight and relies on NautilusDirectory
+ * to do most of the work.
+ */
+
+/* NautilusFile is defined both here and in nautilus-directory.h. */
+#ifndef NAUTILUS_FILE_DEFINED
+#define NAUTILUS_FILE_DEFINED
+typedef struct NautilusFile NautilusFile;
+#endif
+
+#define NAUTILUS_TYPE_FILE nautilus_file_get_type()
+#define NAUTILUS_FILE(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST ((obj), NAUTILUS_TYPE_FILE, NautilusFile))
+#define NAUTILUS_FILE_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST ((klass), NAUTILUS_TYPE_FILE, NautilusFileClass))
+#define NAUTILUS_IS_FILE(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE ((obj), NAUTILUS_TYPE_FILE))
+#define NAUTILUS_IS_FILE_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE ((klass), NAUTILUS_TYPE_FILE))
+#define NAUTILUS_FILE_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), NAUTILUS_TYPE_FILE, NautilusFileClass))
+
+typedef enum {
+ /* These may be set as default-sort-order. When touching this, make sure to
+ * keep the values in sync with the "org.gnome.nautilus.SortOrder" enum in the
+ * `data/org.gnome.nautilus.gschema.xml` schemas file, and the attributes[]
+ * array in `src/nautilus-list-view.c`.
+ */
+ NAUTILUS_FILE_SORT_BY_DISPLAY_NAME = 0,
+ NAUTILUS_FILE_SORT_BY_SIZE = 1,
+ NAUTILUS_FILE_SORT_BY_TYPE = 2,
+ NAUTILUS_FILE_SORT_BY_MTIME = 3,
+ NAUTILUS_FILE_SORT_BY_ATIME = 4,
+ NAUTILUS_FILE_SORT_BY_BTIME = 5,
+ NAUTILUS_FILE_SORT_BY_STARRED = 6,
+
+ /* The following are specific to special locations and as such are not to be
+ * included in the "org.gnome.nautilus.SortOrder" enum.
+ */
+ NAUTILUS_FILE_SORT_BY_TRASHED_TIME,
+ NAUTILUS_FILE_SORT_BY_SEARCH_RELEVANCE,
+ NAUTILUS_FILE_SORT_BY_RECENCY
+} NautilusFileSortType;
+
+typedef enum {
+ NAUTILUS_REQUEST_NOT_STARTED,
+ NAUTILUS_REQUEST_IN_PROGRESS,
+ NAUTILUS_REQUEST_DONE
+} NautilusRequestStatus;
+
+typedef enum {
+ NAUTILUS_FILE_ICON_FLAGS_NONE = 0,
+ NAUTILUS_FILE_ICON_FLAGS_USE_THUMBNAILS = (1<<0),
+ /* uses the icon of the mount if present */
+ NAUTILUS_FILE_ICON_FLAGS_USE_MOUNT_ICON = (1<<1),
+} NautilusFileIconFlags;
+
+#define NAUTILUS_THUMBNAIL_MINIMUM_ICON_SIZE 32
+
+/* Emblems sometimes displayed for NautilusFiles. Do not localize. */
+#define NAUTILUS_FILE_EMBLEM_NAME_SYMBOLIC_LINK "symbolic-link"
+#define NAUTILUS_FILE_EMBLEM_NAME_CANT_READ "unreadable"
+#define NAUTILUS_FILE_EMBLEM_NAME_CANT_WRITE "readonly"
+#define NAUTILUS_FILE_EMBLEM_NAME_TRASH "trash"
+#define NAUTILUS_FILE_EMBLEM_NAME_NOTE "note"
+
+typedef void (*NautilusFileCallback) (NautilusFile *file,
+ gpointer callback_data);
+typedef gboolean (*NautilusFileFilterFunc) (NautilusFile *file,
+ gpointer callback_data);
+typedef void (*NautilusFileListCallback) (GList *file_list,
+ gpointer callback_data);
+typedef void (*NautilusFileOperationCallback) (NautilusFile *file,
+ GFile *result_location,
+ GError *error,
+ gpointer callback_data);
+
+
+#define NAUTILUS_FILE_ATTRIBUTES_FOR_ICON (NAUTILUS_FILE_ATTRIBUTE_INFO | NAUTILUS_FILE_ATTRIBUTE_THUMBNAIL)
+
+typedef void NautilusFileListHandle;
+
+/* GObject requirements. */
+GType nautilus_file_get_type (void);
+
+/* Getting at a single file. */
+NautilusFile * nautilus_file_get (GFile *location);
+NautilusFile * nautilus_file_get_by_uri (const char *uri);
+
+/* Get a file only if the nautilus version already exists */
+NautilusFile * nautilus_file_get_existing (GFile *location);
+NautilusFile * nautilus_file_get_existing_by_uri (const char *uri);
+
+/* Covers for g_object_ref and g_object_unref that provide two conveniences:
+ * 1) Using these is type safe.
+ * 2) You are allowed to call these with NULL,
+ */
+NautilusFile * nautilus_file_ref (NautilusFile *file);
+void nautilus_file_unref (NautilusFile *file);
+
+G_DEFINE_AUTOPTR_CLEANUP_FUNC (NautilusFile, nautilus_file_unref)
+
+/* Monitor the file. */
+void nautilus_file_monitor_add (NautilusFile *file,
+ gconstpointer client,
+ NautilusFileAttributes attributes);
+void nautilus_file_monitor_remove (NautilusFile *file,
+ gconstpointer client);
+
+/* Waiting for data that's read asynchronously.
+ * This interface currently works only for metadata, but could be expanded
+ * to other attributes as well.
+ */
+void nautilus_file_call_when_ready (NautilusFile *file,
+ NautilusFileAttributes attributes,
+ NautilusFileCallback callback,
+ gpointer callback_data);
+void nautilus_file_cancel_call_when_ready (NautilusFile *file,
+ NautilusFileCallback callback,
+ gpointer callback_data);
+gboolean nautilus_file_check_if_ready (NautilusFile *file,
+ NautilusFileAttributes attributes);
+void nautilus_file_invalidate_attributes (NautilusFile *file,
+ NautilusFileAttributes attributes);
+void nautilus_file_invalidate_all_attributes (NautilusFile *file);
+
+/* Basic attributes for file objects. */
+gboolean nautilus_file_contains_text (NautilusFile *file);
+char * nautilus_file_get_display_name (NautilusFile *file);
+char * nautilus_file_get_edit_name (NautilusFile *file);
+char * nautilus_file_get_name (NautilusFile *file);
+GFile * nautilus_file_get_location (NautilusFile *file);
+char * nautilus_file_get_description (NautilusFile *file);
+char * nautilus_file_get_uri (NautilusFile *file);
+char * nautilus_file_get_uri_scheme (NautilusFile *file);
+NautilusFile * nautilus_file_get_parent (NautilusFile *file);
+GFile * nautilus_file_get_parent_location (NautilusFile *file);
+char * nautilus_file_get_parent_uri (NautilusFile *file);
+char * nautilus_file_get_parent_uri_for_display (NautilusFile *file);
+char * nautilus_file_get_thumbnail_path (NautilusFile *file);
+gboolean nautilus_file_can_get_size (NautilusFile *file);
+goffset nautilus_file_get_size (NautilusFile *file);
+time_t nautilus_file_get_mtime (NautilusFile *file);
+time_t nautilus_file_get_atime (NautilusFile *file);
+time_t nautilus_file_get_btime (NautilusFile *file);
+time_t nautilus_file_get_recency (NautilusFile *file);
+time_t nautilus_file_get_trash_time (NautilusFile *file);
+GFileType nautilus_file_get_file_type (NautilusFile *file);
+char * nautilus_file_get_mime_type (NautilusFile *file);
+char * nautilus_file_get_extension (NautilusFile *file);
+gboolean nautilus_file_is_mime_type (NautilusFile *file,
+ const char *mime_type);
+gboolean nautilus_file_is_launchable (NautilusFile *file);
+gboolean nautilus_file_is_symbolic_link (NautilusFile *file);
+GMount * nautilus_file_get_mount (NautilusFile *file);
+char * nautilus_file_get_volume_free_space (NautilusFile *file);
+char * nautilus_file_get_volume_name (NautilusFile *file);
+char * nautilus_file_get_symbolic_link_target_path (NautilusFile *file);
+char * nautilus_file_get_symbolic_link_target_uri (NautilusFile *file);
+gboolean nautilus_file_is_broken_symbolic_link (NautilusFile *file);
+gboolean nautilus_file_is_executable (NautilusFile *file);
+gboolean nautilus_file_is_directory (NautilusFile *file);
+gboolean nautilus_file_is_regular_file (NautilusFile *file);
+gboolean nautilus_file_is_user_special_directory (NautilusFile *file,
+ GUserDirectory special_directory);
+gboolean nautilus_file_is_archive (NautilusFile *file);
+gboolean nautilus_file_is_in_search (NautilusFile *file);
+gboolean nautilus_file_is_in_trash (NautilusFile *file);
+gboolean nautilus_file_is_in_recent (NautilusFile *file);
+gboolean nautilus_file_is_in_starred (NautilusFile *file);
+gboolean nautilus_file_is_in_admin (NautilusFile *file);
+gboolean nautilus_file_is_remote (NautilusFile *file);
+gboolean nautilus_file_is_other_locations (NautilusFile *file);
+gboolean nautilus_file_is_starred_location (NautilusFile *file);
+gboolean nautilus_file_is_home (NautilusFile *file);
+GError * nautilus_file_get_file_info_error (NautilusFile *file);
+gboolean nautilus_file_get_directory_item_count (NautilusFile *file,
+ guint *count,
+ gboolean *count_unreadable);
+void nautilus_file_recompute_deep_counts (NautilusFile *file);
+NautilusRequestStatus nautilus_file_get_deep_counts (NautilusFile *file,
+ guint *directory_count,
+ guint *file_count,
+ guint *unreadable_directory_count,
+ goffset *total_size,
+ gboolean force);
+gboolean nautilus_file_should_show_thumbnail (NautilusFile *file);
+gboolean nautilus_file_should_show_directory_item_count (NautilusFile *file);
+
+void nautilus_file_set_search_relevance (NautilusFile *file,
+ gdouble relevance);
+void nautilus_file_set_search_fts_snippet (NautilusFile *file,
+ const gchar *fts_snippet);
+const gchar* nautilus_file_get_search_fts_snippet (NautilusFile *file);
+
+void nautilus_file_set_attributes (NautilusFile *file,
+ GFileInfo *attributes,
+ NautilusFileOperationCallback callback,
+ gpointer callback_data);
+GFilesystemPreviewType nautilus_file_get_filesystem_use_preview (NautilusFile *file);
+
+char * nautilus_file_get_filesystem_id (NautilusFile *file);
+
+char * nautilus_file_get_filesystem_type (NautilusFile *file);
+
+gboolean nautilus_file_get_filesystem_remote (NautilusFile *file);
+
+NautilusFile * nautilus_file_get_trash_original_file (NautilusFile *file);
+
+/* Permissions. */
+gboolean nautilus_file_can_get_permissions (NautilusFile *file);
+gboolean nautilus_file_can_set_permissions (NautilusFile *file);
+guint nautilus_file_get_permissions (NautilusFile *file);
+gboolean nautilus_file_can_get_owner (NautilusFile *file);
+gboolean nautilus_file_can_set_owner (NautilusFile *file);
+gboolean nautilus_file_can_get_group (NautilusFile *file);
+gboolean nautilus_file_can_set_group (NautilusFile *file);
+const uid_t nautilus_file_get_uid (NautilusFile *file);
+char * nautilus_file_get_owner_name (NautilusFile *file);
+const gid_t nautilus_file_get_gid (NautilusFile *file);
+char * nautilus_file_get_group_name (NautilusFile *file);
+GList * nautilus_get_user_names (void);
+GList * nautilus_get_all_group_names (void);
+GList * nautilus_file_get_settable_group_names (NautilusFile *file);
+gboolean nautilus_file_can_get_selinux_context (NautilusFile *file);
+char * nautilus_file_get_selinux_context (NautilusFile *file);
+
+/* "Capabilities". */
+gboolean nautilus_file_can_read (NautilusFile *file);
+gboolean nautilus_file_can_write (NautilusFile *file);
+gboolean nautilus_file_can_execute (NautilusFile *file);
+gboolean nautilus_file_can_rename (NautilusFile *file);
+gboolean nautilus_file_can_delete (NautilusFile *file);
+gboolean nautilus_file_can_trash (NautilusFile *file);
+
+gboolean nautilus_file_can_mount (NautilusFile *file);
+gboolean nautilus_file_can_unmount (NautilusFile *file);
+gboolean nautilus_file_can_eject (NautilusFile *file);
+gboolean nautilus_file_can_start (NautilusFile *file);
+gboolean nautilus_file_can_start_degraded (NautilusFile *file);
+gboolean nautilus_file_can_stop (NautilusFile *file);
+GDriveStartStopType nautilus_file_get_start_stop_type (NautilusFile *file);
+gboolean nautilus_file_can_poll_for_media (NautilusFile *file);
+gboolean nautilus_file_is_media_check_automatic (NautilusFile *file);
+
+void nautilus_file_mount (NautilusFile *file,
+ GMountOperation *mount_op,
+ GCancellable *cancellable,
+ NautilusFileOperationCallback callback,
+ gpointer callback_data);
+void nautilus_file_unmount (NautilusFile *file,
+ GMountOperation *mount_op,
+ GCancellable *cancellable,
+ NautilusFileOperationCallback callback,
+ gpointer callback_data);
+void nautilus_file_eject (NautilusFile *file,
+ GMountOperation *mount_op,
+ GCancellable *cancellable,
+ NautilusFileOperationCallback callback,
+ gpointer callback_data);
+
+void nautilus_file_start (NautilusFile *file,
+ GMountOperation *start_op,
+ GCancellable *cancellable,
+ NautilusFileOperationCallback callback,
+ gpointer callback_data);
+void nautilus_file_stop (NautilusFile *file,
+ GMountOperation *mount_op,
+ GCancellable *cancellable,
+ NautilusFileOperationCallback callback,
+ gpointer callback_data);
+void nautilus_file_poll_for_media (NautilusFile *file);
+
+/* Basic operations for file objects. */
+void nautilus_file_set_owner (NautilusFile *file,
+ const char *user_name_or_id,
+ NautilusFileOperationCallback callback,
+ gpointer callback_data);
+void nautilus_file_set_group (NautilusFile *file,
+ const char *group_name_or_id,
+ NautilusFileOperationCallback callback,
+ gpointer callback_data);
+void nautilus_file_set_permissions (NautilusFile *file,
+ guint32 permissions,
+ NautilusFileOperationCallback callback,
+ gpointer callback_data);
+void nautilus_file_rename (NautilusFile *file,
+ const char *new_name,
+ NautilusFileOperationCallback callback,
+ gpointer callback_data);
+void nautilus_file_batch_rename (GList *files,
+ GList *new_names,
+ NautilusFileOperationCallback callback,
+ gpointer callback_data);
+void nautilus_file_cancel (NautilusFile *file,
+ NautilusFileOperationCallback callback,
+ gpointer callback_data);
+
+/* Return true if this file has already been deleted.
+ * This object will be unref'd after sending the files_removed signal,
+ * but it could hang around longer if someone ref'd it.
+ */
+gboolean nautilus_file_is_gone (NautilusFile *file);
+
+/* Used in subclasses that handles the rename of a file. This handles the case
+ * when the file is gone. If this returns TRUE, simply do nothing
+ */
+gboolean nautilus_file_rename_handle_file_gone (NautilusFile *file,
+ NautilusFileOperationCallback callback,
+ gpointer callback_data);
+
+/* Return true if this file is not confirmed to have ever really
+ * existed. This is true when the NautilusFile object has been created, but no I/O
+ * has yet confirmed the existence of a file by that name.
+ */
+gboolean nautilus_file_is_not_yet_confirmed (NautilusFile *file);
+
+/* Simple getting and setting top-level metadata. */
+char * nautilus_file_get_metadata (NautilusFile *file,
+ const char *key,
+ const char *default_metadata);
+gchar ** nautilus_file_get_metadata_list (NautilusFile *file,
+ const char *key);
+void nautilus_file_set_metadata (NautilusFile *file,
+ const char *key,
+ const char *default_metadata,
+ const char *metadata);
+void nautilus_file_set_metadata_list (NautilusFile *file,
+ const char *key,
+ gchar **list);
+
+/* Covers for common data types. */
+gboolean nautilus_file_get_boolean_metadata (NautilusFile *file,
+ const char *key,
+ gboolean default_metadata);
+void nautilus_file_set_boolean_metadata (NautilusFile *file,
+ const char *key,
+ gboolean default_metadata,
+ gboolean metadata);
+int nautilus_file_get_integer_metadata (NautilusFile *file,
+ const char *key,
+ int default_metadata);
+void nautilus_file_set_integer_metadata (NautilusFile *file,
+ const char *key,
+ int default_metadata,
+ int metadata);
+
+#define UNDEFINED_TIME ((time_t) (-1))
+
+time_t nautilus_file_get_time_metadata (NautilusFile *file,
+ const char *key);
+void nautilus_file_set_time_metadata (NautilusFile *file,
+ const char *key,
+ time_t time);
+
+
+/* Attributes for file objects as user-displayable strings. */
+char * nautilus_file_get_string_attribute (NautilusFile *file,
+ const char *attribute_name);
+char * nautilus_file_get_string_attribute_q (NautilusFile *file,
+ GQuark attribute_q);
+char * nautilus_file_get_string_attribute_with_default (NautilusFile *file,
+ const char *attribute_name);
+char * nautilus_file_get_string_attribute_with_default_q (NautilusFile *file,
+ GQuark attribute_q);
+
+/* Matching with another URI. */
+gboolean nautilus_file_matches_uri (NautilusFile *file,
+ const char *uri);
+
+gboolean nautilus_file_has_local_path (NautilusFile *file);
+
+/* Comparing two file objects for sorting */
+NautilusFileSortType nautilus_file_get_default_sort_type (NautilusFile *file,
+ gboolean *reversed);
+
+int nautilus_file_compare_for_sort (NautilusFile *file_1,
+ NautilusFile *file_2,
+ NautilusFileSortType sort_type,
+ gboolean directories_first,
+ gboolean reversed);
+int nautilus_file_compare_for_sort_by_attribute (NautilusFile *file_1,
+ NautilusFile *file_2,
+ const char *attribute,
+ gboolean directories_first,
+ gboolean reversed);
+int nautilus_file_compare_for_sort_by_attribute_q (NautilusFile *file_1,
+ NautilusFile *file_2,
+ GQuark attribute,
+ gboolean directories_first,
+ gboolean reversed);
+gboolean nautilus_file_is_date_sort_attribute_q (GQuark attribute);
+
+int nautilus_file_compare_location (NautilusFile *file_1,
+ NautilusFile *file_2);
+
+/* Compare display name of file with string for equality */
+int nautilus_file_compare_display_name (NautilusFile *file,
+ const char *string);
+
+/* filtering functions for use by various directory views */
+gboolean nautilus_file_is_hidden_file (NautilusFile *file);
+gboolean nautilus_file_should_show (NautilusFile *file,
+ gboolean show_hidden);
+GList *nautilus_file_list_filter_hidden (GList *files,
+ gboolean show_hidden);
+
+
+/* Get the URI that's used when activating the file.
+ * Getting this can require reading the contents of the file.
+ */
+gboolean nautilus_file_has_activation_uri (NautilusFile *file);
+char * nautilus_file_get_activation_uri (NautilusFile *file);
+GFile * nautilus_file_get_activation_location (NautilusFile *file);
+GIcon * nautilus_file_get_gicon (NautilusFile *file,
+ NautilusFileIconFlags flags);
+NautilusIconInfo * nautilus_file_get_icon (NautilusFile *file,
+ int size,
+ int scale,
+ NautilusFileIconFlags flags);
+GdkTexture * nautilus_file_get_icon_texture (NautilusFile *file,
+ int size,
+ int scale,
+ NautilusFileIconFlags flags);
+GdkPaintable * nautilus_file_get_icon_paintable (NautilusFile *file,
+ int size,
+ int scale,
+ NautilusFileIconFlags flags);
+
+GList * nautilus_file_get_emblem_icons (NautilusFile *file);
+
+/* Whether the file should open inside a view */
+gboolean nautilus_file_opens_in_view (NautilusFile *file);
+/* Thumbnailing handling */
+gboolean nautilus_file_is_thumbnailing (NautilusFile *file);
+
+/* Convenience functions for dealing with a list of NautilusFile objects that each have a ref.
+ * These are just convenient names for functions that work on lists of GtkObject *.
+ */
+GList * nautilus_file_list_ref (GList *file_list);
+void nautilus_file_list_unref (GList *file_list);
+void nautilus_file_list_free (GList *file_list);
+GList * nautilus_file_list_copy (GList *file_list);
+GList * nautilus_file_list_sort_by_display_name (GList *file_list);
+void nautilus_file_list_call_when_ready (GList *file_list,
+ NautilusFileAttributes attributes,
+ NautilusFileListHandle **handle,
+ NautilusFileListCallback callback,
+ gpointer callback_data);
+void nautilus_file_list_cancel_call_when_ready (NautilusFileListHandle *handle);
+
+GList * nautilus_file_list_filter (GList *files,
+ GList **failed,
+ NautilusFileFilterFunc filter_function,
+ gpointer user_data);
+gboolean nautilus_file_list_are_all_folders (const GList *files);
+
+/* Debugging */
+void nautilus_file_dump (NautilusFile *file);
+
+typedef struct NautilusFileDetails NautilusFileDetails;
+
+struct NautilusFile {
+ GObject parent_slot;
+ NautilusFileDetails *details;
+};
+
+/* This is actually a "protected" type, but it must be here so we can
+ * compile the get_date function pointer declaration below.
+ */
+typedef enum {
+ NAUTILUS_DATE_TYPE_MODIFIED,
+ NAUTILUS_DATE_TYPE_ACCESSED,
+ NAUTILUS_DATE_TYPE_CREATED,
+ NAUTILUS_DATE_TYPE_TRASHED,
+ NAUTILUS_DATE_TYPE_RECENCY
+} NautilusDateType;
+
+gboolean nautilus_file_get_date (NautilusFile *file,
+ NautilusDateType date_type,
+ time_t *date);
+
+typedef struct {
+ GObjectClass parent_slot;
+
+ /* Subclasses can set this to something other than G_FILE_TYPE_UNKNOWN and
+ it will be used as the default file type. This is useful when creating
+ a "virtual" NautilusFile subclass that you can't actually get real
+ information about. For exaple NautilusDesktopDirectoryFile. */
+ GFileType default_file_type;
+
+ /* Called when the file notices any change. */
+ void (* changed) (NautilusFile *file);
+
+ /* Called periodically while directory deep count is being computed. */
+ void (* updated_deep_count_in_progress) (NautilusFile *file);
+
+ /* Virtual functions (mainly used for trash directory). */
+ void (* monitor_add) (NautilusFile *file,
+ gconstpointer client,
+ NautilusFileAttributes attributes);
+ void (* monitor_remove) (NautilusFile *file,
+ gconstpointer client);
+ void (* call_when_ready) (NautilusFile *file,
+ NautilusFileAttributes attributes,
+ NautilusFileCallback callback,
+ gpointer callback_data);
+ void (* cancel_call_when_ready) (NautilusFile *file,
+ NautilusFileCallback callback,
+ gpointer callback_data);
+ gboolean (* check_if_ready) (NautilusFile *file,
+ NautilusFileAttributes attributes);
+ gboolean (* get_item_count) (NautilusFile *file,
+ guint *count,
+ gboolean *count_unreadable);
+ NautilusRequestStatus (* get_deep_counts) (NautilusFile *file,
+ guint *directory_count,
+ guint *file_count,
+ guint *unreadable_directory_count,
+ goffset *total_size);
+ gboolean (* get_date) (NautilusFile *file,
+ NautilusDateType type,
+ time_t *date);
+ char * (* get_where_string) (NautilusFile *file);
+
+ void (* set_metadata) (NautilusFile *file,
+ const char *key,
+ const char *value);
+ void (* set_metadata_as_list) (NautilusFile *file,
+ const char *key,
+ char **value);
+
+ void (* mount) (NautilusFile *file,
+ GMountOperation *mount_op,
+ GCancellable *cancellable,
+ NautilusFileOperationCallback callback,
+ gpointer callback_data);
+ void (* unmount) (NautilusFile *file,
+ GMountOperation *mount_op,
+ GCancellable *cancellable,
+ NautilusFileOperationCallback callback,
+ gpointer callback_data);
+ void (* eject) (NautilusFile *file,
+ GMountOperation *mount_op,
+ GCancellable *cancellable,
+ NautilusFileOperationCallback callback,
+ gpointer callback_data);
+
+ void (* start) (NautilusFile *file,
+ GMountOperation *start_op,
+ GCancellable *cancellable,
+ NautilusFileOperationCallback callback,
+ gpointer callback_data);
+ void (* stop) (NautilusFile *file,
+ GMountOperation *mount_op,
+ GCancellable *cancellable,
+ NautilusFileOperationCallback callback,
+ gpointer callback_data);
+
+ void (* poll_for_media) (NautilusFile *file);
+} NautilusFileClass;
diff --git a/src/nautilus-files-view-dnd.c b/src/nautilus-files-view-dnd.c
new file mode 100644
index 0000000..96a5c82
--- /dev/null
+++ b/src/nautilus-files-view-dnd.c
@@ -0,0 +1,362 @@
+/*
+ * nautilus-view-dnd.c: DnD helpers for NautilusFilesView
+ *
+ * Copyright (C) 1999, 2000 Free Software Foundaton
+ * 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
+ * Darin Adler <darin@bentspoon.com>
+ * John Sullivan <sullivan@eazel.com>
+ * Pavel Cisler <pavel@eazel.com>
+ */
+
+#include <config.h>
+
+#include "nautilus-files-view-dnd.h"
+
+#include "nautilus-files-view.h"
+#include "nautilus-application.h"
+
+#include <eel/eel-string.h>
+#include <eel/eel-vfs-extensions.h>
+
+#include <glib/gi18n.h>
+
+#include "nautilus-clipboard.h"
+#include "nautilus-dnd.h"
+#include "nautilus-global-preferences.h"
+#include "nautilus-ui-utilities.h"
+
+#define GET_ANCESTOR(obj) \
+ GTK_WINDOW (gtk_widget_get_ancestor (GTK_WIDGET (obj), GTK_TYPE_WINDOW))
+
+void
+nautilus_files_view_handle_uri_list_drop (NautilusFilesView *view,
+ const char *item_uris,
+ const char *target_uri,
+ GdkDragAction action)
+{
+ gchar **uri_list;
+ GList *real_uri_list = NULL;
+ char *container_uri;
+ const char *real_target_uri;
+ int n_uris, i;
+
+ if (item_uris == NULL)
+ {
+ return;
+ }
+
+ container_uri = NULL;
+ if (target_uri == NULL)
+ {
+ container_uri = nautilus_files_view_get_backing_uri (view);
+ g_assert (container_uri != NULL);
+ }
+
+ if (action == GDK_ACTION_ASK)
+ {
+#if 0 && NAUTILUS_DND_NEEDS_GTK4_REIMPLEMENTATION
+ action = nautilus_drag_drop_action_ask
+ (GTK_WIDGET (view),
+ GDK_ACTION_MOVE | GDK_ACTION_COPY | GDK_ACTION_LINK);
+ if (action == 0)
+#endif
+ {
+ g_free (container_uri);
+ return;
+ }
+ }
+
+ if ((action != GDK_ACTION_COPY) &&
+ (action != GDK_ACTION_MOVE) &&
+ (action != GDK_ACTION_LINK))
+ {
+ show_dialog (_("Drag and drop is not supported."),
+ _("An invalid drag type was used."),
+ GET_ANCESTOR (view),
+ GTK_MESSAGE_WARNING);
+ g_free (container_uri);
+ return;
+ }
+
+ n_uris = 0;
+ uri_list = g_uri_list_extract_uris (item_uris);
+ for (i = 0; uri_list[i] != NULL; i++)
+ {
+ real_uri_list = g_list_append (real_uri_list, uri_list[i]);
+ n_uris++;
+ }
+ g_free (uri_list);
+
+ /* do nothing if no real uris are left */
+ if (n_uris == 0)
+ {
+ g_free (container_uri);
+ return;
+ }
+
+ real_target_uri = target_uri != NULL ? target_uri : container_uri;
+
+ nautilus_files_view_move_copy_items (view, real_uri_list,
+ real_target_uri,
+ action);
+
+ g_list_free_full (real_uri_list, g_free);
+
+ g_free (container_uri);
+}
+
+#define MAX_LEN_FILENAME 64
+#define MIN_LEN_FILENAME 8
+
+static char *
+get_drop_filename (const char *text)
+{
+ char *filename;
+ char trimmed[MAX_LEN_FILENAME];
+ int i;
+ int last_word = -1;
+ int end_sentence = -1;
+ int last_nonspace = -1;
+ int start_sentence = -1;
+ int num_attrs;
+ PangoLogAttr *attrs;
+ gchar *current_char;
+
+ num_attrs = MIN (g_utf8_strlen (text, -1), MAX_LEN_FILENAME) + 1;
+ attrs = g_new (PangoLogAttr, num_attrs);
+ g_utf8_strncpy (trimmed, text, num_attrs - 1);
+ pango_get_log_attrs (trimmed, -1, -1, pango_language_get_default (), attrs, num_attrs);
+
+ /* since the end of the text will always match a word boundary don't include it */
+ for (i = 0; (i < num_attrs - 1); i++)
+ {
+ if (attrs[i].is_sentence_start && start_sentence == -1)
+ {
+ start_sentence = i;
+ }
+ if (!attrs[i].is_white)
+ {
+ last_nonspace = i;
+ }
+ if (attrs[i].is_word_boundary)
+ {
+ last_word = last_nonspace;
+ }
+ if (attrs[i].is_sentence_end)
+ {
+ end_sentence = last_nonspace;
+ break;
+ }
+ }
+ g_free (attrs);
+
+ if (end_sentence > 0)
+ {
+ i = end_sentence;
+ }
+ else
+ {
+ i = last_word;
+ }
+
+ if (i - start_sentence > MIN_LEN_FILENAME)
+ {
+ g_autofree char *substring = g_utf8_substring (trimmed, start_sentence, i);
+ filename = g_strdup_printf ("%s.txt", substring);
+ }
+ else
+ {
+ /* Translator: This is the filename used for when you dnd text to a directory */
+ filename = g_strdup (_("Dropped Text.txt"));
+ }
+
+ /* Remove any invalid characters */
+ for (current_char = filename;
+ *current_char;
+ current_char = g_utf8_next_char (current_char))
+ {
+ if (G_IS_DIR_SEPARATOR (g_utf8_get_char (current_char)))
+ {
+ *current_char = '-';
+ }
+ }
+
+ return filename;
+}
+
+void
+nautilus_files_view_handle_text_drop (NautilusFilesView *view,
+ const char *text,
+ const char *target_uri,
+ GdkDragAction action)
+{
+ int length;
+ char *container_uri;
+ char *filename;
+
+ if (text == NULL)
+ {
+ return;
+ }
+
+ g_return_if_fail (action == GDK_ACTION_COPY);
+
+ container_uri = NULL;
+ if (target_uri == NULL)
+ {
+ container_uri = nautilus_files_view_get_backing_uri (view);
+ g_assert (container_uri != NULL);
+ }
+
+ length = strlen (text);
+
+ /* try to get text to use as a filename */
+ filename = get_drop_filename (text);
+
+ nautilus_files_view_new_file_with_initial_contents (view,
+ target_uri != NULL ? target_uri : container_uri,
+ filename,
+ text,
+ length);
+ g_free (filename);
+ g_free (container_uri);
+}
+
+void
+nautilus_files_view_handle_raw_drop (NautilusFilesView *view,
+ const char *raw_data,
+ int length,
+ const char *target_uri,
+ const char *direct_save_uri,
+ GdkDragAction action)
+{
+ char *container_uri, *filename;
+ GFile *direct_save_full;
+
+ if (raw_data == NULL)
+ {
+ return;
+ }
+
+ g_return_if_fail (action == GDK_ACTION_COPY);
+
+ container_uri = NULL;
+ if (target_uri == NULL)
+ {
+ container_uri = nautilus_files_view_get_backing_uri (view);
+ g_assert (container_uri != NULL);
+ }
+
+ filename = NULL;
+ if (direct_save_uri != NULL)
+ {
+ direct_save_full = g_file_new_for_uri (direct_save_uri);
+ filename = g_file_get_basename (direct_save_full);
+ }
+ if (filename == NULL)
+ {
+ /* Translator: This is the filename used for when you dnd raw
+ * data to a directory, if the source didn't supply a name.
+ */
+ filename = g_strdup (_("dropped data"));
+ }
+
+ nautilus_files_view_new_file_with_initial_contents (
+ view, target_uri != NULL ? target_uri : container_uri,
+ filename, raw_data, length);
+
+ g_free (container_uri);
+ g_free (filename);
+}
+
+void
+nautilus_files_view_drop_proxy_received_uris (NautilusFilesView *view,
+ const GList *source_uri_list,
+ const char *target_uri,
+ GdkDragAction action)
+{
+ g_autofree char *container_uri = NULL;
+ g_autoptr (GFile) source_location = g_file_new_for_uri (source_uri_list->data);
+ g_autoptr (GFile) target_location = g_file_new_for_uri (target_uri);
+
+ if (target_uri == NULL)
+ {
+ container_uri = nautilus_files_view_get_backing_uri (view);
+ g_assert (container_uri != NULL);
+ }
+ if (g_file_has_parent (source_location, target_location) &&
+ action & GDK_ACTION_MOVE)
+ {
+ /* By default dragging to the same directory is allowed so that
+ * users can duplicate a file using the CTRL modifier key. Prevent
+ * an accidental MOVE, by rejecting what would be an error anyways. */
+ return;
+ }
+
+ if (action == GDK_ACTION_ASK)
+ {
+#if 0 && NAUTILUS_DND_NEEDS_GTK4_REIMPLEMENTATION
+ action = nautilus_drag_drop_action_ask
+ (GTK_WIDGET (view),
+ GDK_ACTION_MOVE | GDK_ACTION_COPY | GDK_ACTION_LINK);
+ if (action == 0)
+#endif
+ {
+ return;
+ }
+ }
+
+#if 0 && NAUTILUS_DND_NEEDS_GTK4_REIMPLEMENTATION
+ nautilus_clipboard_clear_if_colliding_uris (GTK_WIDGET (view),
+ source_uri_list);
+#endif
+ nautilus_files_view_move_copy_items (view, source_uri_list,
+ target_uri != NULL ? target_uri : container_uri,
+ action);
+}
+
+void
+nautilus_files_view_handle_hover (NautilusFilesView *view,
+ const char *target_uri)
+{
+ NautilusWindowSlot *slot;
+ GFile *location;
+ GFile *current_location;
+ NautilusFile *target_file;
+ gboolean target_is_dir;
+ gboolean open_folder_on_hover;
+
+ slot = nautilus_files_view_get_nautilus_window_slot (view);
+
+ location = g_file_new_for_uri (target_uri);
+ target_file = nautilus_file_get_existing (location);
+ target_is_dir = nautilus_file_get_file_type (target_file) == G_FILE_TYPE_DIRECTORY;
+ current_location = nautilus_window_slot_get_location (slot);
+ open_folder_on_hover = g_settings_get_boolean (nautilus_preferences,
+ NAUTILUS_PREFERENCES_OPEN_FOLDER_ON_DND_HOVER);
+
+ if (target_is_dir && open_folder_on_hover &&
+ !(current_location != NULL && g_file_equal (location, current_location)))
+ {
+ nautilus_application_open_location_full (NAUTILUS_APPLICATION (g_application_get_default ()),
+ location, NAUTILUS_OPEN_FLAG_DONT_MAKE_ACTIVE,
+ NULL, NULL, slot);
+ }
+ g_object_unref (location);
+ nautilus_file_unref (target_file);
+}
diff --git a/src/nautilus-files-view-dnd.h b/src/nautilus-files-view-dnd.h
new file mode 100644
index 0000000..9cde154
--- /dev/null
+++ b/src/nautilus-files-view-dnd.h
@@ -0,0 +1,51 @@
+
+/*
+ * nautilus-view-dnd.h: DnD helpers for NautilusFilesView
+ *
+ * Copyright (C) 1999, 2000 Free Software Foundaton
+ * 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
+ * Darin Adler <darin@bentspoon.com>
+ * John Sullivan <sullivan@eazel.com>
+ * Pavel Cisler <pavel@eazel.com>
+ */
+
+#pragma once
+
+#include "nautilus-files-view.h"
+
+void nautilus_files_view_handle_uri_list_drop (NautilusFilesView *view,
+ const char *item_uris,
+ const char *target_uri,
+ GdkDragAction action);
+void nautilus_files_view_handle_text_drop (NautilusFilesView *view,
+ const char *text,
+ const char *target_uri,
+ GdkDragAction action);
+void nautilus_files_view_handle_raw_drop (NautilusFilesView *view,
+ const char *raw_data,
+ int length,
+ const char *target_uri,
+ const char *direct_save_uri,
+ GdkDragAction action);
+void nautilus_files_view_handle_hover (NautilusFilesView *view,
+ const char *target_uri);
+
+void nautilus_files_view_drop_proxy_received_uris (NautilusFilesView *view,
+ const GList *uris,
+ const char *target_location,
+ GdkDragAction action);
diff --git a/src/nautilus-files-view.c b/src/nautilus-files-view.c
new file mode 100644
index 0000000..13f256a
--- /dev/null
+++ b/src/nautilus-files-view.c
@@ -0,0 +1,9880 @@
+/* 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>
+ */
+
+#include "nautilus-files-view.h"
+
+#include <eel/eel-glib-extensions.h>
+#include <eel/eel-stock-dialogs.h>
+#include <eel/eel-string.h>
+#include <eel/eel-vfs-extensions.h>
+#include <fcntl.h>
+#include <gdesktop-enums.h>
+#include <gdk/gdkkeysyms.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 <math.h>
+#include <nautilus-extension.h>
+#include <string.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#define DEBUG_FLAG NAUTILUS_DEBUG_DIRECTORY_VIEW
+#include "nautilus-debug.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-controller.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-changes-queue.h"
+#include "nautilus-file-name-widget-controller.h"
+#include "nautilus-file-operations.h"
+#include "nautilus-file-private.h"
+#include "nautilus-file-undo-manager.h"
+#include "nautilus-file-utilities.h"
+#include "nautilus-file.h"
+#include "nautilus-floating-bar.h"
+#include "nautilus-global-preferences.h"
+#include "nautilus-icon-info.h"
+#include "nautilus-icon-names.h"
+#include "nautilus-list-view.h"
+#include "nautilus-metadata.h"
+#include "nautilus-mime-actions.h"
+#include "nautilus-module.h"
+#include "nautilus-new-folder-dialog-controller.h"
+#include "nautilus-previewer.h"
+#include "nautilus-profile.h"
+#include "nautilus-program-choosing.h"
+#include "nautilus-properties-window.h"
+#include "nautilus-rename-file-popover-controller.h"
+#include "nautilus-search-directory.h"
+#include "nautilus-signaller.h"
+#include "nautilus-tag-manager.h"
+#include "nautilus-toolbar.h"
+#include "nautilus-trash-monitor.h"
+#include "nautilus-ui-utilities.h"
+#include "nautilus-view.h"
+#include "nautilus-grid-view.h"
+#include "nautilus-window.h"
+#include "nautilus-tracker-utilities.h"
+
+/* Minimum starting update inverval */
+#define UPDATE_INTERVAL_MIN 100
+/* Maximum update interval */
+#define UPDATE_INTERVAL_MAX 2000
+/* Amount of miliseconds 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 DUPLICATE_HORIZONTAL_ICON_OFFSET 70
+#define DUPLICATE_VERTICAL_ICON_OFFSET 30
+
+#define MAX_QUEUED_UPDATES 500
+
+#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 */
+
+#define MIN_COMMON_FILENAME_PREFIX_LENGTH 4
+
+enum
+{
+ ADD_FILES,
+ BEGIN_FILE_CHANGES,
+ BEGIN_LOADING,
+ CLEAR,
+ END_FILE_CHANGES,
+ END_LOADING,
+ FILE_CHANGED,
+ MOVE_COPY_ITEMS,
+ REMOVE_FILE,
+ SELECTION_CHANGED,
+ TRASH,
+ DELETE,
+ LAST_SIGNAL
+};
+
+enum
+{
+ PROP_WINDOW_SLOT = 1,
+ PROP_SUPPORTS_ZOOMING,
+ PROP_ICON,
+ PROP_SEARCHING,
+ PROP_LOADING,
+ PROP_SELECTION,
+ PROP_LOCATION,
+ PROP_SEARCH_QUERY,
+ PROP_EXTENSIONS_BACKGROUND_MENU,
+ PROP_TEMPLATES_MENU,
+ NUM_PROPERTIES
+};
+
+static guint signals[LAST_SIGNAL];
+
+static char *scripts_directory_uri = NULL;
+static int scripts_directory_uri_length;
+
+static GHashTable *script_accels = NULL;
+
+typedef struct
+{
+ /* Main components */
+ GtkWidget *overlay;
+
+ NautilusWindowSlot *slot;
+ NautilusDirectory *model;
+ NautilusFile *directory_as_file;
+ GFile *location;
+ guint dir_merge_id;
+
+ NautilusQuery *search_query;
+
+ NautilusRenameFilePopoverController *rename_file_controller;
+ NautilusNewFolderDialogController *new_folder_controller;
+ NautilusCompressDialogController *compress_controller;
+
+ gboolean supports_zooming;
+
+ 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 reveal_selection_idle_id;
+
+ 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;
+ GHashTable *non_ready_files;
+ GList *old_added_files;
+ GList *old_changed_files;
+
+ GList *pending_selection;
+ GHashTable *pending_reveal;
+
+ /* 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 sort_directories_first;
+
+ gboolean show_hidden_files;
+ gboolean ignore_hidden_file_preferences;
+
+ gboolean selection_was_removed;
+
+ gboolean metadata_for_directory_as_file_pending;
+ gboolean metadata_for_files_in_directory_pending;
+
+ GList *subdirectory_list;
+
+ GMenu *selection_menu_model;
+ GMenu *background_menu_model;
+
+ GtkWidget *selection_menu;
+ GtkWidget *background_menu;
+
+ GActionGroup *view_action_group;
+
+ GtkWidget *stack;
+
+ GtkWidget *scrolled_window;
+
+ /* 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;
+
+ gulong name_accepted_handler_id;
+ gulong cancelled_handler_id;
+} NautilusFilesViewPrivate;
+
+/**
+ * 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;
+ GList *selection;
+} CompressCallbackData;
+
+/* forward declarations */
+
+static gboolean display_selection_info_idle_callback (gpointer data);
+static void trash_or_delete_files (GtkWindow *parent_window,
+ const GList *files,
+ NautilusFilesView *view);
+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_model_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 nautilus_files_view_select_file (NautilusFilesView *view,
+ NautilusFile *file);
+
+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 void nautilus_files_view_check_empty_states (NautilusFilesView *view);
+
+static gboolean nautilus_files_view_is_searching (NautilusView *view);
+
+static void nautilus_files_view_iface_init (NautilusViewInterface *view);
+
+static void set_search_query_internal (NautilusFilesView *files_view,
+ NautilusQuery *query,
+ NautilusDirectory *base_model);
+
+static gboolean nautilus_files_view_is_read_only (NautilusFilesView *view);
+
+G_DEFINE_TYPE_WITH_CODE (NautilusFilesView,
+ nautilus_files_view,
+ ADW_TYPE_BIN,
+ G_IMPLEMENT_INTERFACE (NAUTILUS_TYPE_VIEW, nautilus_files_view_iface_init)
+ G_ADD_PRIVATE (NautilusFilesView));
+
+/* Clipboard async helpers. */
+static void
+clipboard_read_value_callback (GObject *source_object,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ GdkClipboard *clipboard = GDK_CLIPBOARD (source_object);
+ g_autoptr (GTask) task = user_data;
+ g_autoptr (GError) error = NULL;
+ const GValue *value;
+
+ value = gdk_clipboard_read_value_finish (clipboard, result, &error);
+
+ if (error != NULL)
+ {
+ g_task_return_error (task, g_steal_pointer (&error));
+ }
+ else
+ {
+ g_warn_if_fail (G_VALUE_HOLDS (value, NAUTILUS_TYPE_CLIPBOARD));
+
+ g_task_return_pointer (task, g_value_get_boxed (value), NULL);
+ }
+}
+
+void
+nautilus_files_view_get_clipboard_async (NautilusFilesView *self,
+ GAsyncReadyCallback callback,
+ gpointer callback_data)
+{
+ NautilusFilesViewPrivate *priv = nautilus_files_view_get_instance_private (self);
+ GTask *task;
+
+ if (priv->clipboard_cancellable == NULL)
+ {
+ priv->clipboard_cancellable = g_cancellable_new ();
+ }
+
+ task = g_task_new (self,
+ priv->clipboard_cancellable,
+ callback,
+ callback_data);
+ gdk_clipboard_read_value_async (gtk_widget_get_clipboard (GTK_WIDGET (self)),
+ NAUTILUS_TYPE_CLIPBOARD,
+ G_PRIORITY_DEFAULT,
+ priv->clipboard_cancellable,
+ clipboard_read_value_callback,
+ task);
+}
+
+NautilusClipboard *
+nautilus_files_view_get_clipboard_finish (NautilusFilesView *self,
+ GAsyncResult *result,
+ GError **error)
+{
+ return g_task_propagate_pointer (G_TASK (result), error);
+}
+
+/*
+ * Floating Bar code
+ */
+static void
+remove_loading_floating_bar (NautilusFilesView *view)
+{
+ NautilusFilesViewPrivate *priv;
+
+ priv = nautilus_files_view_get_instance_private (view);
+
+ if (priv->floating_bar_loading_timeout_id != 0)
+ {
+ g_source_remove (priv->floating_bar_loading_timeout_id);
+ priv->floating_bar_loading_timeout_id = 0;
+ }
+
+ gtk_widget_hide (priv->floating_bar);
+ nautilus_floating_bar_set_show_stop (NAUTILUS_FLOATING_BAR (priv->floating_bar), FALSE);
+}
+
+static void
+real_setup_loading_floating_bar (NautilusFilesView *view)
+{
+ NautilusFilesViewPrivate *priv;
+
+ priv = nautilus_files_view_get_instance_private (view);
+
+ nautilus_floating_bar_set_primary_label (NAUTILUS_FLOATING_BAR (priv->floating_bar),
+ nautilus_view_is_searching (NAUTILUS_VIEW (view)) ? _("Searching…") : _("Loading…"));
+ nautilus_floating_bar_set_details_label (NAUTILUS_FLOATING_BAR (priv->floating_bar), NULL);
+ nautilus_floating_bar_set_show_spinner (NAUTILUS_FLOATING_BAR (priv->floating_bar), priv->loading);
+ nautilus_floating_bar_set_show_stop (NAUTILUS_FLOATING_BAR (priv->floating_bar), priv->loading);
+
+ gtk_widget_set_halign (priv->floating_bar, GTK_ALIGN_END);
+ gtk_widget_show (priv->floating_bar);
+}
+
+static gboolean
+setup_loading_floating_bar_timeout_cb (gpointer user_data)
+{
+ NautilusFilesView *view = user_data;
+ NautilusFilesViewPrivate *priv;
+
+ priv = nautilus_files_view_get_instance_private (view);
+
+ priv->floating_bar_loading_timeout_id = 0;
+ real_setup_loading_floating_bar (view);
+
+ return FALSE;
+}
+
+static void
+setup_loading_floating_bar (NautilusFilesView *view)
+{
+ NautilusFilesViewPrivate *priv;
+
+ priv = nautilus_files_view_get_instance_private (view);
+
+ /* setup loading overlay */
+ if (priv->floating_bar_set_status_timeout_id != 0)
+ {
+ g_source_remove (priv->floating_bar_set_status_timeout_id);
+ priv->floating_bar_set_status_timeout_id = 0;
+ }
+
+ if (priv->floating_bar_loading_timeout_id != 0)
+ {
+ g_source_remove (priv->floating_bar_loading_timeout_id);
+ priv->floating_bar_loading_timeout_id = 0;
+ }
+
+ priv->floating_bar_loading_timeout_id =
+ g_timeout_add (FLOATING_BAR_LOADING_DELAY, setup_loading_floating_bar_timeout_cb, view);
+}
+
+static void
+floating_bar_stop_cb (NautilusFloatingBar *floating_bar,
+ NautilusFilesView *view)
+{
+ NautilusFilesViewPrivate *priv;
+
+ priv = nautilus_files_view_get_instance_private (view);
+
+ remove_loading_floating_bar (view);
+ nautilus_window_slot_stop_loading (priv->slot);
+}
+
+static void
+real_floating_bar_set_short_status (NautilusFilesView *view,
+ const gchar *primary_status,
+ const gchar *detail_status)
+{
+ NautilusFilesViewPrivate *priv;
+
+ priv = nautilus_files_view_get_instance_private (view);
+
+ if (priv->loading)
+ {
+ return;
+ }
+
+ nautilus_floating_bar_set_show_spinner (NAUTILUS_FLOATING_BAR (priv->floating_bar),
+ FALSE);
+ nautilus_floating_bar_set_show_stop (NAUTILUS_FLOATING_BAR (priv->floating_bar),
+ FALSE);
+
+ if (primary_status == NULL && detail_status == NULL)
+ {
+ gtk_widget_hide (priv->floating_bar);
+ nautilus_floating_bar_remove_hover_timeout (NAUTILUS_FLOATING_BAR (priv->floating_bar));
+ return;
+ }
+
+ nautilus_floating_bar_set_labels (NAUTILUS_FLOATING_BAR (priv->floating_bar),
+ primary_status,
+ detail_status);
+
+ gtk_widget_show (priv->floating_bar);
+}
+
+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)
+{
+ NautilusFilesViewPrivate *priv;
+
+ FloatingBarSetStatusData *status_data = data;
+
+ priv = nautilus_files_view_get_instance_private (status_data->view);
+
+ priv->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)
+{
+ NautilusFilesViewPrivate *priv;
+
+ priv = nautilus_files_view_get_instance_private (NAUTILUS_FILES_VIEW (data));
+ gtk_widget_set_can_target (priv->floating_bar, TRUE);
+ priv->floating_bar_set_passthrough_timeout_id = 0;
+
+ return G_SOURCE_REMOVE;
+}
+
+static void
+set_floating_bar_status (NautilusFilesView *view,
+ const gchar *primary_status,
+ const gchar *detail_status)
+{
+ GtkSettings *settings;
+ gint double_click_time;
+ FloatingBarSetStatusData *status_data;
+ NautilusFilesViewPrivate *priv;
+
+ priv = nautilus_files_view_get_instance_private (view);
+
+ if (priv->floating_bar_set_status_timeout_id != 0)
+ {
+ g_source_remove (priv->floating_bar_set_status_timeout_id);
+ priv->floating_bar_set_status_timeout_id = 0;
+ }
+
+ settings = gtk_settings_get_for_display (gtk_widget_get_display (GTK_WIDGET (view)));
+ 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 = view;
+
+ if (priv->floating_bar_set_passthrough_timeout_id != 0)
+ {
+ g_source_remove (priv->floating_bar_set_passthrough_timeout_id);
+ priv->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 (priv->floating_bar, FALSE);
+ priv->floating_bar_set_passthrough_timeout_id = g_timeout_add ((guint) double_click_time,
+ remove_floating_bar_passthrough,
+ view);
+
+ /* 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.
+ */
+ priv->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);
+}
+
+static char *
+real_get_backing_uri (NautilusFilesView *view)
+{
+ NautilusFilesViewPrivate *priv;
+
+ g_return_val_if_fail (NAUTILUS_IS_FILES_VIEW (view), NULL);
+
+ priv = nautilus_files_view_get_instance_private (view);
+
+ if (priv->model == NULL)
+ {
+ return NULL;
+ }
+
+ return nautilus_directory_get_uri (priv->model);
+}
+
+/**
+ *
+ * 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 *view)
+{
+ g_return_val_if_fail (NAUTILUS_IS_FILES_VIEW (view), NULL);
+
+ return NAUTILUS_FILES_VIEW_CLASS (G_OBJECT_GET_CLASS (view))->get_backing_uri (view);
+}
+
+/**
+ * nautilus_files_view_select_all:
+ *
+ * select all the items in the view
+ *
+ **/
+static void
+nautilus_files_view_select_all (NautilusFilesView *view)
+{
+ g_return_if_fail (NAUTILUS_IS_FILES_VIEW (view));
+
+ NAUTILUS_FILES_VIEW_CLASS (G_OBJECT_GET_CLASS (view))->select_all (view);
+}
+
+static void
+nautilus_files_view_select_first (NautilusFilesView *view)
+{
+ g_return_if_fail (NAUTILUS_IS_FILES_VIEW (view));
+
+ NAUTILUS_FILES_VIEW_CLASS (G_OBJECT_GET_CLASS (view))->select_first (view);
+}
+
+static void
+nautilus_files_view_call_set_selection (NautilusFilesView *view,
+ GList *selection)
+{
+ g_return_if_fail (NAUTILUS_IS_FILES_VIEW (view));
+
+ NAUTILUS_FILES_VIEW_CLASS (G_OBJECT_GET_CLASS (view))->set_selection (view, selection);
+}
+
+static GList *
+nautilus_files_view_get_selection_for_file_transfer (NautilusFilesView *view)
+{
+ g_return_val_if_fail (NAUTILUS_IS_FILES_VIEW (view), NULL);
+
+ return NAUTILUS_FILES_VIEW_CLASS (G_OBJECT_GET_CLASS (view))->get_selection_for_file_transfer (view);
+}
+
+static void
+nautilus_files_view_invert_selection (NautilusFilesView *view)
+{
+ g_return_if_fail (NAUTILUS_IS_FILES_VIEW (view));
+
+ NAUTILUS_FILES_VIEW_CLASS (G_OBJECT_GET_CLASS (view))->invert_selection (view);
+}
+
+/**
+ * nautilus_files_view_reveal_selection:
+ *
+ * Scroll as necessary to reveal the selected items.
+ **/
+static void
+nautilus_files_view_reveal_selection (NautilusFilesView *view)
+{
+ g_return_if_fail (NAUTILUS_IS_FILES_VIEW (view));
+
+ NAUTILUS_FILES_VIEW_CLASS (G_OBJECT_GET_CLASS (view))->reveal_selection (view);
+}
+
+/**
+ * nautilus_files_view_get_toolbar_menu_sections:
+ * @view: 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
+ */
+static NautilusToolbarMenuSections *
+nautilus_files_view_get_toolbar_menu_sections (NautilusView *view)
+{
+ NautilusFilesViewPrivate *priv;
+
+ g_return_val_if_fail (NAUTILUS_IS_FILES_VIEW (view), NULL);
+
+ priv = nautilus_files_view_get_instance_private (NAUTILUS_FILES_VIEW (view));
+
+ return priv->toolbar_menu_sections;
+}
+
+static GMenuModel *
+nautilus_files_view_get_templates_menu (NautilusView *self)
+{
+ GMenuModel *menu;
+
+ g_object_get (self, "templates-menu", &menu, NULL);
+
+ return menu;
+}
+
+static GMenuModel *
+nautilus_files_view_get_extensions_background_menu (NautilusView *self)
+{
+ GMenuModel *menu;
+
+ g_object_get (self, "extensions-background-menu", &menu, NULL);
+
+ return menu;
+}
+
+static GMenuModel *
+real_get_extensions_background_menu (NautilusView *view)
+{
+ NautilusFilesViewPrivate *priv;
+
+ g_return_val_if_fail (NAUTILUS_IS_FILES_VIEW (view), NULL);
+
+ priv = nautilus_files_view_get_instance_private (NAUTILUS_FILES_VIEW (view));
+
+ return priv->extensions_background_menu;
+}
+
+static GMenuModel *
+real_get_templates_menu (NautilusView *view)
+{
+ NautilusFilesViewPrivate *priv;
+
+ g_return_val_if_fail (NAUTILUS_IS_FILES_VIEW (view), NULL);
+
+ priv = nautilus_files_view_get_instance_private (NAUTILUS_FILES_VIEW (view));
+
+ return priv->templates_menu;
+}
+
+static void
+nautilus_files_view_set_templates_menu (NautilusView *self,
+ GMenuModel *menu)
+{
+ g_object_set (self, "templates-menu", menu, NULL);
+}
+
+static void
+nautilus_files_view_set_extensions_background_menu (NautilusView *self,
+ GMenuModel *menu)
+{
+ g_object_set (self, "extensions-background-menu", menu, NULL);
+}
+
+static void
+real_set_extensions_background_menu (NautilusView *view,
+ GMenuModel *menu)
+{
+ NautilusFilesViewPrivate *priv;
+
+ g_return_if_fail (NAUTILUS_IS_FILES_VIEW (view));
+
+ priv = nautilus_files_view_get_instance_private (NAUTILUS_FILES_VIEW (view));
+
+ g_set_object (&priv->extensions_background_menu, menu);
+}
+
+static void
+real_set_templates_menu (NautilusView *view,
+ GMenuModel *menu)
+{
+ NautilusFilesViewPrivate *priv;
+
+ g_return_if_fail (NAUTILUS_IS_FILES_VIEW (view));
+
+ priv = nautilus_files_view_get_instance_private (NAUTILUS_FILES_VIEW (view));
+
+ g_set_object (&priv->templates_menu, menu);
+}
+
+static gboolean
+showing_trash_directory (NautilusFilesView *view)
+{
+ NautilusFile *file;
+
+ file = nautilus_files_view_get_directory_as_file (view);
+ if (file != NULL)
+ {
+ return nautilus_file_is_in_trash (file);
+ }
+ return FALSE;
+}
+
+static gboolean
+showing_recent_directory (NautilusFilesView *view)
+{
+ NautilusFile *file;
+
+ file = nautilus_files_view_get_directory_as_file (view);
+ if (file != NULL)
+ {
+ return nautilus_file_is_in_recent (file);
+ }
+ return FALSE;
+}
+
+static gboolean
+showing_starred_directory (NautilusFilesView *view)
+{
+ NautilusFile *file;
+
+ file = nautilus_files_view_get_directory_as_file (view);
+ if (file != NULL)
+ {
+ return nautilus_file_is_in_starred (file);
+ }
+ return FALSE;
+}
+
+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_view_is_searching (NAUTILUS_VIEW (view));
+}
+
+static gboolean
+nautilus_files_view_is_empty (NautilusFilesView *view)
+{
+ g_return_val_if_fail (NAUTILUS_IS_FILES_VIEW (view), FALSE);
+
+ return NAUTILUS_FILES_VIEW_CLASS (G_OBJECT_GET_CLASS (view))->is_empty (view);
+}
+
+/**
+ * nautilus_files_view_bump_zoom_level:
+ *
+ * bump the current zoom level by invoking the relevant subclass through the slot
+ *
+ **/
+void
+nautilus_files_view_bump_zoom_level (NautilusFilesView *view,
+ int zoom_increment)
+{
+ g_return_if_fail (NAUTILUS_IS_FILES_VIEW (view));
+
+ if (!nautilus_files_view_supports_zooming (view))
+ {
+ return;
+ }
+
+ NAUTILUS_FILES_VIEW_CLASS (G_OBJECT_GET_CLASS (view))->bump_zoom_level (view, zoom_increment);
+}
+
+/**
+ * nautilus_files_view_can_zoom_in:
+ *
+ * Determine whether the view can be zoomed any closer.
+ * @view: The zoomable NautilusFilesView.
+ *
+ * Return value: TRUE if @view can be zoomed any closer, FALSE otherwise.
+ *
+ **/
+gboolean
+nautilus_files_view_can_zoom_in (NautilusFilesView *view)
+{
+ g_return_val_if_fail (NAUTILUS_IS_FILES_VIEW (view), FALSE);
+
+ if (!nautilus_files_view_supports_zooming (view))
+ {
+ return FALSE;
+ }
+
+ return NAUTILUS_FILES_VIEW_CLASS (G_OBJECT_GET_CLASS (view))->can_zoom_in (view);
+}
+
+/**
+ * nautilus_files_view_can_zoom_out:
+ *
+ * Determine whether the view can be zoomed any further away.
+ * @view: The zoomable NautilusFilesView.
+ *
+ * Return value: TRUE if @view can be zoomed any further away, FALSE otherwise.
+ *
+ **/
+gboolean
+nautilus_files_view_can_zoom_out (NautilusFilesView *view)
+{
+ g_return_val_if_fail (NAUTILUS_IS_FILES_VIEW (view), FALSE);
+
+ if (!nautilus_files_view_supports_zooming (view))
+ {
+ return FALSE;
+ }
+
+ return NAUTILUS_FILES_VIEW_CLASS (G_OBJECT_GET_CLASS (view))->can_zoom_out (view);
+}
+
+gboolean
+nautilus_files_view_supports_zooming (NautilusFilesView *view)
+{
+ NautilusFilesViewPrivate *priv;
+
+ priv = nautilus_files_view_get_instance_private (view);
+
+ g_return_val_if_fail (NAUTILUS_IS_FILES_VIEW (view), FALSE);
+
+ return priv->supports_zooming;
+}
+
+/**
+ * nautilus_files_view_restore_standard_zoom_level:
+ *
+ * Restore the zoom level to 100%
+ */
+static void
+nautilus_files_view_restore_standard_zoom_level (NautilusFilesView *view)
+{
+ if (!nautilus_files_view_supports_zooming (view))
+ {
+ return;
+ }
+
+ NAUTILUS_FILES_VIEW_CLASS (G_OBJECT_GET_CLASS (view))->restore_standard_zoom_level (view);
+}
+
+static gboolean
+nautilus_files_view_is_zoom_level_default (NautilusFilesView *view)
+{
+ g_return_val_if_fail (NAUTILUS_IS_FILES_VIEW (view), FALSE);
+
+ return NAUTILUS_FILES_VIEW_CLASS (G_OBJECT_GET_CLASS (view))->is_zoom_level_default (view);
+}
+
+gboolean
+nautilus_files_view_is_searching (NautilusView *view)
+{
+ NautilusFilesView *files_view;
+ NautilusFilesViewPrivate *priv;
+
+ files_view = NAUTILUS_FILES_VIEW (view);
+ priv = nautilus_files_view_get_instance_private (files_view);
+
+ if (!priv->model)
+ {
+ return FALSE;
+ }
+
+ return NAUTILUS_IS_SEARCH_DIRECTORY (priv->model);
+}
+
+static guint
+nautilus_files_view_get_view_id (NautilusView *view)
+{
+ return NAUTILUS_FILES_VIEW_CLASS (G_OBJECT_GET_CLASS (view))->get_view_id (NAUTILUS_FILES_VIEW (view));
+}
+
+char *
+nautilus_files_view_get_first_visible_file (NautilusFilesView *view)
+{
+ return NAUTILUS_FILES_VIEW_CLASS (G_OBJECT_GET_CLASS (view))->get_first_visible_file (view);
+}
+
+void
+nautilus_files_view_scroll_to_file (NautilusFilesView *view,
+ const char *uri)
+{
+ NAUTILUS_FILES_VIEW_CLASS (G_OBJECT_GET_CLASS (view))->scroll_to_file (view, uri);
+}
+
+/**
+ * nautilus_files_view_get_selection:
+ *
+ * Get a list of NautilusFile pointers that represents the
+ * currently-selected items in this view. Subclasses must override
+ * the signal handler for the 'get_selection' signal. Callers are
+ * responsible for g_free-ing the list (and unrefing its data).
+ * @view: NautilusFilesView whose selected items are of interest.
+ *
+ * Return value: GList of NautilusFile pointers representing the selection.
+ *
+ **/
+static GList *
+nautilus_files_view_get_selection (NautilusView *view)
+{
+ g_return_val_if_fail (NAUTILUS_IS_FILES_VIEW (view), NULL);
+
+ return NAUTILUS_FILES_VIEW_CLASS (G_OBJECT_GET_CLASS (view))->get_selection (NAUTILUS_FILES_VIEW (view));
+}
+
+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 nautilus_file_ref (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 gboolean
+file_and_directory_equal (gconstpointer v1,
+ gconstpointer v2)
+{
+ const FileAndDirectory *fad1, *fad2;
+ fad1 = v1;
+ fad2 = v2;
+
+ return (fad1->file == fad2->file &&
+ fad1->directory == fad2->directory);
+}
+
+static guint
+file_and_directory_hash (gconstpointer v)
+{
+ const FileAndDirectory *fad;
+
+ fad = v;
+ return GPOINTER_TO_UINT (fad->file) ^ GPOINTER_TO_UINT (fad->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);
+}
+
+NautilusWindow *
+nautilus_files_view_get_window (NautilusFilesView *view)
+{
+ NautilusFilesViewPrivate *priv;
+
+ priv = nautilus_files_view_get_instance_private (view);
+
+ return nautilus_window_slot_get_window (priv->slot);
+}
+
+NautilusWindowSlot *
+nautilus_files_view_get_nautilus_window_slot (NautilusFilesView *view)
+{
+ NautilusFilesViewPrivate *priv;
+
+ priv = nautilus_files_view_get_instance_private (view);
+
+ g_assert (priv->slot != NULL);
+
+ return priv->slot;
+}
+
+/* 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 *view)
+{
+ NautilusFilesViewPrivate *priv;
+ char *uri, *path;
+ GFile *f;
+
+ priv = nautilus_files_view_get_instance_private (view);
+
+ uri = nautilus_directory_get_uri (priv->model);
+ f = g_file_new_for_uri (uri);
+ path = g_file_get_path (f);
+ g_object_unref (f);
+ g_free (uri);
+
+ return path;
+}
+
+typedef struct
+{
+ gchar *uri;
+ gboolean is_update;
+} PreviewExportData;
+
+static void
+preview_export_data_free (gpointer _data)
+{
+ PreviewExportData *data = _data;
+ g_free (data->uri);
+ g_free (data);
+}
+
+G_DEFINE_AUTOPTR_CLEANUP_FUNC (PreviewExportData, preview_export_data_free)
+
+static void
+on_window_handle_export (NautilusWindow *window,
+ const char *handle,
+ guint xid,
+ gpointer user_data)
+{
+ g_autoptr (PreviewExportData) data = user_data;
+ nautilus_previewer_call_show_file (data->uri, handle, xid, !data->is_update);
+}
+
+static void
+nautilus_files_view_preview (NautilusFilesView *view,
+ PreviewExportData *data)
+{
+ if (!nautilus_window_export_handle (nautilus_files_view_get_window (view),
+ on_window_handle_export,
+ data))
+ {
+ /* Let's use a fallback, so at least a preview will be displayed */
+ nautilus_previewer_call_show_file (data->uri, "x11:0", 0, !data->is_update);
+ }
+}
+
+static void
+nautilus_files_view_preview_update (NautilusFilesView *view)
+{
+ NautilusFilesViewPrivate *priv = nautilus_files_view_get_instance_private (view);
+ GtkApplication *app;
+ GtkWindow *window;
+ g_autolist (NautilusFile) selection = NULL;
+ PreviewExportData *data;
+
+ if (!priv->active ||
+ !nautilus_previewer_is_visible ())
+ {
+ return;
+ }
+
+ app = GTK_APPLICATION (g_application_get_default ());
+ window = GTK_WINDOW (nautilus_files_view_get_window (view));
+ if (window == NULL || window != gtk_application_get_active_window (app))
+ {
+ return;
+ }
+
+ selection = nautilus_view_get_selection (NAUTILUS_VIEW (view));
+ if (selection == NULL)
+ {
+ return;
+ }
+
+ data = g_new0 (PreviewExportData, 1);
+ data->uri = nautilus_file_get_uri (selection->data);
+ data->is_update = TRUE;
+
+ nautilus_files_view_preview (view, data);
+}
+
+void
+nautilus_files_view_preview_selection_event (NautilusFilesView *view,
+ GtkDirectionType direction)
+{
+ NAUTILUS_FILES_VIEW_CLASS (G_OBJECT_GET_CLASS (view))->preview_selection_event (view, direction);
+}
+
+void
+nautilus_files_view_activate_selection (NautilusFilesView *view)
+{
+ g_autolist (NautilusFile) selection = NULL;
+
+ selection = nautilus_view_get_selection (NAUTILUS_VIEW (view));
+ nautilus_files_view_activate_files (view,
+ selection,
+ 0,
+ TRUE);
+}
+
+void
+nautilus_files_view_activate_files (NautilusFilesView *view,
+ GList *files,
+ NautilusOpenFlags flags,
+ gboolean confirm_multiple)
+{
+ NautilusFilesViewPrivate *priv;
+ GList *files_to_extract;
+ GList *files_to_activate;
+ char *path;
+
+ if (files == NULL)
+ {
+ return;
+ }
+
+ priv = nautilus_files_view_get_instance_private (view);
+
+ 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),
+ priv->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_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);
+}
+
+static void
+action_open_item_location (GSimpleAction *action,
+ GVariant *state,
+ gpointer user_data)
+{
+ NautilusFilesView *view;
+ g_autolist (NautilusFile) selection = NULL;
+ NautilusFile *item;
+ GFile *activation_location;
+ NautilusFile *activation_file;
+ NautilusFile *parent;
+ g_autoptr (GFile) parent_location = NULL;
+
+ view = NAUTILUS_FILES_VIEW (user_data);
+ selection = nautilus_view_get_selection (NAUTILUS_VIEW (view));
+
+ 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_application_open_location_full (NAUTILUS_APPLICATION (g_application_get_default ()),
+ parent_location, 0, selection, NULL,
+ nautilus_files_view_get_nautilus_window_slot (view));
+
+ 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_view_get_selection (NAUTILUS_VIEW (view));
+
+ nautilus_files_view_activate_files (view,
+ selection,
+ NAUTILUS_OPEN_FLAG_NEW_TAB |
+ NAUTILUS_OPEN_FLAG_DONT_MAKE_ACTIVE,
+ TRUE);
+}
+
+static void
+app_chooser_dialog_response_cb (GtkDialog *dialog,
+ gint response_id,
+ gpointer user_data)
+{
+ GtkWindow *parent_window;
+ GList *files;
+ GAppInfo *info;
+
+ parent_window = user_data;
+ files = g_object_get_data (G_OBJECT (dialog), "directory-view:files");
+
+ if (response_id != GTK_RESPONSE_OK)
+ {
+ goto out;
+ }
+
+ info = nautilus_app_chooser_get_app_info (NAUTILUS_APP_CHOOSER (dialog));
+
+ nautilus_launch_application (info, files, parent_window);
+
+ g_object_unref (info);
+out:
+ gtk_window_destroy (GTK_WINDOW (dialog));
+}
+
+static void
+choose_program (NautilusFilesView *view,
+ GList *files)
+{
+ GtkWidget *dialog;
+ GtkWindow *parent_window;
+
+ g_assert (NAUTILUS_IS_FILES_VIEW (view));
+
+ parent_window = nautilus_files_view_get_containing_window (view);
+
+ dialog = GTK_WIDGET (nautilus_app_chooser_new (files, parent_window));
+ g_object_set_data_full (G_OBJECT (dialog),
+ "directory-view:files",
+ files,
+ (GDestroyNotify) nautilus_file_list_free);
+ gtk_widget_show (dialog);
+
+ g_signal_connect_object (dialog, "response",
+ G_CALLBACK (app_chooser_dialog_response_cb),
+ 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_view_get_selection (NAUTILUS_VIEW (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)
+{
+ NautilusFilesView *view;
+ NautilusFilesViewPrivate *priv;
+ GList *files;
+
+ g_return_if_fail (NAUTILUS_IS_FILES_VIEW (user_data));
+
+ view = NAUTILUS_FILES_VIEW (user_data);
+ priv = nautilus_files_view_get_instance_private (view);
+
+ if (priv->directory_as_file != NULL)
+ {
+ files = g_list_append (NULL, nautilus_file_ref (priv->directory_as_file));
+ choose_program (view, files);
+ }
+}
+
+static void
+trash_or_delete_selected_files (NautilusFilesView *view)
+{
+ NautilusFilesViewPrivate *priv;
+ priv = nautilus_files_view_get_instance_private (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 (!priv->selection_was_removed)
+ {
+ g_autolist (NautilusFile) selection = NULL;
+ selection = nautilus_files_view_get_selection_for_file_transfer (view);
+ trash_or_delete_files (nautilus_files_view_get_containing_window (view),
+ selection,
+ view);
+ priv->selection_was_removed = TRUE;
+ }
+}
+
+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)
+{
+ /* TODO:implement a set of functions for this, is very confusing to
+ * call trash_or_delete_file to remove from recent, even if it does like
+ * that not deleting/moving the files to trash */
+ trash_or_delete_selected_files (NAUTILUS_FILES_VIEW (user_data));
+}
+
+static void
+delete_selected_files (NautilusFilesView *view)
+{
+ GList *selection;
+ GList *node;
+ GList *locations;
+
+ selection = nautilus_files_view_get_selection_for_file_transfer (view);
+ if (selection == NULL)
+ {
+ return;
+ }
+
+ locations = NULL;
+ for (node = selection; node != NULL; node = node->next)
+ {
+ locations = g_list_prepend (locations,
+ nautilus_file_get_location ((NautilusFile *) node->data));
+ }
+ locations = g_list_reverse (locations);
+
+ nautilus_file_operations_delete_async (locations, nautilus_files_view_get_containing_window (view), NULL, NULL, NULL);
+
+ g_list_free_full (locations, g_object_unref);
+ nautilus_file_list_free (selection);
+}
+
+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;
+ NautilusFilesViewPrivate *priv;
+
+ view = NAUTILUS_FILES_VIEW (user_data);
+ priv = nautilus_files_view_get_instance_private (view);
+ selection = nautilus_view_get_selection (NAUTILUS_VIEW (view));
+
+ nautilus_tag_manager_star_files (nautilus_tag_manager_get (),
+ G_OBJECT (view),
+ selection,
+ NULL,
+ priv->starred_cancellable);
+}
+
+static void
+action_unstar (GSimpleAction *action,
+ GVariant *state,
+ gpointer user_data)
+{
+ NautilusFilesView *view;
+ g_autolist (NautilusFile) selection = NULL;
+ NautilusFilesViewPrivate *priv;
+
+ view = NAUTILUS_FILES_VIEW (user_data);
+ priv = nautilus_files_view_get_instance_private (view);
+ selection = nautilus_view_get_selection (NAUTILUS_VIEW (view));
+
+ nautilus_tag_manager_unstar_files (nautilus_tag_manager_get (),
+ G_OBJECT (view),
+ selection,
+ NULL,
+ priv->starred_cancellable);
+}
+
+static void
+action_restore_from_trash (GSimpleAction *action,
+ GVariant *state,
+ gpointer user_data)
+{
+ NautilusFilesView *view;
+ GList *selection;
+
+ 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));
+
+ nautilus_file_list_free (selection);
+}
+
+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 *view = NAUTILUS_FILES_VIEW (user_data);
+ g_autolist (NautilusFile) selection = NULL;
+ PreviewExportData *data = g_new0 (PreviewExportData, 1);
+
+ selection = nautilus_view_get_selection (NAUTILUS_VIEW (view));
+
+ data->uri = nautilus_file_get_uri (selection->data);
+ data->is_update = FALSE;
+
+ nautilus_files_view_preview (view, data);
+}
+
+static void
+action_popup_menu (GSimpleAction *action,
+ GVariant *state,
+ gpointer user_data)
+{
+ NautilusFilesView *view = NAUTILUS_FILES_VIEW (user_data);
+ g_autolist (NautilusFile) selection = nautilus_files_view_get_selection (NAUTILUS_VIEW (view));
+
+ if (selection == NULL)
+ {
+ nautilus_files_view_pop_up_background_context_menu (view, 0, 0);
+ return;
+ }
+
+ nautilus_files_view_pop_up_selection_context_menu (view, -1, -1);
+}
+
+static void
+pattern_select_response_cb (GtkWidget *dialog,
+ int response,
+ gpointer user_data)
+{
+ NautilusFilesView *view;
+ NautilusDirectory *directory;
+ GtkWidget *entry;
+ GList *selection;
+
+ view = NAUTILUS_FILES_VIEW (user_data);
+
+ switch (response)
+ {
+ case GTK_RESPONSE_OK:
+ {
+ entry = g_object_get_data (G_OBJECT (dialog), "entry");
+ directory = nautilus_files_view_get_model (view);
+ selection = nautilus_directory_match_pattern (directory,
+ gtk_editable_get_text (GTK_EDITABLE (entry)));
+
+ nautilus_files_view_call_set_selection (view, selection);
+ nautilus_files_view_reveal_selection (view);
+
+ if (selection)
+ {
+ nautilus_file_list_free (selection);
+ }
+ /* fall through */
+ }
+
+ case GTK_RESPONSE_NONE:
+ case GTK_RESPONSE_DELETE_EVENT:
+ case GTK_RESPONSE_CANCEL:
+ {
+ gtk_window_destroy (GTK_WINDOW (dialog));
+ }
+ break;
+
+ default:
+ {
+ g_assert_not_reached ();
+ }
+ }
+}
+
+static void
+select_pattern (NautilusFilesView *view)
+{
+ g_autoptr (GtkBuilder) builder = NULL;
+ GtkWidget *dialog;
+ NautilusWindow *window;
+ GtkWidget *example;
+ GtkWidget *entry;
+ char *example_pattern;
+
+ window = nautilus_files_view_get_window (view);
+ 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);
+ gtk_window_set_transient_for (GTK_WINDOW (dialog), GTK_WINDOW (window));
+
+ entry = GTK_WIDGET (gtk_builder_get_object (builder, "pattern_entry"));
+
+ g_object_set_data (G_OBJECT (dialog), "entry", entry);
+ g_signal_connect (dialog, "response",
+ G_CALLBACK (pattern_select_response_cb),
+ view);
+ gtk_widget_show (dialog);
+}
+
+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;
+ GList *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;
+ NautilusFilesViewPrivate *priv;
+ NautilusFile *file;
+ NewFolderData *data;
+
+ data = (NewFolderData *) user_data;
+
+ directory_view = data->directory_view;
+ priv = nautilus_files_view_get_instance_private (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_select_file (directory_view, file);
+ nautilus_files_view_reveal_selection (directory_view);
+ }
+ else
+ {
+ g_hash_table_insert (priv->pending_reveal,
+ file,
+ GUINT_TO_POINTER (TRUE));
+ }
+
+ 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 GdkRectangle *
+nautilus_files_view_compute_rename_popover_pointing_to (NautilusFilesView *view)
+{
+ return NAUTILUS_FILES_VIEW_CLASS (G_OBJECT_GET_CLASS (view))->compute_rename_popover_pointing_to (view);
+}
+
+static void
+disconnect_rename_controller_signals (NautilusFilesView *self)
+{
+ NautilusFilesViewPrivate *priv;
+
+ g_assert (NAUTILUS_IS_FILES_VIEW (self));
+
+ priv = nautilus_files_view_get_instance_private (self);
+
+ g_clear_signal_handler (&priv->name_accepted_handler_id, priv->rename_file_controller);
+ g_clear_signal_handler (&priv->cancelled_handler_id, priv->rename_file_controller);
+}
+
+static void
+rename_file_popover_controller_on_name_accepted (NautilusFileNameWidgetController *controller,
+ gpointer user_data)
+{
+ NautilusFilesView *view;
+ NautilusFile *target_file;
+ g_autofree gchar *name = NULL;
+ NautilusFilesViewPrivate *priv;
+
+ view = NAUTILUS_FILES_VIEW (user_data);
+ priv = nautilus_files_view_get_instance_private (view);
+
+ name = nautilus_file_name_widget_controller_get_new_name (controller);
+
+ target_file =
+ nautilus_rename_file_popover_controller_get_target_file (priv->rename_file_controller);
+
+ /* Put it on the queue for reveal after the view acknowledges the change */
+ g_hash_table_insert (priv->pending_reveal,
+ target_file,
+ GUINT_TO_POINTER (FALSE));
+
+ nautilus_rename_file (target_file, name, NULL, NULL);
+
+ disconnect_rename_controller_signals (view);
+}
+
+static void
+rename_file_popover_controller_on_cancelled (NautilusFileNameWidgetController *controller,
+ gpointer user_data)
+{
+ NautilusFilesView *view;
+
+ view = NAUTILUS_FILES_VIEW (user_data);
+
+ disconnect_rename_controller_signals (view);
+}
+
+static void
+nautilus_files_view_rename_file_popover_new (NautilusFilesView *view,
+ NautilusFile *target_file)
+{
+ GdkRectangle *pointing_to;
+ NautilusFilesViewPrivate *priv;
+
+ priv = nautilus_files_view_get_instance_private (view);
+
+ /* Make sure the whole item is visible. The selection is a single item, the
+ * one to rename with the popover, so we can use reveal_selection() for this.
+ */
+ nautilus_files_view_reveal_selection (view);
+
+ pointing_to = nautilus_files_view_compute_rename_popover_pointing_to (view);
+
+ nautilus_rename_file_popover_controller_show_for_file (priv->rename_file_controller,
+ target_file,
+ pointing_to);
+
+ priv->name_accepted_handler_id = g_signal_connect (priv->rename_file_controller,
+ "name-accepted",
+ G_CALLBACK (rename_file_popover_controller_on_name_accepted),
+ view);
+ priv->cancelled_handler_id = g_signal_connect (priv->rename_file_controller,
+ "cancelled",
+ G_CALLBACK (rename_file_popover_controller_on_cancelled),
+ view);
+}
+
+static void
+new_folder_dialog_controller_on_name_accepted (NautilusFileNameWidgetController *controller,
+ gpointer user_data)
+{
+ NautilusFilesView *view;
+ NautilusFilesViewPrivate *priv;
+ NewFolderData *data;
+ g_autofree gchar *parent_uri = NULL;
+ g_autofree gchar *name = NULL;
+ NautilusFile *parent;
+ gboolean with_selection;
+
+ view = NAUTILUS_FILES_VIEW (user_data);
+ priv = nautilus_files_view_get_instance_private (view);
+
+ with_selection =
+ nautilus_new_folder_dialog_controller_get_with_selection (priv->new_folder_controller);
+
+ data = new_folder_data_new (view, with_selection);
+
+ name = nautilus_file_name_widget_controller_get_new_name (controller);
+ 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, name,
+ new_folder_done, data);
+
+ g_clear_object (&priv->new_folder_controller);
+
+ /* 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
+new_folder_dialog_controller_on_cancelled (NautilusNewFolderDialogController *controller,
+ gpointer user_data)
+{
+ NautilusFilesView *view;
+ NautilusFilesViewPrivate *priv;
+
+ view = NAUTILUS_FILES_VIEW (user_data);
+ priv = nautilus_files_view_get_instance_private (view);
+
+ g_clear_object (&priv->new_folder_controller);
+}
+
+static void
+nautilus_files_view_new_folder_dialog_new (NautilusFilesView *view,
+ gboolean with_selection)
+{
+ g_autoptr (NautilusDirectory) containing_directory = NULL;
+ NautilusFilesViewPrivate *priv;
+ g_autofree char *uri = NULL;
+ g_autofree char *common_prefix = NULL;
+
+ priv = nautilus_files_view_get_instance_private (view);
+
+ if (priv->new_folder_controller != NULL)
+ {
+ return;
+ }
+
+ 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_view_get_selection (NAUTILUS_VIEW (view));
+ common_prefix = nautilus_get_common_filename_prefix (selection, MIN_COMMON_FILENAME_PREFIX_LENGTH);
+ }
+
+ priv->new_folder_controller =
+ nautilus_new_folder_dialog_controller_new (nautilus_files_view_get_containing_window (view),
+ containing_directory,
+ with_selection,
+ common_prefix);
+
+ g_signal_connect (priv->new_folder_controller,
+ "name-accepted",
+ (GCallback) new_folder_dialog_controller_on_name_accepted,
+ view);
+ g_signal_connect (priv->new_folder_controller,
+ "cancelled",
+ (GCallback) new_folder_dialog_controller_on_cancelled,
+ 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;
+ NautilusFilesViewPrivate *priv;
+ NautilusFile *file;
+ char *uri = NULL;
+
+ data = user_data;
+ view = data->view;
+
+ if (view == NULL)
+ {
+ goto out;
+ }
+
+ priv = nautilus_files_view_get_instance_private (view);
+
+ 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_select_file (view, file);
+ nautilus_files_view_reveal_selection (view);
+ }
+ else
+ {
+ g_hash_table_insert (priv->pending_reveal,
+ file,
+ GUINT_TO_POINTER (TRUE));
+ }
+
+ 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
+compress_dialog_controller_on_name_accepted (NautilusFileNameWidgetController *controller,
+ gpointer user_data)
+{
+ CompressCallbackData *callback_data = user_data;
+ NautilusFilesView *view;
+ g_autofree gchar *name = NULL;
+ GList *source_files = NULL;
+ GList *l;
+ CompressData *data;
+ g_autoptr (GFile) output = NULL;
+ g_autoptr (GFile) parent = NULL;
+ NautilusCompressionFormat compression_format;
+ NautilusFilesViewPrivate *priv;
+ AutoarFormat format;
+ AutoarFilter filter;
+ const gchar *passphrase = NULL;
+
+ view = NAUTILUS_FILES_VIEW (callback_data->view);
+ priv = nautilus_files_view_get_instance_private (view);
+
+ for (l = callback_data->selection; l != NULL; l = l->next)
+ {
+ source_files = g_list_prepend (source_files,
+ nautilus_file_get_location (l->data));
+ }
+ source_files = g_list_reverse (source_files);
+
+ name = nautilus_file_name_widget_controller_get_new_name (controller);
+ /* 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, 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;
+ passphrase = nautilus_compress_dialog_controller_get_passphrase (priv->compress_controller);
+ }
+ 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);
+
+ g_list_free_full (source_files, g_object_unref);
+ g_clear_object (&priv->compress_controller);
+}
+
+static void
+compress_dialog_controller_on_cancelled (NautilusNewFolderDialogController *controller,
+ gpointer user_data)
+{
+ NautilusFilesView *view;
+ NautilusFilesViewPrivate *priv;
+
+ view = NAUTILUS_FILES_VIEW (user_data);
+ priv = nautilus_files_view_get_instance_private (view);
+
+ g_clear_object (&priv->compress_controller);
+}
+
+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;
+ NautilusFilesViewPrivate *priv;
+ g_autolist (NautilusFile) selection = NULL;
+ g_autofree char *common_prefix = NULL;
+ CompressCallbackData *data;
+
+ priv = nautilus_files_view_get_instance_private (view);
+
+ if (priv->compress_controller != NULL)
+ {
+ return;
+ }
+
+ containing_directory = nautilus_directory_get_by_uri (nautilus_files_view_get_backing_uri (view));
+
+ selection = nautilus_view_get_selection (NAUTILUS_VIEW (view));
+
+ if (g_list_length (selection) == 1)
+ {
+ g_autofree char *display_name = NULL;
+
+ display_name = nautilus_file_get_display_name (selection->data);
+
+ if (nautilus_file_is_directory (selection->data))
+ {
+ common_prefix = g_steal_pointer (&display_name);
+ }
+ else
+ {
+ common_prefix = eel_filename_strip_extension (display_name);
+ }
+ }
+ else
+ {
+ common_prefix = nautilus_get_common_filename_prefix (selection,
+ MIN_COMMON_FILENAME_PREFIX_LENGTH);
+ }
+
+ priv->compress_controller = nautilus_compress_dialog_controller_new (nautilus_files_view_get_containing_window (view),
+ containing_directory,
+ common_prefix);
+
+ data = g_new0 (CompressCallbackData, 1);
+ data->view = view;
+ data->selection = nautilus_files_view_get_selection_for_file_transfer (view);
+
+ g_signal_connect_data (priv->compress_controller,
+ "name-accepted",
+ (GCallback) compress_dialog_controller_on_name_accepted,
+ data,
+ (GClosureNotify) compress_callback_data_free,
+ G_CONNECT_AFTER);
+
+ g_signal_connect (priv->compress_controller,
+ "cancelled",
+ (GCallback) compress_dialog_controller_on_cancelled,
+ view);
+}
+
+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 char *initial_contents,
+ int 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)
+{
+ g_autolist (NautilusFile) selection = NULL;
+
+ selection = nautilus_view_get_selection (NAUTILUS_VIEW (user_data));
+ g_return_if_fail (selection != NULL && g_list_length (selection) == 1);
+
+ real_open_console (NAUTILUS_FILE (selection->data), NAUTILUS_FILES_VIEW (user_data));
+}
+
+static void
+action_current_dir_open_console (GSimpleAction *action,
+ GVariant *state,
+ gpointer user_data)
+{
+ NautilusFilesView *view;
+ NautilusFilesViewPrivate *priv;
+
+ g_return_if_fail (NAUTILUS_IS_FILES_VIEW (user_data));
+
+ view = NAUTILUS_FILES_VIEW (user_data);
+ priv = nautilus_files_view_get_instance_private (view);
+ real_open_console (priv->directory_as_file, view);
+}
+
+static void
+action_properties (GSimpleAction *action,
+ GVariant *state,
+ gpointer user_data)
+{
+ NautilusFilesView *view;
+ NautilusFilesViewPrivate *priv;
+ g_autolist (NautilusFile) selection = NULL;
+ GList *files;
+
+ g_assert (NAUTILUS_IS_FILES_VIEW (user_data));
+
+ view = NAUTILUS_FILES_VIEW (user_data);
+ priv = nautilus_files_view_get_instance_private (view);
+ selection = nautilus_view_get_selection (NAUTILUS_VIEW (view));
+ if (g_list_length (selection) == 0)
+ {
+ if (priv->directory_as_file != NULL)
+ {
+ files = g_list_append (NULL, nautilus_file_ref (priv->directory_as_file));
+
+ nautilus_properties_window_present (files, GTK_WIDGET (view), NULL,
+ NULL, NULL);
+
+ nautilus_file_list_free (files);
+ }
+ }
+ else
+ {
+ nautilus_properties_window_present (selection, GTK_WIDGET (view), NULL,
+ NULL, NULL);
+ }
+}
+
+static void
+action_current_dir_properties (GSimpleAction *action,
+ GVariant *state,
+ gpointer user_data)
+{
+ NautilusFilesView *view;
+ NautilusFilesViewPrivate *priv;
+ GList *files;
+
+ g_return_if_fail (NAUTILUS_IS_FILES_VIEW (user_data));
+
+ view = NAUTILUS_FILES_VIEW (user_data);
+ priv = nautilus_files_view_get_instance_private (view);
+
+ if (priv->directory_as_file != NULL)
+ {
+ files = g_list_append (NULL, nautilus_file_ref (priv->directory_as_file));
+
+ nautilus_properties_window_present (files, GTK_WIDGET (view), NULL,
+ NULL, NULL);
+
+ nautilus_file_list_free (files);
+ }
+}
+
+static void
+nautilus_files_view_set_show_hidden_files (NautilusFilesView *view,
+ gboolean show_hidden)
+{
+ NautilusFilesViewPrivate *priv;
+
+ priv = nautilus_files_view_get_instance_private (view);
+
+ if (priv->ignore_hidden_file_preferences)
+ {
+ return;
+ }
+
+ if (show_hidden != priv->show_hidden_files)
+ {
+ priv->show_hidden_files = show_hidden;
+
+ g_settings_set_boolean (gtk_filechooser_preferences,
+ NAUTILUS_PREFERENCES_SHOW_HIDDEN_FILES,
+ show_hidden);
+
+ if (priv->model != NULL)
+ {
+ load_directory (view, priv->model);
+ }
+ }
+}
+
+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_zoom_in (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_bump_zoom_level (view, 1);
+}
+
+static void
+action_zoom_out (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_bump_zoom_level (view, -1);
+}
+
+static void
+action_zoom_standard (GSimpleAction *action,
+ GVariant *state,
+ gpointer user_data)
+{
+ nautilus_files_view_restore_standard_zoom_level (user_data);
+}
+
+static void
+action_open_item_new_window (GSimpleAction *action,
+ GVariant *state,
+ gpointer user_data)
+{
+ NautilusFilesView *view;
+ GList *selection;
+
+ view = NAUTILUS_FILES_VIEW (user_data);
+ selection = nautilus_view_get_selection (NAUTILUS_VIEW (view));
+
+ nautilus_files_view_activate_files (view,
+ selection,
+ NAUTILUS_OPEN_FLAG_NEW_WINDOW,
+ TRUE);
+
+ nautilus_file_list_free (selection);
+}
+
+static void
+handle_clipboard_data (NautilusFilesView *view,
+ NautilusClipboard *clip,
+ char *destination_uri,
+ GdkDragAction action)
+{
+ GList *item_uris = nautilus_clipboard_get_uri_list (clip);
+
+ 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);
+ }
+
+ g_list_free_full (item_uris, g_free);
+ }
+}
+
+static void
+paste_clipboard_data (NautilusFilesView *view,
+ NautilusClipboard *clip,
+ char *destination_uri)
+{
+ GdkDragAction action;
+
+ if (nautilus_clipboard_is_cut (clip))
+ {
+ action = GDK_ACTION_MOVE;
+ }
+ else
+ {
+ action = GDK_ACTION_COPY;
+ }
+
+ handle_clipboard_data (view, clip, destination_uri, action);
+}
+
+static void
+paste_clipboard_received_callback (GObject *source_object,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ NautilusFilesView *view = NAUTILUS_FILES_VIEW (source_object);
+ NautilusClipboard *clip;
+ g_autofree char *view_uri = NULL;
+
+ clip = nautilus_files_view_get_clipboard_finish (view, res, NULL);
+ if (clip != NULL)
+ {
+ view_uri = nautilus_files_view_get_backing_uri (view);
+ paste_clipboard_data (view, clip, view_uri);
+ }
+}
+
+static void
+paste_files (NautilusFilesView *view)
+{
+ nautilus_files_view_get_clipboard_async (view,
+ paste_clipboard_received_callback,
+ NULL);
+}
+
+static void
+action_paste_files (GSimpleAction *action,
+ GVariant *state,
+ gpointer user_data)
+{
+ NautilusFilesView *view;
+
+ view = NAUTILUS_FILES_VIEW (user_data);
+
+ paste_files (view);
+}
+
+static void
+action_paste_files_accel (GSimpleAction *action,
+ GVariant *state,
+ gpointer user_data)
+{
+ NautilusFilesView *view;
+
+ view = NAUTILUS_FILES_VIEW (user_data);
+
+ 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);
+ }
+}
+
+static void
+create_links_clipboard_received_callback (GObject *source_object,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ NautilusFilesView *view = NAUTILUS_FILES_VIEW (source_object);
+ NautilusClipboard *clip;
+ g_autofree char *view_uri = NULL;
+
+ clip = nautilus_files_view_get_clipboard_finish (view, res, NULL);
+ if (clip != NULL)
+ {
+ view_uri = nautilus_files_view_get_backing_uri (view);
+ handle_clipboard_data (view, clip, view_uri, GDK_ACTION_LINK);
+ }
+}
+
+static void
+action_create_links (GSimpleAction *action,
+ GVariant *state,
+ gpointer user_data)
+{
+ g_assert (NAUTILUS_IS_FILES_VIEW (user_data));
+
+ nautilus_files_view_get_clipboard_async (NAUTILUS_FILES_VIEW (user_data),
+ create_links_clipboard_received_callback,
+ NULL);
+}
+
+static void
+click_policy_changed_callback (gpointer callback_data)
+{
+ NautilusFilesView *view;
+
+ view = NAUTILUS_FILES_VIEW (callback_data);
+
+ NAUTILUS_FILES_VIEW_CLASS (G_OBJECT_GET_CLASS (view))->click_policy_changed (view);
+}
+
+gboolean
+nautilus_files_view_should_sort_directories_first (NautilusFilesView *view)
+{
+ NautilusFilesViewPrivate *priv;
+ gboolean is_search;
+
+ priv = nautilus_files_view_get_instance_private (view);
+ is_search = nautilus_view_is_searching (NAUTILUS_VIEW (view));
+
+ return priv->sort_directories_first && !is_search;
+}
+
+static void
+sort_directories_first_changed_callback (gpointer callback_data)
+{
+ NautilusFilesView *view;
+ NautilusFilesViewPrivate *priv;
+ gboolean preference_value;
+
+ view = NAUTILUS_FILES_VIEW (callback_data);
+ priv = nautilus_files_view_get_instance_private (view);
+
+ preference_value =
+ g_settings_get_boolean (gtk_filechooser_preferences, NAUTILUS_PREFERENCES_SORT_DIRECTORIES_FIRST);
+
+ if (preference_value != priv->sort_directories_first)
+ {
+ priv->sort_directories_first = preference_value;
+ NAUTILUS_FILES_VIEW_CLASS (G_OBJECT_GET_CLASS (view))->sort_directories_first_changed (view);
+ }
+}
+
+static void
+show_hidden_files_changed_callback (gpointer callback_data)
+{
+ NautilusFilesView *view;
+ NautilusFilesViewPrivate *priv;
+ gboolean preference_value;
+
+ view = NAUTILUS_FILES_VIEW (callback_data);
+ priv = nautilus_files_view_get_instance_private (view);
+
+ 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 (priv->active)
+ {
+ schedule_update_context_menus (view);
+ }
+}
+
+static gboolean
+set_up_scripts_directory_global (void)
+{
+ g_autofree gchar *old_scripts_directory_path = NULL;
+ g_autoptr (GFile) old_scripts_directory = NULL;
+ g_autofree gchar *scripts_directory_path = NULL;
+ g_autoptr (GFile) scripts_directory = NULL;
+ const char *override;
+ GFileType file_type;
+ g_autoptr (GError) error = NULL;
+
+ if (scripts_directory_uri != NULL)
+ {
+ return TRUE;
+ }
+
+ scripts_directory_path = nautilus_get_scripts_directory_path ();
+
+ override = g_getenv ("GNOME22_USER_DIR");
+
+ if (override)
+ {
+ old_scripts_directory_path = g_build_filename (override,
+ "nautilus-scripts",
+ NULL);
+ }
+ else
+ {
+ old_scripts_directory_path = g_build_filename (g_get_home_dir (),
+ ".gnome2",
+ "nautilus-scripts",
+ NULL);
+ }
+
+ old_scripts_directory = g_file_new_for_path (old_scripts_directory_path);
+ scripts_directory = g_file_new_for_path (scripts_directory_path);
+
+ file_type = g_file_query_file_type (old_scripts_directory,
+ G_FILE_QUERY_INFO_NONE,
+ NULL);
+
+ if (file_type == G_FILE_TYPE_DIRECTORY &&
+ !g_file_query_exists (scripts_directory, NULL))
+ {
+ g_autoptr (GFile) updated = NULL;
+ const char *message;
+
+ /* test if we already attempted to migrate first */
+ updated = g_file_get_child (old_scripts_directory, "DEPRECATED-DIRECTORY");
+ message = _("Nautilus 3.6 deprecated this directory and tried migrating "
+ "this configuration to ~/.local/share/nautilus");
+ if (!g_file_query_exists (updated, NULL))
+ {
+ g_autoptr (GFile) parent = NULL;
+
+ parent = g_file_get_parent (scripts_directory);
+ g_file_make_directory_with_parents (parent, NULL, &error);
+
+ if (error == NULL ||
+ g_error_matches (error, G_IO_ERROR, G_IO_ERROR_EXISTS))
+ {
+ g_clear_error (&error);
+
+ g_file_set_attribute_uint32 (parent,
+ G_FILE_ATTRIBUTE_UNIX_MODE,
+ S_IRWXU,
+ G_FILE_QUERY_INFO_NONE,
+ NULL, NULL);
+
+ g_file_move (old_scripts_directory,
+ scripts_directory,
+ G_FILE_COPY_NONE,
+ NULL, NULL, NULL,
+ &error);
+
+ if (error == NULL)
+ {
+ g_file_replace_contents (updated,
+ message, strlen (message),
+ NULL,
+ FALSE,
+ G_FILE_CREATE_PRIVATE,
+ NULL, NULL, NULL);
+ }
+ }
+
+ g_clear_error (&error);
+ }
+ }
+
+ 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 *view;
+ NautilusFilesViewPrivate *priv;
+
+ view = NAUTILUS_FILES_VIEW (callback_data);
+ priv = nautilus_files_view_get_instance_private (view);
+
+ priv->scripts_menu_updated = FALSE;
+ if (priv->active)
+ {
+ schedule_update_context_menus (view);
+ }
+}
+
+static void
+templates_added_or_changed_callback (NautilusDirectory *directory,
+ GList *files,
+ gpointer callback_data)
+{
+ NautilusFilesView *view;
+ NautilusFilesViewPrivate *priv;
+
+ view = NAUTILUS_FILES_VIEW (callback_data);
+ priv = nautilus_files_view_get_instance_private (view);
+
+ priv->templates_menu_updated = FALSE;
+ if (priv->active)
+ {
+ schedule_update_context_menus (view);
+ }
+}
+
+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 *view,
+ NautilusDirectory *directory)
+{
+ NautilusFilesViewPrivate *priv;
+
+ priv = nautilus_files_view_get_instance_private (view);
+
+ add_directory_to_directory_list (view, directory,
+ &priv->scripts_directory_list,
+ G_CALLBACK (scripts_added_or_changed_callback));
+}
+
+static void
+remove_directory_from_scripts_directory_list (NautilusFilesView *view,
+ NautilusDirectory *directory)
+{
+ NautilusFilesViewPrivate *priv;
+
+ priv = nautilus_files_view_get_instance_private (view);
+
+ remove_directory_from_directory_list (view, directory,
+ &priv->scripts_directory_list,
+ G_CALLBACK (scripts_added_or_changed_callback));
+}
+
+static void
+add_directory_to_templates_directory_list (NautilusFilesView *view,
+ NautilusDirectory *directory)
+{
+ NautilusFilesViewPrivate *priv;
+
+ priv = nautilus_files_view_get_instance_private (view);
+
+ add_directory_to_directory_list (view, directory,
+ &priv->templates_directory_list,
+ G_CALLBACK (templates_added_or_changed_callback));
+}
+
+static void
+remove_directory_from_templates_directory_list (NautilusFilesView *view,
+ NautilusDirectory *directory)
+{
+ NautilusFilesViewPrivate *priv;
+
+ priv = nautilus_files_view_get_instance_private (view);
+
+ remove_directory_from_directory_list (view, directory,
+ &priv->templates_directory_list,
+ G_CALLBACK (templates_added_or_changed_callback));
+}
+
+static void
+slot_active_changed (NautilusWindowSlot *slot,
+ GParamSpec *pspec,
+ NautilusFilesView *view)
+{
+ NautilusFilesViewPrivate *priv;
+
+ priv = nautilus_files_view_get_instance_private (view);
+
+ if (priv->active == nautilus_window_slot_get_active (slot))
+ {
+ return;
+ }
+
+ priv->active = nautilus_window_slot_get_active (slot);
+
+ if (priv->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);
+
+ schedule_update_context_menus (view);
+
+ gtk_widget_insert_action_group (GTK_WIDGET (nautilus_files_view_get_window (view)),
+ "view",
+ G_ACTION_GROUP (priv->view_action_group));
+ }
+ else
+ {
+ remove_update_context_menus_timeout_callback (view);
+ gtk_widget_insert_action_group (GTK_WIDGET (nautilus_files_view_get_window (view)),
+ "view",
+ NULL);
+ }
+}
+
+static gboolean
+nautilus_files_view_focus (GtkWidget *widget,
+ GtkDirectionType direction)
+{
+ NautilusFilesView *view;
+ NautilusFilesViewPrivate *priv;
+ GtkWidget *focus;
+ GtkWidget *main_child;
+
+ view = NAUTILUS_FILES_VIEW (widget);
+ priv = nautilus_files_view_get_instance_private (view);
+ focus = gtk_window_get_focus (GTK_WINDOW (gtk_widget_get_root (widget)));
+
+ /* In general, we want to forward focus movement to the main child. However,
+ * we must chain up for default focus handling in case the focus in in any
+ * other child, e.g. a popover. */
+ if (gtk_widget_is_ancestor (focus, widget) &&
+ !gtk_widget_is_ancestor (focus, priv->scrolled_window))
+ {
+ if (GTK_WIDGET_CLASS (nautilus_files_view_parent_class)->focus (widget, direction))
+ {
+ return TRUE;
+ }
+ else
+ {
+ /* The default handler returns FALSE if a popover has just been
+ * closed, because it moves the focus forward. But we want to move
+ * focus back into the view's main child. So, fall through. */
+ }
+ }
+
+ main_child = gtk_scrolled_window_get_child (GTK_SCROLLED_WINDOW (priv->scrolled_window));
+ if (main_child != NULL)
+ {
+ return gtk_widget_child_focus (main_child, direction);
+ }
+
+ return FALSE;
+}
+
+static gboolean
+nautilus_files_view_grab_focus (GtkWidget *widget)
+{
+ /* focus the child of the scrolled window if it exists */
+ NautilusFilesView *view;
+ NautilusFilesViewPrivate *priv;
+ GtkWidget *child;
+
+ view = NAUTILUS_FILES_VIEW (widget);
+ priv = nautilus_files_view_get_instance_private (view);
+ child = gtk_scrolled_window_get_child (GTK_SCROLLED_WINDOW (priv->scrolled_window));
+
+ if (child != NULL)
+ {
+ return gtk_widget_grab_focus (GTK_WIDGET (child));
+ }
+
+ return GTK_WIDGET_CLASS (nautilus_files_view_parent_class)->grab_focus (widget);
+}
+
+static void
+nautilus_files_view_set_selection (NautilusView *nautilus_files_view,
+ GList *selection)
+{
+ NautilusFilesView *view;
+ NautilusFilesViewPrivate *priv;
+ GList *pending_selection;
+
+ view = NAUTILUS_FILES_VIEW (nautilus_files_view);
+ priv = nautilus_files_view_get_instance_private (view);
+
+ if (!priv->loading)
+ {
+ /* If we aren't still loading, set the selection right now,
+ * and reveal the new selection.
+ */
+ nautilus_files_view_call_set_selection (view, selection);
+ nautilus_files_view_reveal_selection (view);
+ }
+ else
+ {
+ /* If we are still loading, set the list of pending URIs instead.
+ * done_loading() will eventually select the pending URIs and reveal them.
+ */
+ pending_selection = g_list_copy_deep (selection,
+ (GCopyFunc) g_object_ref, NULL);
+ g_list_free_full (priv->pending_selection, g_object_unref);
+
+ priv->pending_selection = pending_selection;
+ }
+}
+
+static void
+nautilus_files_view_dispose (GObject *object)
+{
+ NautilusFilesView *view;
+ NautilusFilesViewPrivate *priv;
+ GdkClipboard *clipboard;
+ GList *node, *next;
+
+ view = NAUTILUS_FILES_VIEW (object);
+ priv = nautilus_files_view_get_instance_private (view);
+
+ priv->in_destruction = TRUE;
+ nautilus_files_view_stop_loading (view);
+
+ g_clear_pointer (&priv->selection_menu, gtk_widget_unparent);
+ g_clear_pointer (&priv->background_menu, gtk_widget_unparent);
+
+ if (priv->model)
+ {
+ nautilus_directory_unref (priv->model);
+ priv->model = NULL;
+ }
+
+ for (node = priv->scripts_directory_list; node != NULL; node = next)
+ {
+ next = node->next;
+ remove_directory_from_scripts_directory_list (view, node->data);
+ }
+
+ for (node = priv->templates_directory_list; node != NULL; node = next)
+ {
+ next = node->next;
+ remove_directory_from_templates_directory_list (view, node->data);
+ }
+
+ while (priv->subdirectory_list != NULL)
+ {
+ nautilus_files_view_remove_subdirectory (view,
+ priv->subdirectory_list->data);
+ }
+
+ remove_update_context_menus_timeout_callback (view);
+ remove_update_status_idle_callback (view);
+
+ if (priv->display_selection_idle_id != 0)
+ {
+ g_source_remove (priv->display_selection_idle_id);
+ priv->display_selection_idle_id = 0;
+ }
+
+ if (priv->reveal_selection_idle_id != 0)
+ {
+ g_source_remove (priv->reveal_selection_idle_id);
+ priv->reveal_selection_idle_id = 0;
+ }
+
+ if (priv->floating_bar_set_status_timeout_id != 0)
+ {
+ g_source_remove (priv->floating_bar_set_status_timeout_id);
+ priv->floating_bar_set_status_timeout_id = 0;
+ }
+
+ if (priv->floating_bar_loading_timeout_id != 0)
+ {
+ g_source_remove (priv->floating_bar_loading_timeout_id);
+ priv->floating_bar_loading_timeout_id = 0;
+ }
+
+ if (priv->floating_bar_set_passthrough_timeout_id != 0)
+ {
+ g_source_remove (priv->floating_bar_set_passthrough_timeout_id);
+ priv->floating_bar_set_passthrough_timeout_id = 0;
+ }
+
+ g_signal_handlers_disconnect_by_func (nautilus_preferences,
+ schedule_update_context_menus, view);
+ g_signal_handlers_disconnect_by_func (nautilus_preferences,
+ click_policy_changed_callback, view);
+ g_signal_handlers_disconnect_by_func (gtk_filechooser_preferences,
+ sort_directories_first_changed_callback, view);
+ g_signal_handlers_disconnect_by_func (gtk_filechooser_preferences,
+ show_hidden_files_changed_callback, view);
+ g_signal_handlers_disconnect_by_func (nautilus_window_state,
+ nautilus_files_view_display_selection_info, view);
+ g_signal_handlers_disconnect_by_func (gnome_lockdown_preferences,
+ schedule_update_context_menus, view);
+ g_signal_handlers_disconnect_by_func (nautilus_trash_monitor_get (),
+ nautilus_files_view_trash_state_changed_callback, view);
+
+ clipboard = gdk_display_get_clipboard (gdk_display_get_default ());
+ g_signal_handlers_disconnect_by_func (clipboard, on_clipboard_owner_changed, view);
+ g_cancellable_cancel (priv->clipboard_cancellable);
+
+ nautilus_file_unref (priv->directory_as_file);
+ priv->directory_as_file = NULL;
+
+ g_clear_object (&priv->search_query);
+ g_clear_object (&priv->location);
+
+ G_OBJECT_CLASS (nautilus_files_view_parent_class)->dispose (object);
+}
+
+static void
+nautilus_files_view_finalize (GObject *object)
+{
+ NautilusFilesView *view;
+ NautilusFilesViewPrivate *priv;
+
+ view = NAUTILUS_FILES_VIEW (object);
+ priv = nautilus_files_view_get_instance_private (view);
+
+ g_clear_object (&priv->view_action_group);
+ g_clear_object (&priv->background_menu_model);
+ g_clear_object (&priv->selection_menu_model);
+ g_clear_object (&priv->toolbar_menu_sections->sort_section);
+ g_clear_object (&priv->extensions_background_menu);
+ g_clear_object (&priv->templates_menu);
+ g_clear_object (&priv->rename_file_controller);
+ g_clear_object (&priv->new_folder_controller);
+ g_clear_object (&priv->compress_controller);
+ /* We don't own the slot, so no unref */
+ priv->slot = NULL;
+
+ g_free (priv->toolbar_menu_sections);
+
+ g_hash_table_destroy (priv->non_ready_files);
+ g_hash_table_destroy (priv->pending_reveal);
+
+ g_clear_object (&priv->clipboard_cancellable);
+
+ g_cancellable_cancel (priv->starred_cancellable);
+ g_clear_object (&priv->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.
+ *
+ **/
+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;
+ char *first_item_name;
+ 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));
+
+ selection = nautilus_view_get_selection (NAUTILUS_VIEW (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;
+ first_item_name = NULL;
+ 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 (first_item_name);
+ 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);
+ g_object_notify (G_OBJECT (view), "selection");
+}
+
+static void
+nautilus_files_view_set_location (NautilusView *view,
+ GFile *location)
+{
+ NautilusDirectory *directory;
+ NautilusFilesView *files_view;
+
+ nautilus_profile_start (NULL);
+ files_view = NAUTILUS_FILES_VIEW (view);
+ directory = nautilus_directory_get (location);
+
+ nautilus_files_view_stop_loading (files_view);
+ /* In case we want to load a previous search we need to extract the real
+ * location and the search location, and load the directory when everything
+ * is ready. That's why we cannot use the nautilus_view_set_query, because
+ * to set a query we need a previous location loaded, but to load a search
+ * location we need to know the real location behind it. */
+ if (NAUTILUS_IS_SEARCH_DIRECTORY (directory))
+ {
+ NautilusQuery *previous_query;
+ NautilusDirectory *base_model;
+
+ base_model = nautilus_search_directory_get_base_model (NAUTILUS_SEARCH_DIRECTORY (directory));
+ previous_query = nautilus_search_directory_get_query (NAUTILUS_SEARCH_DIRECTORY (directory));
+ set_search_query_internal (files_view, previous_query, base_model);
+ g_object_unref (previous_query);
+ }
+ else
+ {
+ load_directory (NAUTILUS_FILES_VIEW (view), directory);
+ }
+ nautilus_directory_unref (directory);
+ nautilus_profile_end (NULL);
+}
+
+static gboolean
+reveal_selection_idle_callback (gpointer data)
+{
+ NautilusFilesView *view;
+ NautilusFilesViewPrivate *priv;
+
+ view = NAUTILUS_FILES_VIEW (data);
+ priv = nautilus_files_view_get_instance_private (view);
+
+ priv->reveal_selection_idle_id = 0;
+ nautilus_files_view_reveal_selection (view);
+
+ return FALSE;
+}
+
+static void
+nautilus_files_view_check_empty_states (NautilusFilesView *view)
+{
+ NAUTILUS_FILES_VIEW_CLASS (G_OBJECT_GET_CLASS (view))->check_empty_states (view);
+}
+
+static void
+real_check_empty_states (NautilusFilesView *view)
+{
+ NautilusFilesViewPrivate *priv = nautilus_files_view_get_instance_private (view);
+ g_autofree gchar *uri = NULL;
+ AdwStatusPage *status_page = ADW_STATUS_PAGE (priv->empty_view_page);
+
+ if (!priv->loading &&
+ nautilus_files_view_is_empty (view))
+ {
+ uri = g_file_get_uri (priv->location);
+
+ if (nautilus_view_is_searching (NAUTILUS_VIEW (view)))
+ {
+ adw_status_page_set_icon_name (status_page, "edit-find-symbolic");
+ adw_status_page_set_title (status_page, _("No Results Found"));
+ adw_status_page_set_description (status_page, _("Try a different search."));
+ }
+ else if (eel_uri_is_trash_root (uri))
+ {
+ 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 (eel_uri_is_starred (uri))
+ {
+ 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 (eel_uri_is_recent (uri))
+ {
+ 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
+ {
+ 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_stack_set_visible_child (GTK_STACK (priv->stack), priv->empty_view_page);
+ }
+ else
+ {
+ gtk_stack_set_visible_child (GTK_STACK (priv->stack), priv->scrolled_window);
+ }
+}
+
+static void
+done_loading (NautilusFilesView *view,
+ gboolean all_files_seen)
+{
+ NautilusFilesViewPrivate *priv;
+ g_autolist (NautilusFile) selection = NULL;
+ gboolean do_reveal = FALSE;
+
+ priv = nautilus_files_view_get_instance_private (view);
+
+ if (!priv->loading)
+ {
+ return;
+ }
+
+ nautilus_profile_start (NULL);
+
+ if (!priv->in_destruction)
+ {
+ remove_loading_floating_bar (view);
+ schedule_update_context_menus (view);
+ schedule_update_status (view);
+ nautilus_files_view_update_toolbar_menus (view);
+ reset_update_interval (view);
+
+ selection = nautilus_view_get_selection (NAUTILUS_VIEW (view));
+
+ if (nautilus_view_is_searching (NAUTILUS_VIEW (view)) &&
+ all_files_seen && selection == NULL && priv->pending_selection == NULL)
+ {
+ nautilus_files_view_select_first (view);
+ do_reveal = TRUE;
+ }
+ else if (priv->pending_selection != NULL && all_files_seen)
+ {
+ g_autolist (NautilusFile) pending_selection = NULL;
+ pending_selection = g_steal_pointer (&priv->pending_selection);
+
+ nautilus_files_view_call_set_selection (view, pending_selection);
+ do_reveal = TRUE;
+ }
+
+ g_clear_pointer (&priv->pending_selection, nautilus_file_list_free);
+
+ if (do_reveal)
+ {
+ if (NAUTILUS_IS_LIST_VIEW (view) || NAUTILUS_IS_GRID_VIEW (view))
+ {
+ /* HACK: We should be able to directly call reveal_selection here,
+ * but at this point the GtkTreeView hasn't allocated the new nodes
+ * yet, and it has a bug in the scroll calculation dealing with this
+ * special case. It would always make the selection the top row, even
+ * if no scrolling would be neccessary to reveal it. So we let it
+ * allocate before revealing.
+ */
+ if (priv->reveal_selection_idle_id != 0)
+ {
+ g_source_remove (priv->reveal_selection_idle_id);
+ }
+ priv->reveal_selection_idle_id =
+ g_idle_add (reveal_selection_idle_callback, view);
+ }
+ else
+ {
+ nautilus_files_view_reveal_selection (view);
+ }
+ }
+ nautilus_files_view_display_selection_info (view);
+ }
+
+ priv->loading = FALSE;
+ g_signal_emit (view, signals[END_LOADING], 0, all_files_seen);
+ g_object_notify (G_OBJECT (view), "loading");
+
+ if (!priv->in_destruction)
+ {
+ nautilus_files_view_check_empty_states (view);
+ }
+
+ nautilus_profile_end (NULL);
+}
+
+
+typedef struct
+{
+ GHashTable *debuting_files;
+ GList *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;
+
+ nautilus_profile_start (NULL);
+
+ 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);
+ nautilus_files_view_reveal_selection (view);
+ g_signal_handlers_disconnect_by_func (view,
+ G_CALLBACK (debuting_files_add_files_callback),
+ data);
+ }
+
+ nautilus_profile_end (NULL);
+}
+
+typedef struct
+{
+ GList *added_files;
+ NautilusFilesView *directory_view;
+} CopyMoveDoneData;
+
+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);
+ nautilus_files_view_reveal_selection (directory_view);
+ }
+ 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 *view,
+ FileAndDirectory *fad)
+{
+ NautilusFilesViewPrivate *priv;
+
+ priv = nautilus_files_view_get_instance_private (view);
+
+ if (priv->model != fad->directory &&
+ g_list_find (priv->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 gboolean
+ready_to_load (NautilusFile *file)
+{
+ return nautilus_file_check_if_ready (file,
+ NAUTILUS_FILE_ATTRIBUTES_FOR_ICON);
+}
+
+/* Go through all the new added and changed files.
+ * Put any that are not ready to load in the non_ready_files hash table.
+ * Add all the rest to the old_added_files and old_changed_files lists.
+ * Sort the old_*_files lists if anything was added to them.
+ */
+static void
+process_new_files (NautilusFilesView *view)
+{
+ NautilusFilesViewPrivate *priv;
+ g_autolist (FileAndDirectory) new_added_files = NULL;
+ g_autolist (FileAndDirectory) new_changed_files = NULL;
+ GList *old_added_files;
+ GList *old_changed_files;
+ GHashTable *non_ready_files;
+ GList *node, *next;
+ FileAndDirectory *pending;
+ gboolean in_non_ready;
+
+ priv = nautilus_files_view_get_instance_private (view);
+
+ new_added_files = g_steal_pointer (&priv->new_added_files);
+ new_changed_files = g_steal_pointer (&priv->new_changed_files);
+
+ non_ready_files = priv->non_ready_files;
+
+ old_added_files = priv->old_added_files;
+ old_changed_files = priv->old_changed_files;
+
+ /* Newly added files go into the old_added_files list if they're
+ * ready, and into the hash table if they're not.
+ */
+ for (node = new_added_files; node != NULL; node = next)
+ {
+ next = node->next;
+ pending = (FileAndDirectory *) node->data;
+ in_non_ready = g_hash_table_contains (non_ready_files, pending);
+ if (nautilus_files_view_should_show_file (view, pending->file))
+ {
+ if (ready_to_load (pending->file))
+ {
+ if (in_non_ready)
+ {
+ g_hash_table_remove (non_ready_files, pending);
+ }
+ new_added_files = g_list_delete_link (new_added_files, node);
+ old_added_files = g_list_prepend (old_added_files, pending);
+ }
+ else
+ {
+ if (!in_non_ready)
+ {
+ new_added_files = g_list_delete_link (new_added_files, node);
+ g_hash_table_add (non_ready_files, pending);
+ }
+ }
+ }
+ }
+
+ /* Newly changed files go into the old_added_files list if they're ready
+ * and were seen non-ready in the past, into the old_changed_files list
+ * if they are read and were not seen non-ready in the past, and into
+ * the hash table if they're not ready.
+ */
+ for (node = new_changed_files; node != NULL; node = next)
+ {
+ next = node->next;
+ pending = (FileAndDirectory *) node->data;
+ if (!still_should_show_file (view, pending) || ready_to_load (pending->file))
+ {
+ if (g_hash_table_contains (non_ready_files, pending))
+ {
+ g_hash_table_remove (non_ready_files, pending);
+ if (still_should_show_file (view, pending))
+ {
+ new_changed_files = g_list_delete_link (new_changed_files, node);
+ old_added_files = g_list_prepend (old_added_files, pending);
+ }
+ }
+ else
+ {
+ new_changed_files = g_list_delete_link (new_changed_files, node);
+ old_changed_files = g_list_prepend (old_changed_files, pending);
+ }
+ }
+ }
+
+ if (old_added_files != priv->old_added_files)
+ {
+ priv->old_added_files = old_added_files;
+ }
+
+ if (old_changed_files != priv->old_changed_files)
+ {
+ priv->old_changed_files = old_changed_files;
+ }
+}
+
+static void
+on_end_file_changes (NautilusFilesView *view)
+{
+ NautilusFilesViewPrivate *priv;
+
+ priv = nautilus_files_view_get_instance_private (view);
+
+ /* Addition and removal of files modify the empty state */
+ nautilus_files_view_check_empty_states (view);
+ /* If the view is empty, zoom slider and sort menu are insensitive */
+ nautilus_files_view_update_toolbar_menus (view);
+
+ /* Reveal files that were pending to be revealed, only if all of them
+ * were acknowledged by the view
+ */
+ if (g_hash_table_size (priv->pending_reveal) > 0)
+ {
+ GList *keys;
+ GList *l;
+ gboolean all_files_acknowledged = TRUE;
+
+ keys = g_hash_table_get_keys (priv->pending_reveal);
+ for (l = keys; l && all_files_acknowledged; l = l->next)
+ {
+ all_files_acknowledged = GPOINTER_TO_UINT (g_hash_table_lookup (priv->pending_reveal,
+ l->data));
+ }
+
+ if (all_files_acknowledged)
+ {
+ nautilus_files_view_set_selection (NAUTILUS_VIEW (view), keys);
+ nautilus_files_view_reveal_selection (view);
+ g_hash_table_remove_all (priv->pending_reveal);
+ }
+
+ g_list_free (keys);
+ }
+}
+
+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
+process_old_files (NautilusFilesView *view)
+{
+ NautilusFilesViewPrivate *priv;
+ g_autolist (FileAndDirectory) files_added = NULL;
+ g_autolist (FileAndDirectory) files_changed = NULL;
+ FileAndDirectory *pending;
+ GList *files;
+ g_autoptr (GList) pending_additions = NULL;
+
+ priv = nautilus_files_view_get_instance_private (view);
+ files_added = g_steal_pointer (&priv->old_added_files);
+ files_changed = g_steal_pointer (&priv->old_changed_files);
+
+
+ if (files_added != NULL || files_changed != NULL)
+ {
+ gboolean send_selection_change = FALSE;
+
+ g_signal_emit (view, signals[BEGIN_FILE_CHANGES], 0);
+
+ for (GList *node = files_added; node != NULL; node = node->next)
+ {
+ pending = node->data;
+ pending_additions = g_list_prepend (pending_additions, pending->file);
+ /* Acknowledge the files that were pending to be revealed */
+ if (g_hash_table_contains (priv->pending_reveal, pending->file))
+ {
+ g_hash_table_insert (priv->pending_reveal,
+ pending->file,
+ GUINT_TO_POINTER (TRUE));
+ }
+ }
+ pending_additions = g_list_reverse (pending_additions);
+
+ if (files_added != NULL)
+ {
+ g_signal_emit (view,
+ signals[ADD_FILES], 0, pending_additions);
+ }
+
+ for (GList *node = files_changed; node != NULL; node = node->next)
+ {
+ gboolean should_show_file;
+ pending = node->data;
+ should_show_file = still_should_show_file (view, pending);
+ g_signal_emit (view,
+ signals[should_show_file ? FILE_CHANGED : REMOVE_FILE], 0,
+ pending->file, pending->directory);
+
+ /* Acknowledge the files that were pending to be revealed */
+ if (g_hash_table_contains (priv->pending_reveal, pending->file))
+ {
+ if (should_show_file)
+ {
+ g_hash_table_insert (priv->pending_reveal,
+ pending->file,
+ GUINT_TO_POINTER (TRUE));
+ }
+ else
+ {
+ g_hash_table_remove (priv->pending_reveal,
+ pending->file);
+ }
+ }
+ }
+
+ if (files_changed != NULL)
+ {
+ g_autolist (NautilusFile) selection = NULL;
+ selection = nautilus_view_get_selection (NAUTILUS_VIEW (view));
+ files = g_list_copy_deep (files_changed, (GCopyFunc) file_and_directory_get_file, NULL);
+ send_selection_change = _g_lists_sort_and_check_for_intersection
+ (&files, &selection);
+ nautilus_file_list_free (files);
+ }
+
+ if (send_selection_change)
+ {
+ /* Send a selection change since some file names could
+ * have changed.
+ */
+ nautilus_files_view_send_selection_change (view);
+ }
+
+ g_signal_emit (view, signals[END_FILE_CHANGES], 0);
+ }
+}
+
+static void
+display_pending_files (NautilusFilesView *view)
+{
+ NautilusFilesViewPrivate *priv;
+ g_autolist (NautilusFile) selection = NULL;
+
+ process_new_files (view);
+ process_old_files (view);
+
+ priv = nautilus_files_view_get_instance_private (view);
+ selection = nautilus_files_view_get_selection (NAUTILUS_VIEW (view));
+
+ if (selection == NULL &&
+ !priv->pending_selection &&
+ nautilus_view_is_searching (NAUTILUS_VIEW (view)))
+ {
+ nautilus_files_view_select_first (view);
+ }
+
+ if (priv->model != NULL
+ && nautilus_directory_are_all_files_seen (priv->model)
+ && g_hash_table_size (priv->non_ready_files) == 0)
+ {
+ done_loading (view, TRUE);
+ }
+}
+
+static gboolean
+display_selection_info_idle_callback (gpointer data)
+{
+ NautilusFilesView *view;
+ NautilusFilesViewPrivate *priv;
+
+ view = NAUTILUS_FILES_VIEW (data);
+ priv = nautilus_files_view_get_instance_private (view);
+
+ g_object_ref (G_OBJECT (view));
+
+ priv->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 *view)
+{
+ NautilusFilesViewPrivate *priv;
+
+ priv = nautilus_files_view_get_instance_private (view);
+
+ if (priv->update_context_menus_timeout_id != 0)
+ {
+ g_source_remove (priv->update_context_menus_timeout_id);
+ priv->update_context_menus_timeout_id = 0;
+ }
+}
+
+static void
+update_context_menus_if_pending (NautilusFilesView *view)
+{
+ NautilusFilesViewPrivate *priv = nautilus_files_view_get_instance_private (view);
+
+ if (priv->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;
+ NautilusFilesViewPrivate *priv;
+
+ view = NAUTILUS_FILES_VIEW (data);
+ priv = nautilus_files_view_get_instance_private (view);
+
+ g_object_ref (G_OBJECT (view));
+
+ priv->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;
+ NautilusFilesViewPrivate *priv;
+
+ view = NAUTILUS_FILES_VIEW (data);
+ priv = nautilus_files_view_get_instance_private (view);
+
+ g_object_ref (G_OBJECT (view));
+
+ priv->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)
+{
+ NautilusFilesViewPrivate *priv;
+
+ priv = nautilus_files_view_get_instance_private (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. */
+ priv->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 *view,
+ guint interval)
+{
+ NautilusFilesViewPrivate *priv;
+
+ priv = nautilus_files_view_get_instance_private (view);
+
+ /* No need to schedule an update if there's already one pending. */
+ if (priv->display_pending_source_id != 0)
+ {
+ return;
+ }
+
+ priv->display_pending_source_id =
+ g_timeout_add (interval, display_pending_callback, view);
+}
+
+static void
+unschedule_display_of_pending_files (NautilusFilesView *view)
+{
+ NautilusFilesViewPrivate *priv;
+
+ priv = nautilus_files_view_get_instance_private (view);
+
+ /* Get rid of source if it's active. */
+ if (priv->display_pending_source_id != 0)
+ {
+ g_source_remove (priv->display_pending_source_id);
+ priv->display_pending_source_id = 0;
+ }
+}
+
+static void
+queue_pending_files (NautilusFilesView *view,
+ NautilusDirectory *directory,
+ GList *files,
+ GList **pending_list)
+{
+ NautilusFilesViewPrivate *priv;
+ GList *fad_list;
+
+ priv = nautilus_files_view_get_instance_private (view);
+
+ 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 it can be a long wait, and we actually want to show files as
+ * they are getting found. So for search is fine if not all files are
+ * seen */
+ if (!priv->loading ||
+ (nautilus_directory_are_all_files_seen (directory) ||
+ nautilus_view_is_searching (NAUTILUS_VIEW (view))))
+ {
+ schedule_timeout_display_of_pending_files (view, priv->update_interval);
+ }
+}
+
+static void
+remove_changes_timeout_callback (NautilusFilesView *view)
+{
+ NautilusFilesViewPrivate *priv;
+
+ priv = nautilus_files_view_get_instance_private (view);
+
+ if (priv->changes_timeout_id != 0)
+ {
+ g_source_remove (priv->changes_timeout_id);
+ priv->changes_timeout_id = 0;
+ }
+}
+
+static void
+reset_update_interval (NautilusFilesView *view)
+{
+ NautilusFilesViewPrivate *priv;
+
+ priv = nautilus_files_view_get_instance_private (view);
+
+ priv->update_interval = UPDATE_INTERVAL_MIN;
+ remove_changes_timeout_callback (view);
+ /* Reschedule a pending timeout to idle */
+ if (priv->display_pending_source_id != 0)
+ {
+ schedule_idle_display_of_pending_files (view);
+ }
+}
+
+static gboolean
+changes_timeout_callback (gpointer data)
+{
+ gint64 now;
+ gint64 time_delta;
+ gboolean ret;
+ NautilusFilesView *view;
+ NautilusFilesViewPrivate *priv;
+
+ view = NAUTILUS_FILES_VIEW (data);
+ priv = nautilus_files_view_get_instance_private (view);
+
+ g_object_ref (G_OBJECT (view));
+
+ now = g_get_monotonic_time ();
+ time_delta = now - priv->last_queued;
+
+ if (time_delta < UPDATE_INTERVAL_RESET * 1000)
+ {
+ if (priv->update_interval < UPDATE_INTERVAL_MAX &&
+ priv->loading)
+ {
+ /* Increase */
+ priv->update_interval += UPDATE_INTERVAL_INC;
+ }
+ ret = TRUE;
+ }
+ else
+ {
+ /* Reset */
+ reset_update_interval (view);
+ ret = FALSE;
+ }
+
+ g_object_unref (G_OBJECT (view));
+
+ return ret;
+}
+
+static void
+schedule_changes (NautilusFilesView *view)
+{
+ NautilusFilesViewPrivate *priv;
+
+ priv = nautilus_files_view_get_instance_private (view);
+ /* Remember when the change was queued */
+ priv->last_queued = g_get_monotonic_time ();
+
+ /* No need to schedule if there are already changes pending or during loading */
+ if (priv->changes_timeout_id != 0 ||
+ priv->loading)
+ {
+ return;
+ }
+
+ priv->changes_timeout_id =
+ g_timeout_add (UPDATE_INTERVAL_TIMEOUT_INTERVAL, changes_timeout_callback, view);
+}
+
+static void
+files_added_callback (NautilusDirectory *directory,
+ GList *files,
+ gpointer callback_data)
+{
+ NautilusFilesViewPrivate *priv;
+ NautilusFilesView *view;
+ GtkWindow *window;
+ char *uri;
+
+ view = NAUTILUS_FILES_VIEW (callback_data);
+ priv = nautilus_files_view_get_instance_private (view);
+
+ nautilus_profile_start (NULL);
+
+ window = nautilus_files_view_get_containing_window (view);
+ uri = nautilus_files_view_get_uri (view);
+ DEBUG_FILES (files, "Files added in window %p: %s",
+ window, uri ? uri : "(no directory)");
+ g_free (uri);
+
+ schedule_changes (view);
+
+ queue_pending_files (view, directory, files, &priv->new_added_files);
+
+ /* The number of items could have changed */
+ schedule_update_status (view);
+
+ nautilus_profile_end (NULL);
+}
+
+static void
+files_changed_callback (NautilusDirectory *directory,
+ GList *files,
+ gpointer callback_data)
+{
+ NautilusFilesViewPrivate *priv;
+ NautilusFilesView *view;
+ GtkWindow *window;
+ char *uri;
+
+ view = NAUTILUS_FILES_VIEW (callback_data);
+ priv = nautilus_files_view_get_instance_private (view);
+
+ window = nautilus_files_view_get_containing_window (view);
+ uri = nautilus_files_view_get_uri (view);
+ DEBUG_FILES (files, "Files changed in window %p: %s",
+ window, uri ? uri : "(no directory)");
+ g_free (uri);
+
+ schedule_changes (view);
+
+ queue_pending_files (view, directory, files, &priv->new_changed_files);
+
+ /* The free space or the number of items could have changed */
+ schedule_update_status (view);
+
+ /* A change in MIME type could affect the Open with menu, for
+ * one thing, so we need to update menus when files change.
+ */
+ schedule_update_context_menus (view);
+}
+
+static void
+done_loading_callback (NautilusDirectory *directory,
+ gpointer callback_data)
+{
+ NautilusFilesView *view;
+ NautilusFilesViewPrivate *priv;
+
+ view = NAUTILUS_FILES_VIEW (callback_data);
+ priv = nautilus_files_view_get_instance_private (view);
+
+ nautilus_profile_start (NULL);
+ process_new_files (view);
+ if (g_hash_table_size (priv->non_ready_files) == 0)
+ {
+ /* Unschedule a pending update and schedule a new one with the minimal
+ * update interval. This gives the view a short chance at gathering the
+ * (cached) deep counts.
+ */
+ unschedule_display_of_pending_files (view);
+ schedule_timeout_display_of_pending_files (view, UPDATE_INTERVAL_MIN);
+
+ remove_loading_floating_bar (view);
+ }
+ nautilus_profile_end (NULL);
+}
+
+static void
+load_error_callback (NautilusDirectory *directory,
+ GError *error,
+ gpointer callback_data)
+{
+ NautilusFilesView *view;
+
+ 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
+ (nautilus_files_view_get_directory_as_file (view),
+ error,
+ nautilus_files_view_get_containing_window (view));
+}
+
+void
+nautilus_files_view_add_subdirectory (NautilusFilesView *view,
+ NautilusDirectory *directory)
+{
+ NautilusFileAttributes attributes;
+ NautilusFilesViewPrivate *priv;
+
+ priv = nautilus_files_view_get_instance_private (view);
+
+ g_return_if_fail (!g_list_find (priv->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,
+ &priv->model,
+ priv->show_hidden_files,
+ attributes,
+ files_added_callback, view);
+
+ g_signal_connect
+ (directory, "files-added",
+ G_CALLBACK (files_added_callback), view);
+ g_signal_connect
+ (directory, "files-changed",
+ G_CALLBACK (files_changed_callback), view);
+
+ priv->subdirectory_list = g_list_prepend (
+ priv->subdirectory_list, directory);
+}
+
+void
+nautilus_files_view_remove_subdirectory (NautilusFilesView *view,
+ NautilusDirectory *directory)
+{
+ NautilusFilesViewPrivate *priv;
+ priv = nautilus_files_view_get_instance_private (view);
+
+ g_return_if_fail (g_list_find (priv->subdirectory_list, directory));
+
+ priv->subdirectory_list = g_list_remove (
+ priv->subdirectory_list, directory);
+
+ g_signal_handlers_disconnect_by_func (directory,
+ G_CALLBACK (files_added_callback),
+ view);
+ g_signal_handlers_disconnect_by_func (directory,
+ G_CALLBACK (files_changed_callback),
+ view);
+
+ nautilus_directory_file_monitor_remove (directory, &priv->model);
+
+ 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 *view)
+{
+ NautilusFilesViewPrivate *priv;
+
+ g_return_val_if_fail (NAUTILUS_IS_FILES_VIEW (view), FALSE);
+
+ priv = nautilus_files_view_get_instance_private (view);
+
+ return priv->loading;
+}
+
+/**
+ * nautilus_files_view_get_model:
+ *
+ * Get the model for this NautilusFilesView.
+ * @view: NautilusFilesView of interest.
+ *
+ * Return value: NautilusDirectory for this view.
+ *
+ **/
+NautilusDirectory *
+nautilus_files_view_get_model (NautilusFilesView *view)
+{
+ NautilusFilesViewPrivate *priv;
+
+ g_return_val_if_fail (NAUTILUS_IS_FILES_VIEW (view), NULL);
+
+ priv = nautilus_files_view_get_instance_private (view);
+
+ return priv->model;
+}
+
+GtkWidget *
+nautilus_files_view_get_content_widget (NautilusFilesView *view)
+{
+ NautilusFilesViewPrivate *priv;
+
+ g_return_val_if_fail (NAUTILUS_IS_FILES_VIEW (view), NULL);
+
+ priv = nautilus_files_view_get_instance_private (view);
+
+ return priv->scrolled_window;
+}
+
+/* home_dir_in_selection()
+ *
+ * Return TRUE if the home directory is in the selection.
+ */
+
+static gboolean
+home_dir_in_selection (GList *selection)
+{
+ for (GList *node = selection; node != NULL; node = node->next)
+ {
+ if (nautilus_file_is_home (NAUTILUS_FILE (node->data)))
+ {
+ return TRUE;
+ }
+ }
+
+ return FALSE;
+}
+
+static void
+trash_or_delete_done_cb (GHashTable *debuting_uris,
+ gboolean user_cancel,
+ NautilusFilesView *view)
+{
+ NautilusFilesViewPrivate *priv;
+
+ priv = nautilus_files_view_get_instance_private (view);
+ if (user_cancel)
+ {
+ priv->selection_was_removed = FALSE;
+ }
+}
+
+static void
+trash_or_delete_files (GtkWindow *parent_window,
+ const GList *files,
+ NautilusFilesView *view)
+{
+ GList *locations;
+ const GList *node;
+
+ locations = NULL;
+ for (node = files; node != NULL; node = node->next)
+ {
+ locations = g_list_prepend (locations,
+ nautilus_file_get_location ((NautilusFile *) node->data));
+ }
+
+ locations = g_list_reverse (locations);
+
+ nautilus_file_operations_trash_or_delete_async (locations,
+ parent_window,
+ NULL,
+ (NautilusDeleteCallback) trash_or_delete_done_cb,
+ view);
+ g_list_free_full (locations, g_object_unref);
+}
+
+NautilusFile *
+nautilus_files_view_get_directory_as_file (NautilusFilesView *view)
+{
+ NautilusFilesViewPrivate *priv;
+
+ g_assert (NAUTILUS_IS_FILES_VIEW (view));
+
+ priv = nautilus_files_view_get_instance_private (view);
+
+ return priv->directory_as_file;
+}
+
+static GdkTexture *
+get_menu_icon_for_file (NautilusFile *file,
+ GtkWidget *widget)
+{
+ int scale = gtk_widget_get_scale_factor (widget);
+
+ return nautilus_file_get_icon_texture (file, 16, scale, 0);
+}
+
+static GList *
+get_extension_selection_menu_items (NautilusFilesView *view)
+{
+ GList *items;
+ GList *providers;
+ GList *l;
+ g_autolist (NautilusFile) selection = NULL;
+
+ selection = nautilus_view_get_selection (NAUTILUS_VIEW (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 *view)
+{
+ NautilusFilesViewPrivate *priv;
+ GList *items;
+ GList *providers;
+ GList *l;
+
+ priv = nautilus_files_view_get_instance_private (view);
+ 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 (priv->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 *view,
+ NautilusMenuItem *item,
+ const char *action_name)
+{
+ NautilusFilesViewPrivate *priv;
+ gboolean sensitive;
+ GSimpleAction *action;
+
+ priv = nautilus_files_view_get_instance_private (view);
+
+ 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 (priv->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);
+ }
+
+ nautilus_view_set_extensions_background_menu (NAUTILUS_VIEW (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 (GList *selection,
+ NautilusDirectory *model)
+{
+ char **parameters;
+ g_autoptr (GFile) model_location = NULL;
+ int i;
+
+ if (model == NULL)
+ {
+ return NULL;
+ }
+
+ parameters = g_new (char *, g_list_length (selection) + 1);
+
+ model_location = nautilus_directory_get_location (model);
+
+ 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 (model_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 (GList *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 (GList *selection)
+{
+ return get_file_paths_or_uris_as_newline_delimited_string (selection, TRUE);
+}
+
+static char *
+get_file_uris_as_newline_delimited_string (GList *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 *view,
+ GList *selected_files)
+{
+ g_autofree gchar *file_paths = NULL;
+ g_autofree gchar *uris = NULL;
+ g_autofree gchar *uri = NULL;
+ NautilusFilesViewPrivate *priv;
+
+ priv = nautilus_files_view_get_instance_private (view);
+
+ 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 (priv->model);
+ 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;
+ NautilusFilesViewPrivate *priv;
+ 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;
+ priv = nautilus_files_view_get_instance_private (launch_parameters->directory_view);
+
+ 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_view_get_selection (NAUTILUS_VIEW (launch_parameters->directory_view));
+ set_script_environment_variables (launch_parameters->directory_view, selection);
+
+ parameters = get_file_names_as_parameter_array (selection, priv->model);
+
+ display = gtk_widget_get_display (GTK_WIDGET (launch_parameters->directory_view));
+
+ 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 *view,
+ NautilusFile *file,
+ GMenu *menu)
+{
+ NautilusFilesViewPrivate *priv;
+ gchar *name;
+ g_autofree gchar *uri = NULL;
+ g_autofree gchar *escaped_uri = NULL;
+ GdkTexture *mimetype_icon;
+ gchar *action_name, *detailed_action_name;
+ ScriptLaunchParameters *launch_parameters;
+ GAction *action;
+ GMenuItem *menu_item;
+ const gchar *shortcut;
+
+ priv = nautilus_files_view_get_instance_private (view);
+ launch_parameters = script_launch_parameters_new (file, view);
+
+ 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 (priv->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);
+
+ mimetype_icon = get_menu_icon_for_file (file, GTK_WIDGET (view));
+ if (mimetype_icon != NULL)
+ {
+ g_menu_item_set_icon (menu_item, G_ICON (mimetype_icon));
+ g_object_unref (mimetype_icon);
+ }
+
+ g_menu_append_item (menu, menu_item);
+
+ if ((shortcut = g_hash_table_lookup (script_accels, name)))
+ {
+ nautilus_application_set_accelerator (g_application_get_default (),
+ detailed_action_name, shortcut);
+ }
+
+ g_free (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, **result;
+ GError *error = NULL;
+ const int max_len = 100;
+ int i;
+
+ 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 (i = 0; lines[i] && (strstr (lines[i], " ") > 0); i++)
+ {
+ result = g_strsplit (lines[i], " ", 2);
+ g_hash_table_insert (script_accels,
+ g_strndup (result[1], max_len),
+ g_strndup (result[0], max_len));
+ g_strfreev (result);
+ }
+
+ g_free (contents);
+ g_strfreev (lines);
+ }
+ else
+ {
+ 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;
+ gchar *file_name;
+ 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)
+ {
+ 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);
+ g_free (file_name);
+ }
+
+ 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)
+{
+ NautilusFilesViewPrivate *priv;
+ g_autolist (NautilusDirectory) sorted_copy = NULL;
+ g_autoptr (NautilusDirectory) directory = NULL;
+ g_autoptr (GMenu) submenu = NULL;
+
+ priv = nautilus_files_view_get_instance_private (view);
+
+ sorted_copy = nautilus_directory_list_sort_by_uri
+ (nautilus_directory_list_copy (priv->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 (&priv->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 *view,
+ NautilusFile *file,
+ GMenu *menu)
+{
+ NautilusFilesViewPrivate *priv;
+ char *uri, *name;
+ g_autofree gchar *escaped_uri = NULL;
+ GdkTexture *mimetype_icon;
+ char *action_name, *detailed_action_name;
+ CreateTemplateParameters *parameters;
+ GAction *action;
+ g_autofree char *label = NULL;
+ GMenuItem *menu_item;
+
+ priv = nautilus_files_view_get_instance_private (view);
+ 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, view);
+
+ 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 (priv->view_action_group), action);
+
+ detailed_action_name = g_strconcat ("view.", action_name, NULL);
+ label = eel_str_double_underscores (name);
+ menu_item = g_menu_item_new (label, detailed_action_name);
+
+ mimetype_icon = get_menu_icon_for_file (file, GTK_WIDGET (view));
+ if (mimetype_icon != NULL)
+ {
+ g_menu_item_set_icon (menu_item, G_ICON (mimetype_icon));
+ g_object_unref (mimetype_icon);
+ }
+
+ g_menu_append_item (menu, menu_item);
+
+ g_free (name);
+ 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)
+{
+ NautilusFilesViewPrivate *priv;
+ NautilusDirectory *templates_directory;
+ GList *node, *next;
+ char *templates_uri;
+
+ priv = nautilus_files_view_get_instance_private (view);
+
+ for (node = priv->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)
+{
+ NautilusFilesViewPrivate *priv;
+ 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);
+
+ priv = nautilus_files_view_get_instance_private (view);
+
+ 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, priv->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)
+ {
+ g_autofree char *display_name = NULL;
+ g_autofree char *label = NULL;
+
+ display_name = nautilus_file_get_display_name (file);
+ label = eel_str_double_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)
+{
+ NautilusFilesViewPrivate *priv;
+ g_autolist (NautilusDirectory) sorted_copy = NULL;
+ g_autoptr (NautilusDirectory) directory = NULL;
+ g_autoptr (GMenuModel) submenu = NULL;
+ g_autofree char *templates_directory_uri = NULL;
+
+ priv = nautilus_files_view_get_instance_private (view);
+
+ if (!nautilus_should_use_templates_directory ())
+ {
+ nautilus_view_set_templates_menu (NAUTILUS_VIEW (view), NULL);
+ return;
+ }
+
+ templates_directory_uri = nautilus_get_templates_directory_uri ();
+ sorted_copy = nautilus_directory_list_sort_by_uri
+ (nautilus_directory_list_copy (priv->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);
+
+ nautilus_view_set_templates_menu (NAUTILUS_VIEW (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);
+}
+
+typedef struct _CopyCallbackData
+{
+ NautilusFilesView *view;
+ GList *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 (GtkDialog *dialog,
+ gint response_id,
+ gpointer user_data)
+{
+ CopyCallbackData *copy_data = user_data;
+
+ if (response_id == GTK_RESPONSE_OK)
+ {
+ g_autoptr (GFile) target_location = NULL;
+ char *target_uri;
+ GList *uris, *l;
+
+ target_location = gtk_file_chooser_get_file (GTK_FILE_CHOOSER (dialog));
+ 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);
+ }
+
+ copy_data_free (copy_data);
+ gtk_window_destroy (GTK_WINDOW (dialog));
+}
+
+static void
+copy_or_move_selection (NautilusFilesView *view,
+ gboolean is_move)
+{
+ NautilusFilesViewPrivate *priv;
+ GtkWidget *dialog;
+ g_autoptr (GFile) location = NULL;
+ CopyCallbackData *copy_data;
+ GList *selection;
+ const gchar *title;
+ NautilusDirectory *directory;
+
+ priv = nautilus_files_view_get_instance_private (view);
+
+ if (is_move)
+ {
+ title = _("Select Move Destination");
+ }
+ else
+ {
+ title = _("Select Copy Destination");
+ }
+
+ selection = nautilus_files_view_get_selection_for_file_transfer (view);
+
+ dialog = gtk_file_chooser_dialog_new (title,
+ GTK_WINDOW (nautilus_files_view_get_window (view)),
+ GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER,
+ _("_Cancel"), GTK_RESPONSE_CANCEL,
+ _("_Select"), GTK_RESPONSE_OK,
+ NULL);
+
+ gtk_dialog_set_default_response (GTK_DIALOG (dialog),
+ GTK_RESPONSE_OK);
+
+ gtk_window_set_destroy_with_parent (GTK_WINDOW (dialog), TRUE);
+ gtk_window_set_modal (GTK_WINDOW (dialog), TRUE);
+
+ copy_data = g_new0 (CopyCallbackData, 1);
+ copy_data->view = view;
+ copy_data->selection = selection;
+ copy_data->is_move = is_move;
+
+ if (nautilus_view_is_searching (NAUTILUS_VIEW (view)))
+ {
+ directory = nautilus_search_directory_get_base_model (NAUTILUS_SEARCH_DIRECTORY (priv->model));
+ location = nautilus_directory_get_location (directory);
+ }
+ else if (showing_starred_directory (view))
+ {
+ location = nautilus_file_get_parent_location (NAUTILUS_FILE (selection->data));
+ }
+ else
+ {
+ location = nautilus_directory_get_location (priv->model);
+ }
+
+ gtk_file_chooser_set_current_folder (GTK_FILE_CHOOSER (dialog), location, NULL);
+ g_signal_connect (dialog, "response",
+ G_CALLBACK (on_destination_dialog_response),
+ copy_data);
+
+ gtk_widget_show (dialog);
+}
+
+static void
+action_copy (GSimpleAction *action,
+ GVariant *state,
+ gpointer user_data)
+{
+ NautilusFilesView *view;
+ GdkClipboard *clipboard;
+ GList *selection;
+
+ 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);
+
+ nautilus_file_list_free (selection);
+}
+
+static void
+action_cut (GSimpleAction *action,
+ GVariant *state,
+ gpointer user_data)
+{
+ NautilusFilesView *view;
+ GList *selection;
+ 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);
+
+ nautilus_file_list_free (selection);
+}
+
+static void
+action_copy_current_location (GSimpleAction *action,
+ GVariant *state,
+ gpointer user_data)
+{
+ NautilusFilesView *view;
+ GdkClipboard *clipboard;
+ GList *files;
+ NautilusFilesViewPrivate *priv;
+
+ view = NAUTILUS_FILES_VIEW (user_data);
+ priv = nautilus_files_view_get_instance_private (view);
+
+ if (priv->directory_as_file != NULL)
+ {
+ files = g_list_append (NULL, nautilus_file_ref (priv->directory_as_file));
+
+ clipboard = gtk_widget_get_clipboard (GTK_WIDGET (view));
+ 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;
+ GList *selection;
+ 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);
+ nautilus_file_list_free (selection);
+}
+
+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
+paste_into_clipboard_received_callback (GObject *source_object,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ NautilusFilesView *view = NAUTILUS_FILES_VIEW (source_object);
+ NautilusClipboard *clip;
+ g_autofree char *directory_uri = user_data;
+
+ clip = nautilus_files_view_get_clipboard_finish (view, res, NULL);
+ if (clip != NULL)
+ {
+ paste_clipboard_data (view, clip, directory_uri);
+ }
+}
+
+static void
+paste_into (NautilusFilesView *view,
+ NautilusFile *target)
+{
+ g_assert (NAUTILUS_IS_FILES_VIEW (view));
+ g_assert (NAUTILUS_IS_FILE (target));
+
+ nautilus_files_view_get_clipboard_async (view,
+ paste_into_clipboard_received_callback,
+ nautilus_file_get_activation_uri (target));
+}
+
+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_view_get_selection (NAUTILUS_VIEW (view));
+ if (selection != NULL)
+ {
+ paste_into (view, NAUTILUS_FILE (selection->data));
+ }
+}
+
+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_view_get_selection (NAUTILUS_VIEW (view));
+
+ if (selection != NULL)
+ {
+ /* If there is more than one file selected, invoke a batch renamer */
+ if (selection->next != NULL)
+ {
+ NautilusWindow *window;
+
+ window = nautilus_files_view_get_window (view);
+ gtk_widget_set_cursor_from_name (GTK_WIDGET (window), "progress");
+
+ dialog = nautilus_batch_rename_dialog_new (selection,
+ nautilus_files_view_get_model (view),
+ window);
+
+ gtk_widget_show (GTK_WIDGET (dialog));
+ }
+ 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)
+{
+ NautilusFilesViewPrivate *priv;
+ ExtractData *data;
+ GList *l;
+ gboolean all_files_acknowledged;
+
+ data = user_data;
+
+ if (data->view == NULL)
+ {
+ goto out;
+ }
+
+ priv = nautilus_files_view_get_instance_private (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)
+ {
+ GList *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 (NAUTILUS_VIEW (data->view),
+ selection);
+ nautilus_files_view_reveal_selection (data->view);
+
+ nautilus_file_list_free (selection);
+ }
+ else
+ {
+ for (l = outputs; l != NULL; l = l->next)
+ {
+ gboolean acknowledged;
+
+ acknowledged = g_hash_table_contains (data->added_locations,
+ l->data);
+
+ g_hash_table_insert (priv->pending_reveal,
+ nautilus_file_get (l->data),
+ GUINT_TO_POINTER (acknowledged));
+ }
+ }
+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)
+{
+ GList *locations = NULL;
+ GList *l;
+ gboolean extracting_to_current_directory;
+
+ if (files == NULL)
+ {
+ return;
+ }
+
+ for (l = files; l != NULL; l = l->next)
+ {
+ locations = g_list_prepend (locations,
+ nautilus_file_get_location (l->data));
+ }
+
+ locations = g_list_reverse (locations);
+
+ extracting_to_current_directory = g_file_equal (destination_directory,
+ nautilus_view_get_location (NAUTILUS_VIEW (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);
+ }
+
+ g_list_free_full (locations, g_object_unref);
+}
+
+typedef struct
+{
+ NautilusFilesView *view;
+ GList *files;
+} ExtractToData;
+
+static void
+on_extract_destination_dialog_response (GtkDialog *dialog,
+ gint response_id,
+ gpointer user_data)
+{
+ ExtractToData *data;
+
+ data = user_data;
+
+ if (response_id == GTK_RESPONSE_OK)
+ {
+ g_autoptr (GFile) destination_directory = NULL;
+
+ destination_directory = gtk_file_chooser_get_file (GTK_FILE_CHOOSER (dialog));
+
+ extract_files (data->view, data->files, destination_directory);
+ }
+
+ gtk_window_destroy (GTK_WINDOW (dialog));
+ nautilus_file_list_free (data->files);
+ g_free (data);
+}
+
+static void
+extract_files_to_chosen_location (NautilusFilesView *view,
+ GList *files)
+{
+ NautilusFilesViewPrivate *priv;
+ ExtractToData *data;
+ GtkWidget *dialog;
+ g_autoptr (GFile) location = NULL;
+
+ priv = nautilus_files_view_get_instance_private (view);
+
+ if (files == NULL)
+ {
+ return;
+ }
+
+ data = g_new (ExtractToData, 1);
+
+ dialog = gtk_file_chooser_dialog_new (_("Select Extract Destination"),
+ GTK_WINDOW (nautilus_files_view_get_window (view)),
+ GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER,
+ _("_Cancel"), GTK_RESPONSE_CANCEL,
+ _("_Select"), GTK_RESPONSE_OK,
+ NULL);
+
+ gtk_dialog_set_default_response (GTK_DIALOG (dialog),
+ GTK_RESPONSE_OK);
+
+ gtk_window_set_destroy_with_parent (GTK_WINDOW (dialog), TRUE);
+ gtk_window_set_modal (GTK_WINDOW (dialog), TRUE);
+
+ /* The file chooser 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_view_is_searching (NAUTILUS_VIEW (view)))
+ {
+ NautilusSearchDirectory *search_directory;
+ NautilusDirectory *directory;
+
+ search_directory = NAUTILUS_SEARCH_DIRECTORY (priv->model);
+ directory = nautilus_search_directory_get_base_model (search_directory);
+ location = nautilus_directory_get_location (directory);
+ }
+ else
+ {
+ location = nautilus_directory_get_location (priv->model);
+ }
+
+ gtk_file_chooser_set_current_folder (GTK_FILE_CHOOSER (dialog), location, NULL);
+
+ data->view = view;
+ data->files = nautilus_file_list_copy (files);
+
+ g_signal_connect (dialog, "response",
+ G_CALLBACK (on_extract_destination_dialog_response),
+ data);
+
+ gtk_widget_show (dialog);
+}
+
+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_view_get_selection (NAUTILUS_VIEW (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_view_get_selection (NAUTILUS_VIEW (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;
+ XdpParent *parent;
+ GtkWidget *toplevel;
+
+ portal = xdp_portal_new ();
+ 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 (NAUTILUS_VIEW (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_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 = NULL;
+ g_autofree gchar *archive_directory_name = NULL;
+ g_autoptr (GFile) archive_directory = NULL;
+ g_autoptr (GFile) archive_location = NULL;
+
+ for (GList *l = selection; l != NULL; l = l->next)
+ {
+ source_locations = g_list_prepend (source_locations,
+ nautilus_file_get_location (l->data));
+ }
+ source_locations = g_list_reverse (source_locations);
+ 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 (GList *selection)
+{
+ NautilusFile *file;
+
+ if (g_list_length (selection) != 1)
+ {
+ 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)
+{
+ NautilusFilesView *view;
+ g_autolist (NautilusFile) selection = NULL;
+ g_autofree char *old_working_dir = NULL;
+ g_autofree char *uri = NULL;
+ g_autofree char *executable_path = NULL;
+ g_autofree char *quoted_path = NULL;
+ GtkWindow *parent_window;
+ GdkDisplay *display;
+
+ g_assert (NAUTILUS_IS_FILES_VIEW (user_data));
+
+ view = NAUTILUS_FILES_VIEW (user_data);
+
+ selection = nautilus_view_get_selection (NAUTILUS_VIEW (view));
+
+ if (!can_run_in_terminal (selection))
+ {
+ return;
+ }
+
+ old_working_dir = change_to_view_directory (view);
+
+ uri = nautilus_file_get_activation_uri (NAUTILUS_FILE (selection->data));
+ executable_path = g_filename_from_uri (uri, NULL, NULL);
+ quoted_path = g_shell_quote (executable_path);
+
+ parent_window = nautilus_files_view_get_containing_window (view);
+ display = gtk_widget_get_display (GTK_WIDGET (parent_window));
+
+ DEBUG ("Launching in terminal %s", quoted_path);
+
+ nautilus_launch_application_from_command (display, quoted_path, TRUE, NULL);
+
+ g_chdir (old_working_dir);
+}
+
+static gboolean
+can_set_wallpaper (GList *selection)
+{
+ NautilusFile *file;
+
+ if (g_list_length (selection) != 1)
+ {
+ 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_autofree gchar *uri = NULL;
+ XdpParent *parent = NULL;
+ GtkWidget *toplevel;
+
+ portal = xdp_portal_new ();
+ 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_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)
+{
+ 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)))
+ {
+ char *text;
+ char *name;
+ name = nautilus_file_get_display_name (file);
+ /* Translators: %s is a file name formatted for display */
+ text = g_strdup_printf (_("Unable to access “%s”"), name);
+ show_dialog (text,
+ error->message,
+ GTK_WINDOW (nautilus_files_view_get_window (view)),
+ GTK_MESSAGE_ERROR);
+ g_free (text);
+ g_free (name);
+ }
+}
+
+static void
+file_unmount_callback (NautilusFile *file,
+ GFile *result_location,
+ GError *error,
+ gpointer callback_data)
+{
+ NautilusFilesView *view;
+
+ view = NAUTILUS_FILES_VIEW (callback_data);
+ g_object_unref (view);
+
+ if (error != NULL &&
+ (error->domain != G_IO_ERROR ||
+ (error->code != G_IO_ERROR_CANCELLED &&
+ error->code != G_IO_ERROR_FAILED_HANDLED)))
+ {
+ char *text;
+ char *name;
+ name = nautilus_file_get_display_name (file);
+ /* Translators: %s is a file name formatted for display */
+ text = g_strdup_printf (_("Unable to remove “%s”"), name);
+ show_dialog (text,
+ error->message,
+ GTK_WINDOW (nautilus_files_view_get_window (view)),
+ GTK_MESSAGE_ERROR);
+ g_free (text);
+ g_free (name);
+ }
+}
+
+static void
+file_eject_callback (NautilusFile *file,
+ GFile *result_location,
+ GError *error,
+ gpointer callback_data)
+{
+ NautilusFilesView *view;
+
+ view = NAUTILUS_FILES_VIEW (callback_data);
+ g_object_unref (view);
+
+ if (error != NULL &&
+ (error->domain != G_IO_ERROR ||
+ (error->code != G_IO_ERROR_CANCELLED &&
+ error->code != G_IO_ERROR_FAILED_HANDLED)))
+ {
+ char *text;
+ char *name;
+ name = nautilus_file_get_display_name (file);
+ /* Translators: %s is a file name formatted for display */
+ text = g_strdup_printf (_("Unable to eject “%s”"), name);
+ show_dialog (text,
+ error->message,
+ GTK_WINDOW (nautilus_files_view_get_window (view)),
+ GTK_MESSAGE_ERROR);
+ g_free (text);
+ g_free (name);
+ }
+}
+
+static void
+file_stop_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)))
+ {
+ show_dialog (_("Unable to stop drive"),
+ error->message,
+ GTK_WINDOW (nautilus_files_view_get_window (view)),
+ GTK_MESSAGE_ERROR);
+ }
+}
+
+static void
+action_mount_volume (GSimpleAction *action,
+ GVariant *state,
+ gpointer user_data)
+{
+ NautilusFile *file;
+ GList *selection, *l;
+ NautilusFilesView *view;
+ GMountOperation *mount_op;
+
+ view = NAUTILUS_FILES_VIEW (user_data);
+
+ selection = nautilus_view_get_selection (NAUTILUS_VIEW (view));
+ for (l = selection; l != NULL; l = l->next)
+ {
+ file = NAUTILUS_FILE (l->data);
+
+ if (nautilus_file_can_mount (file))
+ {
+ 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,
+ view);
+ g_object_unref (mount_op);
+ }
+ }
+ nautilus_file_list_free (selection);
+}
+
+static void
+action_unmount_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_view_get_selection (NAUTILUS_VIEW (view));
+
+ for (l = selection; l != NULL; l = l->next)
+ {
+ file = NAUTILUS_FILE (l->data);
+ if (nautilus_file_can_unmount (file))
+ {
+ GMountOperation *mount_op;
+ 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_view_get_selection (NAUTILUS_VIEW (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)))
+ {
+ char *text;
+ char *name;
+ name = nautilus_file_get_display_name (file);
+ /* Translators: %s is a file name formatted for display */
+ text = g_strdup_printf (_("Unable to start “%s”"), name);
+ show_dialog (text,
+ error->message,
+ GTK_WINDOW (nautilus_files_view_get_window (view)),
+ GTK_MESSAGE_ERROR);
+ g_free (text);
+ g_free (name);
+ }
+}
+
+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_view_get_selection (NAUTILUS_VIEW (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_view_get_selection (NAUTILUS_VIEW (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, 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;
+ NautilusView *view;
+
+ view = NAUTILUS_VIEW (user_data);
+
+ selection = nautilus_view_get_selection (view);
+ 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);
+ }
+ }
+}
+
+const GActionEntry view_entries[] =
+{
+ /* Toolbar menu */
+ { "zoom-in", action_zoom_in },
+ { "zoom-out", action_zoom_out },
+ { "zoom-standard", action_zoom_standard },
+ { "show-hidden-files", NULL, NULL, "true", action_show_hidden_files },
+ /* Background menu */
+ { "empty-trash", action_empty_trash },
+ { "new-folder", action_new_folder },
+ { "select-all", action_select_all },
+ { "paste", action_paste_files },
+ { "copy-current-location", action_copy_current_location },
+ { "paste_accel", action_paste_files_accel },
+ { "create-link", action_create_links },
+ { "create-link-shortcut", action_create_links },
+ /* Selection menu */
+ { "new-folder-with-selection", action_new_folder_with_selection },
+ { "open-scripts-folder", action_open_scripts_folder },
+ { "open-item-location", action_open_item_location },
+ { "open-with-default-application", action_open_with_default_application },
+ { "open-with-other-application", action_open_with_other_application },
+ { "open-current-directory-with-other-application", action_open_current_directory_with_other_application },
+ { "open-item-new-window", action_open_item_new_window },
+ { "open-item-new-tab", action_open_item_new_tab },
+ { "cut", action_cut},
+ { "copy", action_copy},
+ { "create-link-in-place", action_create_links_in_place },
+ { "create-link-in-place-shortcut", action_create_links_in_place },
+ { "move-to", action_move_to},
+ { "copy-to", action_copy_to},
+ { "move-to-trash", action_move_to_trash},
+ { "delete-from-trash", action_delete },
+ { "star", action_star},
+ { "unstar", 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. */
+ { "delete-permanently-shortcut", action_delete },
+ { "delete-permanently-menu-item", 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 */
+ { "permanent-delete-permanently-menu-item", action_delete },
+ { "remove-from-recent", action_remove_from_recent },
+ { "restore-from-trash", action_restore_from_trash},
+ { "paste-into", action_paste_files_into },
+ { "rename", action_rename},
+ { "extract-here", action_extract_here },
+ { "extract-to", action_extract_to },
+ { "compress", action_compress },
+ { "send-email", action_send_email },
+ { "console", action_open_console },
+ { "current-directory-console", action_current_dir_open_console },
+ { "properties", action_properties},
+ { "current-directory-properties", action_current_dir_properties},
+ { "run-in-terminal", action_run_in_terminal },
+ { "set-as-wallpaper", action_set_as_wallpaper },
+ { "mount-volume", action_mount_volume },
+ { "unmount-volume", action_unmount_volume },
+ { "eject-volume", action_eject_volume },
+ { "start-volume", action_start_volume },
+ { "stop-volume", action_stop_volume },
+ { "detect-media", action_detect_media },
+ /* Only accesible by shorcuts */
+ { "select-pattern", action_select_pattern },
+ { "invert-selection", action_invert_selection },
+ { "preview-selection", action_preview_selection },
+ { "popup-menu", 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 *view = NAUTILUS_FILES_VIEW (source_object);
+ NautilusFilesViewPrivate *priv = nautilus_files_view_get_instance_private (view);
+ NautilusClipboard *clip;
+ 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;
+
+ clip = nautilus_files_view_get_clipboard_finish (view, res, NULL);
+
+ if (priv->in_destruction ||
+ !priv->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 (view);
+ selection_contains_recent = showing_recent_directory (view);
+ selection_contains_starred = showing_starred_directory (view);
+ can_link_from_copied_files = clip != NULL && !nautilus_clipboard_is_cut (clip) &&
+ !selection_contains_recent && !selection_contains_starred &&
+ !is_read_only;
+
+ action = g_action_map_lookup_action (G_ACTION_MAP (priv->view_action_group),
+ "create-link");
+ g_simple_action_set_enabled (G_SIMPLE_ACTION (action),
+ can_link_from_copied_files &&
+ settings_show_create_link);
+ action = g_action_map_lookup_action (G_ACTION_MAP (priv->view_action_group),
+ "create-link-shortcut");
+ g_simple_action_set_enabled (G_SIMPLE_ACTION (action),
+ can_link_from_copied_files &&
+ !settings_show_create_link);
+}
+
+static void
+update_actions_state_for_clipboard_targets (NautilusFilesView *view)
+{
+ NautilusFilesViewPrivate *priv;
+ GdkClipboard *clipboard;
+ GdkContentFormats *formats;
+ gboolean is_data_copied;
+ GAction *action;
+
+ priv = nautilus_files_view_get_instance_private (view);
+ clipboard = gtk_widget_get_clipboard (GTK_WIDGET (view));
+ formats = gdk_clipboard_get_formats (clipboard);
+ is_data_copied = gdk_content_formats_contain_gtype (formats, NAUTILUS_TYPE_CLIPBOARD);
+
+ action = g_action_map_lookup_action (G_ACTION_MAP (priv->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 (priv->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 (priv->view_action_group),
+ "create-link");
+
+ g_simple_action_set_enabled (G_SIMPLE_ACTION (action),
+ is_data_copied && g_action_get_enabled (action));
+}
+
+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 (GList *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 (g_list_length (files) == 1)
+ {
+ 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 (g_list_length (original_dirs) == 1)
+ {
+ 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);
+
+ /* 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;
+}
+
+GActionGroup *
+nautilus_files_view_get_action_group (NautilusFilesView *view)
+{
+ NautilusFilesViewPrivate *priv;
+
+ g_assert (NAUTILUS_IS_FILES_VIEW (view));
+
+ priv = nautilus_files_view_get_instance_private (view);
+
+ return priv->view_action_group;
+}
+
+static void
+real_update_actions_state (NautilusFilesView *view)
+{
+ NautilusFilesViewPrivate *priv;
+ g_autolist (NautilusFile) selection = NULL;
+ GList *l;
+ gint selection_count;
+ gboolean zoom_level_is_default;
+ gboolean selection_contains_home_dir;
+ gboolean selection_contains_recent;
+ gboolean selection_contains_search;
+ gboolean selection_contains_starred;
+ gboolean selection_all_in_trash;
+ gboolean selection_is_read_only;
+ 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;
+ g_autofree gchar *current_uri = NULL;
+ gboolean can_star_current_directory;
+ gboolean show_star;
+ gboolean show_unstar;
+ gchar *uri;
+ g_autoptr (GAppInfo) app_info_mailto = NULL;
+
+ priv = nautilus_files_view_get_instance_private (view);
+
+ view_action_group = priv->view_action_group;
+
+ selection = nautilus_view_get_selection (NAUTILUS_VIEW (view));
+ selection_count = g_list_length (selection);
+ selection_contains_home_dir = home_dir_in_selection (selection);
+ selection_contains_recent = showing_recent_directory (view);
+ selection_contains_starred = showing_starred_directory (view);
+ selection_contains_search = nautilus_view_is_searching (NAUTILUS_VIEW (view));
+ selection_is_read_only = selection_count == 1 &&
+ (!nautilus_file_can_write (NAUTILUS_FILE (selection->data)) &&
+ !nautilus_file_has_activation_uri (NAUTILUS_FILE (selection->data)));
+ selection_all_in_trash = all_in_trash (selection);
+ zoom_level_is_default = nautilus_files_view_is_zoom_level_default (view);
+
+ is_read_only = nautilus_files_view_is_read_only (view);
+ is_in_trash = showing_trash_directory (view);
+ can_create_files = nautilus_files_view_supports_creating_files (view);
+ can_delete_files =
+ can_delete_all (selection) &&
+ selection_count != 0 &&
+ !selection_contains_home_dir;
+ can_trash_files =
+ can_trash_all (selection) &&
+ selection_count != 0 &&
+ !selection_contains_home_dir;
+ can_copy_files = selection_count != 0;
+ can_move_files = can_delete_files && !selection_contains_recent &&
+ !selection_contains_starred;
+ can_paste_files_into = (selection_count == 1 &&
+ can_paste_into_file (NAUTILUS_FILE (selection->data)));
+ can_extract_files = selection_count != 0 &&
+ can_extract_all (selection);
+ can_extract_here = nautilus_files_view_supports_extract_here (view);
+ 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),
+ can_create_files && can_delete_files && (selection_count > 1) && !selection_contains_recent
+ && !selection_contains_starred);
+
+ action = g_action_map_lookup_action (G_ACTION_MAP (view_action_group),
+ "rename");
+ if (selection_count > 1)
+ {
+ 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),
+ selection_count == 1 &&
+ 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),
+ 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),
+ 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),
+ 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),
+ selection_count == 1 &&
+ (selection_contains_recent || selection_contains_search ||
+ selection_contains_starred));
+
+ item_opens_in_view = selection_count != 0;
+
+ 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), selection_count != 0);
+
+ /* 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),
+ selection_count > 0);
+
+ 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), 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), 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), 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), 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), 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),
+ 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),
+ 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),
+ 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),
+ 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),
+ selection_contains_recent && selection_count > 0);
+
+ action = g_action_map_lookup_action (G_ACTION_MAP (view_action_group),
+ "cut");
+ g_simple_action_set_enabled (G_SIMPLE_ACTION (action),
+ 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),
+ 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),
+ 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),
+ 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),
+ 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),
+ 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),
+ 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_count != 0);
+ 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);
+
+ /* Drive menu */
+ show_mount = (selection != NULL);
+ show_unmount = (selection != NULL);
+ show_eject = (selection != NULL);
+ show_start = (selection != NULL && selection_count == 1);
+ show_stop = (selection != NULL && selection_count == 1);
+ show_detect_media = (selection != NULL && selection_count == 1);
+ 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),
+ !selection_contains_recent &&
+ !selection_contains_search &&
+ !selection_contains_starred);
+ action = g_action_map_lookup_action (G_ACTION_MAP (view_action_group),
+ "new-folder");
+ g_simple_action_set_enabled (G_SIMPLE_ACTION (action), 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),
+ !is_read_only && !selection_contains_recent &&
+ !selection_contains_starred);
+
+ action = g_action_map_lookup_action (G_ACTION_MAP (view_action_group),
+ "paste-into");
+ g_simple_action_set_enabled (G_SIMPLE_ACTION (action),
+ 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),
+ selection_count == 1 && 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),
+ 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),
+ TRUE);
+ action = g_action_map_lookup_action (G_ACTION_MAP (view_action_group),
+ "current-directory-properties");
+ g_simple_action_set_enabled (G_SIMPLE_ACTION (action),
+ !selection_contains_recent &&
+ !selection_contains_search &&
+ !selection_contains_starred);
+
+ /* 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 (view);
+ nautilus_files_view_get_clipboard_async (view,
+ update_actions_clipboard_contents_received,
+ NULL);
+
+ 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 (view) &&
+ !priv->loading);
+
+ /* Toolbar menu actions */
+ g_action_group_change_action_state (view_action_group,
+ "show-hidden-files",
+ g_variant_new_boolean (priv->show_hidden_files));
+
+ /* Zoom */
+ action = g_action_map_lookup_action (G_ACTION_MAP (view_action_group),
+ "zoom-in");
+ g_simple_action_set_enabled (G_SIMPLE_ACTION (action),
+ nautilus_files_view_can_zoom_in (view));
+ action = g_action_map_lookup_action (G_ACTION_MAP (view_action_group),
+ "zoom-out");
+ g_simple_action_set_enabled (G_SIMPLE_ACTION (action),
+ nautilus_files_view_can_zoom_out (view));
+ action = g_action_map_lookup_action (G_ACTION_MAP (view_action_group),
+ "zoom-standard");
+ g_simple_action_set_enabled (G_SIMPLE_ACTION (action),
+ nautilus_files_view_supports_zooming (view) && !zoom_level_is_default);
+ action = g_action_map_lookup_action (G_ACTION_MAP (view_action_group),
+ "zoom-to-level");
+ g_simple_action_set_enabled (G_SIMPLE_ACTION (action),
+ !nautilus_files_view_is_empty (view));
+
+ current_location = nautilus_file_get_location (nautilus_files_view_get_directory_as_file (view));
+ current_uri = g_file_get_uri (current_location);
+ 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;
+
+ 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;
+ }
+
+ g_free (uri);
+ }
+
+ action = g_action_map_lookup_action (G_ACTION_MAP (view_action_group),
+ "star");
+ g_simple_action_set_enabled (G_SIMPLE_ACTION (action), show_star);
+
+ action = g_action_map_lookup_action (G_ACTION_MAP (view_action_group),
+ "unstar");
+ g_simple_action_set_enabled (G_SIMPLE_ACTION (action), show_unstar && selection_contains_starred);
+}
+
+/* Convenience function to be called when updating menus,
+ * so children can subclass it and it will be called when
+ * they chain up to the parent in update_context_menus
+ * or update_toolbar_menus
+ */
+void
+nautilus_files_view_update_actions_state (NautilusFilesView *view)
+{
+ g_assert (NAUTILUS_IS_FILES_VIEW (view));
+
+ NAUTILUS_FILES_VIEW_CLASS (G_OBJECT_GET_CLASS (view))->update_actions_state (view);
+}
+
+static void
+update_selection_menu (NautilusFilesView *view,
+ GtkBuilder *builder)
+{
+ NautilusFilesViewPrivate *priv = nautilus_files_view_get_instance_private (view);
+ g_autolist (NautilusFile) selection = NULL;
+ GList *l;
+ gint selection_count;
+ gboolean show_app;
+ gboolean show_run;
+ gboolean show_extract;
+ gboolean item_opens_in_view;
+ gchar *item_label;
+ GAppInfo *app;
+ GIcon *app_icon;
+ 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_view_get_selection (NAUTILUS_VIEW (view));
+ selection_count = g_list_length (selection);
+
+ show_mount = (selection != NULL);
+ show_unmount = (selection != NULL);
+ show_eject = (selection != NULL);
+ show_start = (selection != NULL && selection_count == 1);
+ show_stop = (selection != NULL && selection_count == 1);
+ show_detect_media = (selection != NULL && selection_count == 1);
+ start_stop_type = G_DRIVE_START_STOP_TYPE_UNKNOWN;
+ 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 = selection_count != 0;
+ 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;
+ app_icon = NULL;
+ if (show_app)
+ {
+ app = nautilus_mime_get_default_application_for_files (selection);
+ }
+
+ if (app != NULL)
+ {
+ char *escaped_app;
+
+ escaped_app = eel_str_double_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_free (escaped_app);
+ 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 (view) ?
+ 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);
+ }
+
+ /* 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);
+
+ g_free (item_label);
+
+ /* 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 (!priv->scripts_menu_updated)
+ {
+ update_scripts_menu (view, builder);
+ priv->scripts_menu_updated = TRUE;
+ }
+
+ if (priv->scripts_menu != NULL)
+ {
+ show_scripts = TRUE;
+ object = gtk_builder_get_object (builder, "scripts-submenu-section");
+ nautilus_gmenu_set_from_model (G_MENU (object), priv->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);
+}
+
+static void
+update_background_menu (NautilusFilesView *view,
+ GtkBuilder *builder)
+{
+ NautilusFilesViewPrivate *priv = nautilus_files_view_get_instance_private (view);
+ GObject *object;
+ gboolean remove_submenu = TRUE;
+ gint i;
+
+ if (nautilus_files_view_supports_creating_files (view) &&
+ !showing_recent_directory (view) &&
+ !showing_starred_directory (view))
+ {
+ if (!priv->templates_menu_updated)
+ {
+ update_templates_menu (view, builder);
+ priv->templates_menu_updated = TRUE;
+ }
+
+ object = gtk_builder_get_object (builder, "templates-submenu");
+ nautilus_gmenu_set_from_model (G_MENU (object), priv->templates_menu);
+
+ if (priv->templates_menu != NULL)
+ {
+ remove_submenu = FALSE;
+ }
+ }
+ else
+ {
+ /* This is necessary because the pathbar menu relies on it being NULL
+ * to hide the submenu. */
+ nautilus_view_set_templates_menu (NAUTILUS_VIEW (view), NULL);
+
+ /* And this is necessary to regenerate the templates menu when we go
+ * back to a normal folder. */
+ priv->templates_menu_updated = FALSE;
+ }
+
+ i = nautilus_g_menu_model_find_by_string (G_MENU_MODEL (priv->background_menu_model),
+ "nautilus-menu-item",
+ "templates-submenu");
+ nautilus_g_menu_replace_string_in_item (priv->background_menu_model, i,
+ "hidden-when",
+ remove_submenu ? "action-missing" : NULL);
+}
+
+static void
+real_update_context_menus (NautilusFilesView *view)
+{
+ NautilusFilesViewPrivate *priv;
+ g_autoptr (GtkBuilder) builder = NULL;
+ GObject *object;
+
+ priv = nautilus_files_view_get_instance_private (view);
+ builder = gtk_builder_new_from_resource ("/org/gnome/nautilus/ui/nautilus-files-view-context-menus.ui");
+
+ g_clear_object (&priv->background_menu_model);
+ g_clear_object (&priv->selection_menu_model);
+
+ object = gtk_builder_get_object (builder, "background-menu");
+ priv->background_menu_model = g_object_ref (G_MENU (object));
+
+ object = gtk_builder_get_object (builder, "selection-menu");
+ priv->selection_menu_model = g_object_ref (G_MENU (object));
+
+ update_selection_menu (view, builder);
+ update_background_menu (view, builder);
+ update_extensions_menus (view, builder);
+
+ nautilus_files_view_update_actions_state (view);
+}
+
+/* Convenience function to reset the context menus owned by the view and update
+ * them with the current state.
+ * Children can subclass it and add items on the menu after chaining up to the
+ * parent, so menus are already reseted.
+ * 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_context_menus (NautilusFilesView *view)
+{
+ g_assert (NAUTILUS_IS_FILES_VIEW (view));
+
+ NAUTILUS_FILES_VIEW_CLASS (G_OBJECT_GET_CLASS (view))->update_context_menus (view);
+}
+
+static void
+nautilus_files_view_reset_view_menu (NautilusFilesView *view)
+{
+ NautilusFilesViewPrivate *priv = nautilus_files_view_get_instance_private (view);
+ NautilusFile *file;
+ GMenuModel *sort_section = priv->toolbar_menu_sections->sort_section;
+ const gchar *action;
+ gint i;
+
+ file = nautilus_files_view_get_directory_as_file (NAUTILUS_FILES_VIEW (view));
+
+ /* 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 *view)
+{
+ NautilusWindow *window;
+ NautilusFilesViewPrivate *priv;
+
+ g_assert (NAUTILUS_IS_FILES_VIEW (view));
+
+ priv = nautilus_files_view_get_instance_private (view);
+
+ /* Don't update after destroy (#349551),
+ * or if we are not active.
+ */
+ if (priv->in_destruction ||
+ !priv->active)
+ {
+ return;
+ }
+ window = nautilus_files_view_get_window (view);
+ nautilus_window_reset_menus (window);
+
+ nautilus_files_view_update_actions_state (view);
+ nautilus_files_view_reset_view_menu (view);
+}
+
+static GdkRectangle *
+nautilus_files_view_reveal_for_selection_context_menu (NautilusFilesView *view)
+{
+ return NAUTILUS_FILES_VIEW_CLASS (G_OBJECT_GET_CLASS (view))->reveal_for_selection_context_menu (view);
+}
+
+/**
+ * nautilus_files_view_pop_up_selection_context_menu
+ *
+ * Pop up a context menu appropriate to the selected items.
+ * @view: NautilusFilesView of interest.
+ * @event: The event that triggered this context menu.
+ *
+ **/
+void
+nautilus_files_view_pop_up_selection_context_menu (NautilusFilesView *view,
+ gdouble x,
+ gdouble y)
+{
+ NautilusFilesViewPrivate *priv;
+
+ g_assert (NAUTILUS_IS_FILES_VIEW (view));
+
+ priv = nautilus_files_view_get_instance_private (view);
+
+ /* 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 (view);
+
+ /* 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 (&priv->selection_menu, gtk_widget_unparent);
+ priv->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 (priv->selection_menu, "closed",
+ G_CALLBACK (gtk_widget_grab_focus), view,
+ G_CONNECT_SWAPPED);
+ gtk_widget_set_parent (priv->selection_menu, GTK_WIDGET (view));
+ gtk_popover_set_has_arrow (GTK_POPOVER (priv->selection_menu), FALSE);
+ gtk_widget_set_halign (priv->selection_menu, GTK_ALIGN_START);
+
+ gtk_popover_menu_set_menu_model (GTK_POPOVER_MENU (priv->selection_menu),
+ G_MENU_MODEL (priv->selection_menu_model));
+ if (x == -1 && y == -1)
+ {
+ /* If triggered from the keyboard, popup at selection, not pointer */
+ g_autofree GdkRectangle *rectangle = NULL;
+
+ rectangle = nautilus_files_view_reveal_for_selection_context_menu (view);
+ g_return_if_fail (rectangle != NULL);
+ gtk_popover_set_pointing_to (GTK_POPOVER (priv->selection_menu),
+ rectangle);
+ }
+ else
+ {
+ gtk_popover_set_pointing_to (GTK_POPOVER (priv->selection_menu),
+ &(GdkRectangle){x, y, 0, 0});
+ }
+ gtk_popover_popup (GTK_POPOVER (priv->selection_menu));
+}
+
+/**
+ * nautilus_files_view_pop_up_background_context_menu
+ *
+ * Pop up a context menu appropriate to the location in view.
+ * @view: NautilusFilesView of interest.
+ *
+ **/
+void
+nautilus_files_view_pop_up_background_context_menu (NautilusFilesView *view,
+ gdouble x,
+ gdouble y)
+{
+ NautilusFilesViewPrivate *priv;
+
+ g_assert (NAUTILUS_IS_FILES_VIEW (view));
+
+ priv = nautilus_files_view_get_instance_private (view);
+
+ /* 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 (view);
+
+ /* 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 (&priv->background_menu, gtk_widget_unparent);
+ priv->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 (priv->background_menu, "closed",
+ G_CALLBACK (gtk_widget_grab_focus), view,
+ G_CONNECT_SWAPPED);
+ gtk_widget_set_parent (priv->background_menu, GTK_WIDGET (view));
+ gtk_popover_set_has_arrow (GTK_POPOVER (priv->background_menu), FALSE);
+ gtk_widget_set_halign (priv->background_menu, GTK_ALIGN_START);
+
+ gtk_popover_menu_set_menu_model (GTK_POPOVER_MENU (priv->background_menu),
+ G_MENU_MODEL (priv->background_menu_model));
+
+ gtk_popover_set_pointing_to (GTK_POPOVER (priv->background_menu),
+ &(GdkRectangle){x, y, 0, 0});
+ gtk_popover_popup (GTK_POPOVER (priv->background_menu));
+}
+
+static void
+schedule_update_context_menus (NautilusFilesView *view)
+{
+ NautilusFilesViewPrivate *priv;
+
+ g_assert (NAUTILUS_IS_FILES_VIEW (view));
+
+ priv = nautilus_files_view_get_instance_private (view);
+
+ /* Don't schedule updates after destroy (#349551),
+ * or if we are not active.
+ */
+ if (priv->in_destruction ||
+ !priv->active)
+ {
+ return;
+ }
+
+ /* Schedule a menu update with the current update interval */
+ if (priv->update_context_menus_timeout_id == 0)
+ {
+ priv->update_context_menus_timeout_id
+ = g_timeout_add (priv->update_interval, update_context_menus_timeout_callback, view);
+ }
+}
+
+static void
+remove_update_status_idle_callback (NautilusFilesView *view)
+{
+ NautilusFilesViewPrivate *priv;
+
+ priv = nautilus_files_view_get_instance_private (view);
+
+ if (priv->update_status_idle_id != 0)
+ {
+ g_source_remove (priv->update_status_idle_id);
+ priv->update_status_idle_id = 0;
+ }
+}
+
+static gboolean
+update_status_idle_callback (gpointer data)
+{
+ NautilusFilesViewPrivate *priv;
+ NautilusFilesView *view;
+
+ view = NAUTILUS_FILES_VIEW (data);
+ priv = nautilus_files_view_get_instance_private (view);
+ nautilus_files_view_display_selection_info (view);
+ priv->update_status_idle_id = 0;
+ return FALSE;
+}
+
+static void
+schedule_update_status (NautilusFilesView *view)
+{
+ NautilusFilesViewPrivate *priv;
+
+ g_assert (NAUTILUS_IS_FILES_VIEW (view));
+
+ priv = nautilus_files_view_get_instance_private (view);
+
+ /* Make sure we haven't already destroyed it */
+ if (priv->in_destruction)
+ {
+ return;
+ }
+
+ if (priv->loading)
+ {
+ /* Don't update status bar while loading the dir */
+ return;
+ }
+
+ if (priv->update_status_idle_id == 0)
+ {
+ priv->update_status_idle_id =
+ g_idle_add_full (G_PRIORITY_DEFAULT_IDLE - 20,
+ update_status_idle_callback, view, NULL);
+ }
+}
+
+/**
+ * nautilus_files_view_notify_selection_changed:
+ *
+ * Notify this view that the selection has changed. This is normally
+ * called only by subclasses.
+ * @view: NautilusFilesView whose selection has changed.
+ *
+ **/
+void
+nautilus_files_view_notify_selection_changed (NautilusFilesView *view)
+{
+ NautilusFilesViewPrivate *priv;
+ GtkWindow *window;
+ g_autolist (NautilusFile) selection = NULL;
+
+ g_return_if_fail (NAUTILUS_IS_FILES_VIEW (view));
+
+ priv = nautilus_files_view_get_instance_private (view);
+
+ selection = nautilus_view_get_selection (NAUTILUS_VIEW (view));
+ window = nautilus_files_view_get_containing_window (view);
+ DEBUG_FILES (selection, "Selection changed in window %p", window);
+
+ priv->selection_was_removed = FALSE;
+
+ /* Schedule a display of the new selection. */
+ if (priv->display_selection_idle_id == 0)
+ {
+ priv->display_selection_idle_id
+ = g_idle_add (display_selection_info_idle_callback,
+ view);
+ }
+
+ schedule_update_context_menus (view);
+}
+
+static void
+file_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);
+}
+
+/**
+ * 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 *view,
+ NautilusDirectory *directory)
+{
+ NautilusFileAttributes attributes;
+ NautilusFilesViewPrivate *priv;
+
+ g_assert (NAUTILUS_IS_FILES_VIEW (view));
+ g_assert (NAUTILUS_IS_DIRECTORY (directory));
+
+ priv = nautilus_files_view_get_instance_private (view);
+
+ nautilus_profile_start (NULL);
+
+ nautilus_files_view_stop_loading (view);
+ g_signal_emit (view, signals[CLEAR], 0);
+
+ priv->loading = TRUE;
+
+ setup_loading_floating_bar (view);
+
+ /* HACK: Fix for https://gitlab.gnome.org/GNOME/nautilus/-/issues/1452 */
+ {
+ GtkScrolledWindow *content = GTK_SCROLLED_WINDOW (priv->scrolled_window);
+
+ /* If we load a new location while the view is still scrolling due to
+ * kinetic deceleration, we get a sudden jump to the same scrolling
+ * position as the previous location, as well as residual scrolling
+ * movement in the new location.
+ *
+ * This is both undesirable and unexpected from a user POV, so we want
+ * to abort deceleration when switching locations.
+ *
+ * However, gtk_scrolled_window_cancel_deceleration() is private. So,
+ * we make use of an undocumented behavior of ::set_kinetic_scrolling(),
+ * which calls ::cancel_deceleration() when set to FALSE.
+ */
+ gtk_scrolled_window_set_kinetic_scrolling (content, FALSE);
+ gtk_scrolled_window_set_kinetic_scrolling (content, TRUE);
+ }
+
+ /* Update menus when directory is empty, before going to new
+ * location, so they won't have any false lingering knowledge
+ * of old selection.
+ */
+ schedule_update_context_menus (view);
+
+ while (priv->subdirectory_list != NULL)
+ {
+ nautilus_files_view_remove_subdirectory (view,
+ priv->subdirectory_list->data);
+ }
+
+ /* Avoid freeing it and won't be able to ref it */
+ if (priv->model != directory)
+ {
+ nautilus_directory_unref (priv->model);
+ priv->model = nautilus_directory_ref (directory);
+ }
+
+ nautilus_file_unref (priv->directory_as_file);
+ priv->directory_as_file = nautilus_directory_get_corresponding_file (directory);
+
+ g_clear_object (&priv->location);
+ priv->location = nautilus_directory_get_location (directory);
+
+ g_object_notify (G_OBJECT (view), "location");
+ g_object_notify (G_OBJECT (view), "loading");
+ g_object_notify (G_OBJECT (view), "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;
+ priv->metadata_for_directory_as_file_pending = TRUE;
+ priv->metadata_for_files_in_directory_pending = TRUE;
+ nautilus_file_call_when_ready
+ (priv->directory_as_file,
+ attributes,
+ metadata_for_directory_as_file_ready_callback, view);
+ nautilus_directory_call_when_ready
+ (priv->model,
+ attributes,
+ FALSE,
+ metadata_for_files_in_directory_ready_callback, view);
+
+ /* 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_FILESYSTEM_INFO;
+ nautilus_file_monitor_add (priv->directory_as_file,
+ &priv->directory_as_file,
+ attributes);
+
+ priv->file_changed_handler_id = g_signal_connect
+ (priv->directory_as_file, "changed",
+ G_CALLBACK (file_changed_callback), view);
+
+ nautilus_profile_end (NULL);
+}
+
+static void
+finish_loading (NautilusFilesView *view)
+{
+ NautilusFileAttributes attributes;
+ NautilusFilesViewPrivate *priv;
+
+ priv = nautilus_files_view_get_instance_private (view);
+
+ nautilus_profile_start (NULL);
+
+ /* Tell interested parties that we've begun loading this directory now.
+ * Subclasses use this to know that the new metadata is now available.
+ */
+ nautilus_profile_start ("BEGIN_LOADING");
+ g_signal_emit (view, signals[BEGIN_LOADING], 0);
+ nautilus_profile_end ("BEGIN_LOADING");
+
+ nautilus_files_view_check_empty_states (view);
+
+ if (nautilus_directory_are_all_files_seen (priv->model))
+ {
+ /* Unschedule a pending update and schedule a new one with the minimal
+ * update interval. This gives the view a short chance at gathering the
+ * (cached) deep counts.
+ */
+ unschedule_display_of_pending_files (view);
+ schedule_timeout_display_of_pending_files (view, UPDATE_INTERVAL_MIN);
+ }
+
+ /* Start loading. */
+
+ /* Connect handlers to learn about loading progress. */
+ priv->done_loading_handler_id = g_signal_connect (priv->model, "done-loading",
+ G_CALLBACK (done_loading_callback), view);
+ priv->load_error_handler_id = g_signal_connect (priv->model, "load-error",
+ G_CALLBACK (load_error_callback), view);
+
+ /* 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;
+
+ priv->files_added_handler_id = g_signal_connect
+ (priv->model, "files-added",
+ G_CALLBACK (files_added_callback), view);
+ priv->files_changed_handler_id = g_signal_connect
+ (priv->model, "files-changed",
+ G_CALLBACK (files_changed_callback), view);
+
+ nautilus_directory_file_monitor_add (priv->model,
+ &priv->model,
+ priv->show_hidden_files,
+ attributes,
+ files_added_callback, view);
+
+ nautilus_profile_end (NULL);
+}
+
+static void
+finish_loading_if_all_metadata_loaded (NautilusFilesView *view)
+{
+ NautilusFilesViewPrivate *priv;
+
+ priv = nautilus_files_view_get_instance_private (view);
+
+ if (!priv->metadata_for_directory_as_file_pending &&
+ !priv->metadata_for_files_in_directory_pending)
+ {
+ finish_loading (view);
+ }
+}
+
+static void
+metadata_for_directory_as_file_ready_callback (NautilusFile *file,
+ gpointer callback_data)
+{
+ NautilusFilesView *view;
+ NautilusFilesViewPrivate *priv;
+
+ view = callback_data;
+
+ g_assert (NAUTILUS_IS_FILES_VIEW (view));
+ priv = nautilus_files_view_get_instance_private (view);
+ g_assert (priv->directory_as_file == file);
+ g_assert (priv->metadata_for_directory_as_file_pending);
+
+ nautilus_profile_start (NULL);
+
+ priv->metadata_for_directory_as_file_pending = FALSE;
+
+ finish_loading_if_all_metadata_loaded (view);
+ nautilus_profile_end (NULL);
+}
+
+static void
+metadata_for_files_in_directory_ready_callback (NautilusDirectory *directory,
+ GList *files,
+ gpointer callback_data)
+{
+ NautilusFilesView *view;
+ NautilusFilesViewPrivate *priv;
+
+ view = callback_data;
+
+ g_assert (NAUTILUS_IS_FILES_VIEW (view));
+ priv = nautilus_files_view_get_instance_private (view);
+ g_assert (priv->model == directory);
+ g_assert (priv->metadata_for_files_in_directory_pending);
+
+ nautilus_profile_start (NULL);
+
+ priv->metadata_for_files_in_directory_pending = FALSE;
+
+ finish_loading_if_all_metadata_loaded (view);
+ nautilus_profile_end (NULL);
+}
+
+static void
+disconnect_model_handlers (NautilusFilesView *view)
+{
+ NautilusFilesViewPrivate *priv;
+
+ priv = nautilus_files_view_get_instance_private (view);
+
+ if (priv->model == NULL)
+ {
+ return;
+ }
+ g_clear_signal_handler (&priv->files_added_handler_id, priv->model);
+ g_clear_signal_handler (&priv->files_changed_handler_id, priv->model);
+ g_clear_signal_handler (&priv->done_loading_handler_id, priv->model);
+ g_clear_signal_handler (&priv->load_error_handler_id, priv->model);
+ g_clear_signal_handler (&priv->file_changed_handler_id, priv->directory_as_file);
+ nautilus_file_cancel_call_when_ready (priv->directory_as_file,
+ metadata_for_directory_as_file_ready_callback,
+ view);
+ nautilus_directory_cancel_callback (priv->model,
+ metadata_for_files_in_directory_ready_callback,
+ view);
+ nautilus_directory_file_monitor_remove (priv->model,
+ &priv->model);
+ nautilus_file_monitor_remove (priv->directory_as_file,
+ &priv->directory_as_file);
+}
+
+static void
+nautilus_files_view_select_file (NautilusFilesView *view,
+ NautilusFile *file)
+{
+ GList file_list;
+
+ file_list.data = file;
+ file_list.next = NULL;
+ file_list.prev = NULL;
+ nautilus_files_view_call_set_selection (view, &file_list);
+}
+
+/**
+ * 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 *view)
+{
+ NautilusFilesViewPrivate *priv;
+
+ g_return_if_fail (NAUTILUS_IS_FILES_VIEW (view));
+
+ priv = nautilus_files_view_get_instance_private (view);
+
+ unschedule_display_of_pending_files (view);
+ reset_update_interval (view);
+
+ /* Free extra undisplayed files */
+ g_list_free_full (priv->new_added_files, file_and_directory_free);
+ priv->new_added_files = NULL;
+
+ g_list_free_full (priv->new_changed_files, file_and_directory_free);
+ priv->new_changed_files = NULL;
+
+ g_hash_table_remove_all (priv->non_ready_files);
+
+ g_list_free_full (priv->old_added_files, file_and_directory_free);
+ priv->old_added_files = NULL;
+
+ g_list_free_full (priv->old_changed_files, file_and_directory_free);
+ priv->old_changed_files = NULL;
+
+ g_list_free_full (priv->pending_selection, g_object_unref);
+ priv->pending_selection = NULL;
+
+ done_loading (view, FALSE);
+
+ disconnect_model_handlers (view);
+}
+
+gboolean
+nautilus_files_view_is_editable (NautilusFilesView *view)
+{
+ NautilusDirectory *directory;
+
+ directory = nautilus_files_view_get_model (view);
+
+ if (directory != NULL)
+ {
+ return nautilus_directory_is_editable (directory);
+ }
+
+ return TRUE;
+}
+
+static gboolean
+nautilus_files_view_is_read_only (NautilusFilesView *view)
+{
+ NautilusFile *file;
+
+ if (!nautilus_files_view_is_editable (view))
+ {
+ return TRUE;
+ }
+
+ file = nautilus_files_view_get_directory_as_file (view);
+ if (file != NULL)
+ {
+ return !nautilus_file_can_write (file);
+ }
+ return FALSE;
+}
+
+/**
+ * nautilus_files_view_should_show_file
+ *
+ * Returns whether or not this file should be displayed based on
+ * current filtering options.
+ */
+gboolean
+nautilus_files_view_should_show_file (NautilusFilesView *view,
+ NautilusFile *file)
+{
+ NautilusFilesViewPrivate *priv;
+
+ priv = nautilus_files_view_get_instance_private (view);
+
+ return nautilus_file_should_show (file,
+ priv->show_hidden_files);
+}
+
+void
+nautilus_files_view_ignore_hidden_file_preferences (NautilusFilesView *view)
+{
+ NautilusFilesViewPrivate *priv;
+
+ priv = nautilus_files_view_get_instance_private (view);
+
+ g_return_if_fail (priv->model == NULL);
+
+ if (priv->ignore_hidden_file_preferences)
+ {
+ return;
+ }
+
+ priv->show_hidden_files = FALSE;
+ priv->ignore_hidden_file_preferences = TRUE;
+}
+
+char *
+nautilus_files_view_get_uri (NautilusFilesView *view)
+{
+ NautilusFilesViewPrivate *priv;
+
+ g_return_val_if_fail (NAUTILUS_IS_FILES_VIEW (view), NULL);
+
+ priv = nautilus_files_view_get_instance_private (view);
+
+ if (priv->model == NULL)
+ {
+ return NULL;
+ }
+ return nautilus_directory_get_uri (priv->model);
+}
+
+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;
+ }
+ 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);
+ NautilusFilesViewPrivate *priv = nautilus_files_view_get_instance_private (view);
+
+ switch (prop_id)
+ {
+ case PROP_LOADING:
+ {
+ g_value_set_boolean (value, nautilus_view_is_loading (NAUTILUS_VIEW (view)));
+ }
+ break;
+
+ case PROP_SEARCHING:
+ {
+ g_value_set_boolean (value, nautilus_view_is_searching (NAUTILUS_VIEW (view)));
+ }
+ break;
+
+ case PROP_LOCATION:
+ {
+ g_value_set_object (value, nautilus_view_get_location (NAUTILUS_VIEW (view)));
+ }
+ break;
+
+ case PROP_SELECTION:
+ {
+ g_value_set_pointer (value, nautilus_view_get_selection (NAUTILUS_VIEW (view)));
+ }
+ break;
+
+ case PROP_SEARCH_QUERY:
+ {
+ g_value_set_object (value, priv->search_query);
+ }
+ break;
+
+ case PROP_EXTENSIONS_BACKGROUND_MENU:
+ {
+ g_value_set_object (value,
+ real_get_extensions_background_menu (NAUTILUS_VIEW (view)));
+ }
+ break;
+
+ case PROP_TEMPLATES_MENU:
+ {
+ g_value_set_object (value,
+ real_get_templates_menu (NAUTILUS_VIEW (view)));
+ }
+ 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 *directory_view;
+ NautilusFilesViewPrivate *priv;
+ NautilusWindowSlot *slot;
+
+ directory_view = NAUTILUS_FILES_VIEW (object);
+ priv = nautilus_files_view_get_instance_private (directory_view);
+
+ switch (prop_id)
+ {
+ case PROP_WINDOW_SLOT:
+ {
+ g_assert (priv->slot == NULL);
+
+ slot = NAUTILUS_WINDOW_SLOT (g_value_get_object (value));
+ priv->slot = slot;
+
+ g_signal_connect_object (priv->slot,
+ "notify::active", G_CALLBACK (slot_active_changed),
+ directory_view, 0);
+ }
+ break;
+
+ case PROP_SUPPORTS_ZOOMING:
+ {
+ priv->supports_zooming = g_value_get_boolean (value);
+ }
+ break;
+
+ case PROP_LOCATION:
+ {
+ nautilus_view_set_location (NAUTILUS_VIEW (directory_view), g_value_get_object (value));
+ }
+ break;
+
+ case PROP_SEARCH_QUERY:
+ {
+ nautilus_view_set_search_query (NAUTILUS_VIEW (directory_view), g_value_get_object (value));
+ }
+ break;
+
+ case PROP_SELECTION:
+ {
+ nautilus_view_set_selection (NAUTILUS_VIEW (directory_view), g_value_get_pointer (value));
+ }
+ break;
+
+ case PROP_EXTENSIONS_BACKGROUND_MENU:
+ {
+ real_set_extensions_background_menu (NAUTILUS_VIEW (directory_view),
+ g_value_get_object (value));
+ }
+ break;
+
+ case PROP_TEMPLATES_MENU:
+ {
+ real_set_templates_menu (NAUTILUS_VIEW (directory_view),
+ g_value_get_object (value));
+ }
+ break;
+
+ default:
+ {
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+ break;
+ }
+}
+
+/* handle Ctrl+Scroll, which will cause a zoom-in/out */
+static gboolean
+on_scroll (GtkEventControllerScroll *scroll,
+ gdouble dx,
+ gdouble dy,
+ gpointer user_data)
+{
+ NautilusFilesView *directory_view;
+ GdkModifierType state;
+
+ directory_view = NAUTILUS_FILES_VIEW (user_data);
+
+ state = gtk_event_controller_get_current_event_state (GTK_EVENT_CONTROLLER (scroll));
+ if (state & GDK_CONTROL_MASK)
+ {
+ if (dy <= -1)
+ {
+ /* Zoom In */
+ nautilus_files_view_bump_zoom_level (directory_view, 1);
+ return GDK_EVENT_STOP;
+ }
+ else if (dy >= 1)
+ {
+ /* Zoom Out */
+ nautilus_files_view_bump_zoom_level (directory_view, -1);
+ return GDK_EVENT_STOP;
+ }
+ }
+
+ return GDK_EVENT_PROPAGATE;
+}
+
+static void
+on_scroll_begin (GtkEventControllerScroll *scroll,
+ gpointer user_data)
+{
+ GdkModifierType state;
+
+ state = gtk_event_controller_get_current_event_state (GTK_EVENT_CONTROLLER (scroll));
+ if (state & GDK_CONTROL_MASK)
+ {
+ gtk_event_controller_scroll_set_flags (scroll,
+ GTK_EVENT_CONTROLLER_SCROLL_VERTICAL |
+ GTK_EVENT_CONTROLLER_SCROLL_DISCRETE);
+ }
+}
+
+static void
+on_scroll_end (GtkEventControllerScroll *scroll,
+ gpointer user_data)
+{
+ gtk_event_controller_scroll_set_flags (scroll, GTK_EVENT_CONTROLLER_SCROLL_VERTICAL);
+}
+
+static void
+on_parent_changed (GObject *object,
+ GParamSpec *pspec,
+ gpointer user_data)
+{
+ GtkWidget *widget;
+ NautilusWindow *window;
+ NautilusFilesView *view;
+ NautilusFilesViewPrivate *priv;
+ GtkWidget *parent;
+
+ widget = GTK_WIDGET (object);
+ view = NAUTILUS_FILES_VIEW (object);
+ priv = nautilus_files_view_get_instance_private (view);
+
+ parent = gtk_widget_get_parent (widget);
+ window = nautilus_files_view_get_window (view);
+
+ if (parent != NULL)
+ {
+ if (priv->slot == nautilus_window_get_active_slot (window))
+ {
+ priv->active = TRUE;
+ gtk_widget_insert_action_group (GTK_WIDGET (nautilus_files_view_get_window (view)),
+ "view",
+ G_ACTION_GROUP (priv->view_action_group));
+ }
+ }
+ else
+ {
+ remove_update_context_menus_timeout_callback (view);
+ /* 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 (priv->active)
+ {
+ gtk_widget_insert_action_group (GTK_WIDGET (nautilus_files_view_get_window (view)),
+ "view",
+ NULL);
+ }
+ }
+}
+
+static NautilusQuery *
+nautilus_files_view_get_search_query (NautilusView *view)
+{
+ NautilusFilesViewPrivate *priv;
+
+ priv = nautilus_files_view_get_instance_private (NAUTILUS_FILES_VIEW (view));
+
+ return priv->search_query;
+}
+
+static void
+set_search_query_internal (NautilusFilesView *files_view,
+ NautilusQuery *query,
+ NautilusDirectory *base_model)
+{
+ GFile *location;
+ NautilusFilesViewPrivate *priv;
+
+ location = NULL;
+ priv = nautilus_files_view_get_instance_private (files_view);
+
+ g_set_object (&priv->search_query, query);
+ g_object_notify (G_OBJECT (files_view), "search-query");
+
+ if (!nautilus_query_is_empty (query))
+ {
+ if (nautilus_view_is_searching (NAUTILUS_VIEW (files_view)))
+ {
+ /*
+ * Reuse the search directory and reload it.
+ */
+ nautilus_search_directory_set_query (NAUTILUS_SEARCH_DIRECTORY (priv->model), query);
+ /* It's important to use load_directory instead of set_location,
+ * since the location is already correct, however we need
+ * to reload the directory with the new query set. But
+ * set_location has a check for wheter the location is a
+ * search directory, so setting the location to a search
+ * directory when is already serching will enter a loop.
+ */
+ load_directory (files_view, priv->model);
+ }
+ 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_base_model (NAUTILUS_SEARCH_DIRECTORY (directory), base_model);
+ nautilus_search_directory_set_query (NAUTILUS_SEARCH_DIRECTORY (directory), query);
+
+ load_directory (files_view, directory);
+
+ g_object_notify (G_OBJECT (files_view), "searching");
+
+ nautilus_directory_unref (directory);
+ g_free (uri);
+ }
+ }
+ else
+ {
+ if (nautilus_view_is_searching (NAUTILUS_VIEW (files_view)))
+ {
+ location = nautilus_directory_get_location (base_model);
+
+ nautilus_view_set_location (NAUTILUS_VIEW (files_view), location);
+ }
+ }
+ g_clear_object (&location);
+}
+
+static void
+nautilus_files_view_set_search_query (NautilusView *view,
+ NautilusQuery *query)
+{
+ NautilusDirectory *base_model;
+ NautilusFilesView *files_view;
+ NautilusFilesViewPrivate *priv;
+
+ files_view = NAUTILUS_FILES_VIEW (view);
+ priv = nautilus_files_view_get_instance_private (files_view);
+
+ if (nautilus_view_is_searching (view))
+ {
+ base_model = nautilus_search_directory_get_base_model (NAUTILUS_SEARCH_DIRECTORY (priv->model));
+ }
+ else
+ {
+ base_model = priv->model;
+ }
+
+ set_search_query_internal (NAUTILUS_FILES_VIEW (view), query, base_model);
+}
+
+static GFile *
+nautilus_files_view_get_location (NautilusView *view)
+{
+ NautilusFilesViewPrivate *priv;
+ NautilusFilesView *files_view;
+
+ files_view = NAUTILUS_FILES_VIEW (view);
+ priv = nautilus_files_view_get_instance_private (files_view);
+
+ return priv->location;
+}
+
+static gboolean
+nautilus_files_view_is_loading (NautilusView *view)
+{
+ NautilusFilesViewPrivate *priv;
+ NautilusFilesView *files_view;
+
+ files_view = NAUTILUS_FILES_VIEW (view);
+ priv = nautilus_files_view_get_instance_private (files_view);
+
+ return priv->loading;
+}
+
+static void
+nautilus_files_view_iface_init (NautilusViewInterface *iface)
+{
+ iface->get_location = nautilus_files_view_get_location;
+ iface->set_location = nautilus_files_view_set_location;
+ iface->get_selection = nautilus_files_view_get_selection;
+ iface->set_selection = nautilus_files_view_set_selection;
+ iface->get_search_query = nautilus_files_view_get_search_query;
+ iface->set_search_query = nautilus_files_view_set_search_query;
+ iface->get_toolbar_menu_sections = nautilus_files_view_get_toolbar_menu_sections;
+ iface->is_searching = nautilus_files_view_is_searching;
+ iface->is_loading = nautilus_files_view_is_loading;
+ iface->get_view_id = nautilus_files_view_get_view_id;
+ iface->get_templates_menu = nautilus_files_view_get_templates_menu;
+ iface->set_templates_menu = nautilus_files_view_set_templates_menu;
+ iface->get_extensions_background_menu = nautilus_files_view_get_extensions_background_menu;
+ iface->set_extensions_background_menu = nautilus_files_view_set_extensions_background_menu;
+}
+
+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->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->focus = nautilus_files_view_focus;
+ widget_class->grab_focus = nautilus_files_view_grab_focus;
+
+
+ signals[ADD_FILES] =
+ g_signal_new ("add-files",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (NautilusFilesViewClass, add_files),
+ NULL, NULL,
+ g_cclosure_marshal_generic,
+ G_TYPE_NONE, 1, G_TYPE_POINTER);
+ signals[BEGIN_FILE_CHANGES] =
+ g_signal_new ("begin-file-changes",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (NautilusFilesViewClass, begin_file_changes),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+ signals[BEGIN_LOADING] =
+ g_signal_new ("begin-loading",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (NautilusFilesViewClass, begin_loading),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+ signals[CLEAR] =
+ g_signal_new ("clear",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (NautilusFilesViewClass, clear),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+ signals[END_FILE_CHANGES] =
+ g_signal_new ("end-file-changes",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (NautilusFilesViewClass, end_file_changes),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+ signals[END_LOADING] =
+ g_signal_new ("end-loading",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (NautilusFilesViewClass, end_loading),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__BOOLEAN,
+ G_TYPE_NONE, 1, G_TYPE_BOOLEAN);
+ signals[FILE_CHANGED] =
+ g_signal_new ("file-changed",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (NautilusFilesViewClass, file_changed),
+ NULL, NULL,
+ g_cclosure_marshal_generic,
+ G_TYPE_NONE, 2, NAUTILUS_TYPE_FILE, NAUTILUS_TYPE_DIRECTORY);
+ signals[REMOVE_FILE] =
+ g_signal_new ("remove-file",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (NautilusFilesViewClass, remove_file),
+ NULL, NULL,
+ g_cclosure_marshal_generic,
+ G_TYPE_NONE, 2, NAUTILUS_TYPE_FILE, NAUTILUS_TYPE_DIRECTORY);
+ signals[SELECTION_CHANGED] =
+ g_signal_new ("selection-changed",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST,
+ 0,
+ NULL, NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+
+ klass->get_backing_uri = real_get_backing_uri;
+ klass->get_window = nautilus_files_view_get_window;
+ klass->update_context_menus = real_update_context_menus;
+ klass->update_actions_state = real_update_actions_state;
+ klass->check_empty_states = real_check_empty_states;
+
+ g_object_class_install_property (
+ oclass,
+ 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_property (
+ oclass,
+ PROP_SUPPORTS_ZOOMING,
+ g_param_spec_boolean ("supports-zooming",
+ "Supports zooming",
+ "Whether the view supports zooming",
+ TRUE,
+ G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY |
+ G_PARAM_STATIC_STRINGS));
+
+ g_object_class_override_property (oclass, PROP_LOADING, "loading");
+ g_object_class_override_property (oclass, PROP_SEARCHING, "searching");
+ g_object_class_override_property (oclass, PROP_LOCATION, "location");
+ g_object_class_override_property (oclass, PROP_SELECTION, "selection");
+ g_object_class_override_property (oclass, PROP_SEARCH_QUERY, "search-query");
+ g_object_class_override_property (oclass, PROP_EXTENSIONS_BACKGROUND_MENU, "extensions-background-menu");
+ g_object_class_override_property (oclass, PROP_TEMPLATES_MENU, "templates-menu");
+
+ gtk_widget_class_set_template_from_resource (widget_class,
+ "/org/gnome/nautilus/ui/nautilus-files-view.ui");
+
+ gtk_widget_class_bind_template_child_private (widget_class, NautilusFilesView, overlay);
+ gtk_widget_class_bind_template_child_private (widget_class, NautilusFilesView, stack);
+ gtk_widget_class_bind_template_child_private (widget_class, NautilusFilesView, empty_view_page);
+ gtk_widget_class_bind_template_child_private (widget_class, NautilusFilesView, scrolled_window);
+ gtk_widget_class_bind_template_child_private (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);
+ gtk_widget_class_add_binding_action (widget_class, GDK_KEY_Down, GDK_ALT_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);
+}
+
+static void
+nautilus_files_view_init (NautilusFilesView *view)
+{
+ NautilusFilesViewPrivate *priv;
+ GtkBuilder *builder;
+ NautilusDirectory *scripts_directory;
+ NautilusDirectory *templates_directory;
+ GtkEventController *controller;
+ GtkShortcut *shortcut;
+ gchar *templates_uri;
+ GdkClipboard *clipboard;
+ GApplication *app;
+ const gchar *zoom_in_accels[] =
+ {
+ "<control>equal",
+ "<control>plus",
+ "<control>KP_Add",
+ "ZoomIn",
+ NULL
+ };
+ const gchar *zoom_out_accels[] =
+ {
+ "<control>minus",
+ "<control>KP_Subtract",
+ "ZoomOut",
+ NULL
+ };
+ const gchar *zoom_standard_accels[] =
+ {
+ "<control>0",
+ "<control>KP_0",
+ NULL
+ };
+
+ nautilus_profile_start (NULL);
+
+ priv = nautilus_files_view_get_instance_private (view);
+
+ /* Toolbar menu */
+ builder = gtk_builder_new_from_resource ("/org/gnome/nautilus/ui/nautilus-toolbar-view-menu.ui");
+ priv->toolbar_menu_sections = g_new0 (NautilusToolbarMenuSections, 1);
+ priv->toolbar_menu_sections->sort_section = G_MENU_MODEL (g_object_ref (gtk_builder_get_object (builder, "sort_section")));
+
+ g_signal_connect (view,
+ "end-file-changes",
+ G_CALLBACK (on_end_file_changes),
+ view);
+ g_signal_connect (view,
+ "notify::selection",
+ G_CALLBACK (nautilus_files_view_preview_update),
+ view);
+ g_signal_connect (view,
+ "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 (view));
+
+ controller = gtk_event_controller_scroll_new (GTK_EVENT_CONTROLLER_SCROLL_VERTICAL);
+ gtk_widget_add_controller (priv->scrolled_window, controller);
+ gtk_event_controller_set_propagation_phase (controller, GTK_PHASE_CAPTURE);
+ g_signal_connect (controller, "scroll", G_CALLBACK (on_scroll), view);
+ g_signal_connect (controller, "scroll-begin", G_CALLBACK (on_scroll_begin), view);
+ g_signal_connect (controller, "scroll-end", G_CALLBACK (on_scroll_end), view);
+
+ g_signal_connect (priv->floating_bar,
+ "stop",
+ G_CALLBACK (floating_bar_stop_cb),
+ view);
+
+ priv->non_ready_files =
+ g_hash_table_new_full (file_and_directory_hash,
+ file_and_directory_equal,
+ file_and_directory_free,
+ NULL);
+
+ priv->pending_reveal = 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 (view, 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 (view, templates_directory);
+ nautilus_directory_unref (templates_directory);
+ }
+ update_templates_directory (view);
+
+ priv->sort_directories_first =
+ g_settings_get_boolean (gtk_filechooser_preferences, NAUTILUS_PREFERENCES_SORT_DIRECTORIES_FIRST);
+ priv->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), view, 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), view);
+
+ /* 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), view, G_CONNECT_SWAPPED);
+
+ gtk_widget_show (GTK_WIDGET (view));
+
+ g_signal_connect_swapped (nautilus_preferences,
+ "changed::" NAUTILUS_PREFERENCES_CLICK_POLICY,
+ G_CALLBACK (click_policy_changed_callback),
+ view);
+ g_signal_connect_swapped (gtk_filechooser_preferences,
+ "changed::" NAUTILUS_PREFERENCES_SORT_DIRECTORIES_FIRST,
+ G_CALLBACK (sort_directories_first_changed_callback), view);
+ g_signal_connect_swapped (gtk_filechooser_preferences,
+ "changed::" NAUTILUS_PREFERENCES_SHOW_HIDDEN_FILES,
+ G_CALLBACK (show_hidden_files_changed_callback), view);
+ g_signal_connect_swapped (gnome_lockdown_preferences,
+ "changed::" NAUTILUS_PREFERENCES_LOCKDOWN_COMMAND_LINE,
+ G_CALLBACK (schedule_update_context_menus), view);
+
+ priv->in_destruction = FALSE;
+
+ priv->view_action_group = G_ACTION_GROUP (g_simple_action_group_new ());
+ g_action_map_add_action_entries (G_ACTION_MAP (priv->view_action_group),
+ view_entries,
+ G_N_ELEMENTS (view_entries),
+ view);
+ gtk_widget_insert_action_group (GTK_WIDGET (view),
+ "view",
+ G_ACTION_GROUP (priv->view_action_group));
+ app = g_application_get_default ();
+
+ /* 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 whole app 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
+ * global accelerator, unless it really makes sense to have them globally
+ * (e.g. Zoom in/out shortcuts).
+ */
+ nautilus_application_set_accelerators (app, "view.zoom-in", zoom_in_accels);
+ nautilus_application_set_accelerators (app, "view.zoom-out", zoom_out_accels);
+ nautilus_application_set_accelerator (app, "view.show-hidden-files", "<control>h");
+ /* Despite putting copy/cut at the widget scope instead of the global one,
+ * we're putting paste globally 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.
+ */
+ nautilus_application_set_accelerator (app, "view.paste_accel", "<control>v");
+ nautilus_application_set_accelerator (app, "view.new-folder", "<control><shift>n");
+ nautilus_application_set_accelerator (app, "view.select-pattern", "<control>s");
+ nautilus_application_set_accelerators (app, "view.zoom-standard", zoom_standard_accels);
+
+ /* 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 (view), 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);
+
+ priv->starred_cancellable = g_cancellable_new ();
+
+ priv->rename_file_controller = nautilus_rename_file_popover_controller_new (GTK_WIDGET (view));
+
+ nautilus_profile_end (NULL);
+}
+
+NautilusFilesView *
+nautilus_files_view_new (guint id,
+ NautilusWindowSlot *slot)
+{
+ NautilusFilesView *view = NULL;
+
+ switch (id)
+ {
+ case NAUTILUS_VIEW_GRID_ID:
+ {
+ view = NAUTILUS_FILES_VIEW (nautilus_grid_view_new (slot));
+ }
+ break;
+
+ case NAUTILUS_VIEW_LIST_ID:
+ {
+ view = NAUTILUS_FILES_VIEW (nautilus_list_view_new (slot));
+ }
+ break;
+
+ default:
+ {
+ g_critical ("Unknown view type ID: %d. Falling back to list.", id);
+ view = NAUTILUS_FILES_VIEW (nautilus_list_view_new (slot));
+ }
+ }
+
+ if (view == NULL)
+ {
+ g_assert_not_reached ();
+ }
+ else if (g_object_is_floating (view))
+ {
+ g_object_ref_sink (view);
+ }
+
+ return view;
+}
diff --git a/src/nautilus-files-view.h b/src/nautilus-files-view.h
new file mode 100644
index 0000000..6806a2b
--- /dev/null
+++ b/src/nautilus-files-view.h
@@ -0,0 +1,315 @@
+/* nautilus-view.h
+ *
+ * Copyright (C) 1999, 2000 Free Software Foundaton
+ * 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
+ * Darin Adler <darin@bentspoon.com>
+ * John Sullivan <sullivan@eazel.com>
+ * Pavel Cisler <pavel@eazel.com>
+ */
+
+#pragma once
+
+#include <gtk/gtk.h>
+#include <gio/gio.h>
+
+#include "nautilus-directory.h"
+#include "nautilus-file.h"
+
+#include "nautilus-window.h"
+#include "nautilus-view.h"
+#include "nautilus-window-slot.h"
+
+G_BEGIN_DECLS
+
+#define NAUTILUS_TYPE_FILES_VIEW nautilus_files_view_get_type()
+
+G_DECLARE_DERIVABLE_TYPE (NautilusFilesView, nautilus_files_view, NAUTILUS, FILES_VIEW, AdwBin)
+
+struct _NautilusFilesViewClass {
+ AdwBinClass parent_class;
+
+ /* The 'clear' signal is emitted to empty the view of its contents.
+ * It must be replaced by each subclass.
+ */
+ void (* clear) (NautilusFilesView *view);
+
+ /* The 'begin_file_changes' signal is emitted before a set of files
+ * are added to the view. It can be replaced by a subclass to do any
+ * necessary preparation for a set of new files. The default
+ * implementation does nothing.
+ */
+ void (* begin_file_changes) (NautilusFilesView *view);
+
+ /* The 'add_files' signal is emitted to add a set of files to the view.
+ * It must be replaced by each subclass.
+ */
+ void (* add_files) (NautilusFilesView *view,
+ GList *files);
+ void (* remove_file) (NautilusFilesView *view,
+ NautilusFile *file,
+ NautilusDirectory *directory);
+
+ /* The 'file_changed' signal is emitted to signal a change in a file,
+ * including the file being removed.
+ * It must be replaced by each subclass.
+ */
+ void (* file_changed) (NautilusFilesView *view,
+ NautilusFile *file,
+ NautilusDirectory *directory);
+
+ /* The 'end_file_changes' signal is emitted after a set of files
+ * are added to the view. It can be replaced by a subclass to do any
+ * necessary cleanup (typically, cleanup for code in begin_file_changes).
+ * The default implementation does nothing.
+ */
+ void (* end_file_changes) (NautilusFilesView *view);
+
+ /* The 'begin_loading' signal is emitted before any of the contents
+ * of a directory are added to the view. It can be replaced by a
+ * subclass to do any necessary preparation to start dealing with a
+ * new directory. The default implementation does nothing.
+ */
+ void (* begin_loading) (NautilusFilesView *view);
+
+ /* The 'end_loading' signal is emitted after all of the contents
+ * of a directory are added to the view. It can be replaced by a
+ * subclass to do any necessary clean-up. The default implementation
+ * does nothing.
+ *
+ * If all_files_seen is true, the handler may assume that
+ * no load error ocurred, 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.
+ */
+ void (* end_loading) (NautilusFilesView *view,
+ gboolean all_files_seen);
+
+ /* Function pointers that don't have corresponding signals */
+
+ /* get_backing uri is a function pointer for subclasses to
+ * override. Subclasses may replace it with a function that
+ * returns the URI for the location where to create new folders,
+ * files, links and paste the clipboard to.
+ */
+
+ char * (* get_backing_uri) (NautilusFilesView *view);
+
+ /* get_selection is not a signal; it is just a function pointer for
+ * subclasses to replace (override). Subclasses must replace it
+ * with a function that returns a newly-allocated GList of
+ * NautilusFile pointers.
+ */
+ GList * (* get_selection) (NautilusFilesView *view);
+
+ /* get_selection_for_file_transfer is a function pointer for
+ * subclasses to replace (override). Subclasses must replace it
+ * with a function that returns a newly-allocated GList of
+ * NautilusFile pointers. The difference from get_selection is
+ * that any files in the selection that also has a parent folder
+ * in the selection is not included.
+ */
+ GList * (* get_selection_for_file_transfer)(NautilusFilesView *view);
+
+ /* select_all is a function pointer that subclasses must override to
+ * select all of the items in the view */
+ void (* select_all) (NautilusFilesView *view);
+
+ /* select_first is a function pointer that subclasses must override to
+ * select the first item in the view */
+ void (* select_first) (NautilusFilesView *view);
+
+ /* set_selection is a function pointer that subclasses must
+ * override to select the specified items (and unselect all
+ * others). The argument is a list of NautilusFiles. */
+
+ void (* set_selection) (NautilusFilesView *view,
+ GList *selection);
+
+ /* invert_selection is a function pointer that subclasses must
+ * override to invert selection. */
+
+ void (* invert_selection) (NautilusFilesView *view);
+
+ /* bump_zoom_level is a function pointer that subclasses must override
+ * to change the zoom level of an object. */
+ void (* bump_zoom_level) (NautilusFilesView *view,
+ int zoom_increment);
+
+ /*
+ * restore_default_zoom_level: restores the zoom level to 100% (or to
+ * whatever is considered the 'standard' zoom level for the view). */
+ void (* restore_standard_zoom_level) (NautilusFilesView *view);
+
+ /* can_zoom_in is a function pointer that subclasses must override to
+ * return whether the view is at maximum size (furthest-in zoom level) */
+ gboolean (* can_zoom_in) (NautilusFilesView *view);
+
+ /* can_zoom_out is a function pointer that subclasses must override to
+ * return whether the view is at minimum size (furthest-out zoom level) */
+ gboolean (* can_zoom_out) (NautilusFilesView *view);
+
+ gboolean (*is_zoom_level_default) (NautilusFilesView *view);
+
+ /* reveal_selection is a function pointer that subclasses may
+ * override to make sure the selected items are sufficiently
+ * apparent to the user (e.g., scrolled into view). By default,
+ * this does nothing.
+ */
+ void (* reveal_selection) (NautilusFilesView *view);
+
+ /* update_menus is a function pointer that subclasses can override to
+ * update the sensitivity or wording of menu items in the menu bar.
+ * It is called (at least) whenever the selection changes. If overridden,
+ * subclasses must call parent class's function.
+ */
+ void (* update_context_menus) (NautilusFilesView *view);
+
+ void (* update_actions_state) (NautilusFilesView *view);
+
+ /* is_empty is a function pointer that subclasses must
+ * override to report whether the view contains any items.
+ */
+ gboolean (* is_empty) (NautilusFilesView *view);
+
+ /* Preference change callbacks, overridden by icon and list views.
+ * Icon and list views respond by synchronizing to the new preference
+ * values and forcing an update if appropriate.
+ */
+ void (* click_policy_changed) (NautilusFilesView *view);
+ void (* sort_directories_first_changed) (NautilusFilesView *view);
+
+ /* Get the id for this view. Its a guint*/
+ guint (* get_view_id) (NautilusFilesView *view);
+
+ /* Return the uri of the first visible file */
+ char * (* get_first_visible_file) (NautilusFilesView *view);
+ /* Scroll the view so that the file specified by the uri is at the top
+ of the view */
+ void (* scroll_to_file) (NautilusFilesView *view,
+ const char *uri);
+
+ NautilusWindow * (*get_window) (NautilusFilesView *view);
+
+ GdkRectangle * (* compute_rename_popover_pointing_to) (NautilusFilesView *view);
+
+ GdkRectangle * (* reveal_for_selection_context_menu) (NautilusFilesView *view);
+
+ /* Use this to show an optional visual feedback when the directory is empty.
+ * By default it shows a widget overlay on top of the view */
+ void (* check_empty_states) (NautilusFilesView *view);
+
+ void (* preview_selection_event) (NautilusFilesView *view,
+ GtkDirectionType direction);
+};
+
+NautilusFilesView * nautilus_files_view_new (guint id,
+ NautilusWindowSlot *slot);
+
+/* Functions callable from the user interface and elsewhere. */
+NautilusWindowSlot *nautilus_files_view_get_nautilus_window_slot (NautilusFilesView *view);
+char * nautilus_files_view_get_uri (NautilusFilesView *view);
+
+void nautilus_files_view_display_selection_info (NautilusFilesView *view);
+
+/* Wrappers for signal emitters. These are normally called
+ * only by NautilusFilesView itself. They have corresponding signals
+ * that observers might want to connect with.
+ */
+gboolean nautilus_files_view_get_loading (NautilusFilesView *view);
+
+/* Hooks for subclasses to call. These are normally called only by
+ * NautilusFilesView and its subclasses
+ */
+void nautilus_files_view_activate_files (NautilusFilesView *view,
+ GList *files,
+ NautilusOpenFlags flags,
+ gboolean confirm_multiple);
+void nautilus_files_view_activate_file (NautilusFilesView *view,
+ NautilusFile *file,
+ NautilusOpenFlags flags);
+void nautilus_files_view_notify_selection_changed (NautilusFilesView *view);
+NautilusDirectory *nautilus_files_view_get_model (NautilusFilesView *view);
+NautilusFile *nautilus_files_view_get_directory_as_file (NautilusFilesView *view);
+void nautilus_files_view_pop_up_background_context_menu (NautilusFilesView *view,
+ gdouble x,
+ gdouble y);
+void nautilus_files_view_pop_up_selection_context_menu (NautilusFilesView *view,
+ gdouble x,
+ gdouble y);
+gboolean nautilus_files_view_should_show_file (NautilusFilesView *view,
+ NautilusFile *file);
+gboolean nautilus_files_view_should_sort_directories_first (NautilusFilesView *view);
+void nautilus_files_view_ignore_hidden_file_preferences (NautilusFilesView *view);
+
+void nautilus_files_view_add_subdirectory (NautilusFilesView *view,
+ NautilusDirectory *directory);
+void nautilus_files_view_remove_subdirectory (NautilusFilesView *view,
+ NautilusDirectory *directory);
+
+gboolean nautilus_files_view_is_editable (NautilusFilesView *view);
+NautilusWindow * nautilus_files_view_get_window (NautilusFilesView *view);
+
+/* file operations */
+char * nautilus_files_view_get_backing_uri (NautilusFilesView *view);
+void nautilus_files_view_move_copy_items (NautilusFilesView *view,
+ const GList *item_uris,
+ const char *target_uri,
+ int copy_action);
+void nautilus_files_view_new_file_with_initial_contents (NautilusFilesView *view,
+ const char *parent_uri,
+ const char *filename,
+ const char *initial_contents,
+ int length);
+
+/* clipboard reading */
+void nautilus_files_view_get_clipboard_async (NautilusFilesView *self,
+ GAsyncReadyCallback callback,
+ gpointer callback_data);
+NautilusClipboard *nautilus_files_view_get_clipboard_finish (NautilusFilesView *self,
+ GAsyncResult *result,
+ GError **error);
+
+/* selection handling */
+void nautilus_files_view_activate_selection (NautilusFilesView *view);
+void nautilus_files_view_preview_selection_event (NautilusFilesView *view,
+ GtkDirectionType direction);
+void nautilus_files_view_stop_loading (NautilusFilesView *view);
+
+char * nautilus_files_view_get_first_visible_file (NautilusFilesView *view);
+void nautilus_files_view_scroll_to_file (NautilusFilesView *view,
+ const char *uri);
+char * nautilus_files_view_get_title (NautilusFilesView *view);
+gboolean nautilus_files_view_supports_zooming (NautilusFilesView *view);
+void nautilus_files_view_bump_zoom_level (NautilusFilesView *view,
+ int zoom_increment);
+gboolean nautilus_files_view_can_zoom_in (NautilusFilesView *view);
+gboolean nautilus_files_view_can_zoom_out (NautilusFilesView *view);
+
+void nautilus_files_view_update_context_menus (NautilusFilesView *view);
+void nautilus_files_view_update_toolbar_menus (NautilusFilesView *view);
+void nautilus_files_view_update_actions_state (NautilusFilesView *view);
+
+void nautilus_files_view_action_show_hidden_files (NautilusFilesView *view,
+ gboolean show_hidden);
+
+GActionGroup * nautilus_files_view_get_action_group (NautilusFilesView *view);
+GtkWidget* nautilus_files_view_get_content_widget (NautilusFilesView *view);
+
+G_END_DECLS
diff --git a/src/nautilus-floating-bar.c b/src/nautilus-floating-bar.c
new file mode 100644
index 0000000..547651f
--- /dev/null
+++ b/src/nautilus-floating-bar.c
@@ -0,0 +1,549 @@
+/* Nautilus - Floating status bar.
+ *
+ * Copyright (C) 2011 Red Hat Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Cosimo Cecchi <cosimoc@redhat.com>
+ *
+ */
+
+#include <config.h>
+
+#include <string.h>
+
+#include "nautilus-floating-bar.h"
+
+#define HOVER_HIDE_TIMEOUT_INTERVAL 100
+
+struct _NautilusFloatingBar
+{
+ GtkBox parent;
+
+ gchar *primary_label;
+ gchar *details_label;
+
+ GtkWidget *primary_label_widget;
+ GtkWidget *details_label_widget;
+ GtkWidget *spinner;
+ gboolean show_spinner;
+ GtkWidget *stop_button;
+ gboolean show_stop;
+ gboolean is_interactive;
+ guint hover_timeout_id;
+
+ GtkEventController *motion_controller;
+ double pointer_x_in_parent_coordinates;
+ double pointer_y_in_parent_coordinates;
+};
+
+enum
+{
+ PROP_PRIMARY_LABEL = 1,
+ PROP_DETAILS_LABEL,
+ PROP_SHOW_SPINNER,
+ PROP_SHOW_STOP,
+ NUM_PROPERTIES
+};
+
+enum
+{
+ STOP,
+ NUM_SIGNALS
+};
+
+static GParamSpec *properties[NUM_PROPERTIES] = { NULL, };
+static guint signals[NUM_SIGNALS] = { 0, };
+
+G_DEFINE_TYPE (NautilusFloatingBar, nautilus_floating_bar,
+ GTK_TYPE_BOX);
+
+static void
+stop_button_clicked_cb (GtkButton *button,
+ NautilusFloatingBar *self)
+{
+ g_signal_emit (self, signals[STOP], 0);
+}
+
+static void
+nautilus_floating_bar_finalize (GObject *obj)
+{
+ NautilusFloatingBar *self = NAUTILUS_FLOATING_BAR (obj);
+
+ nautilus_floating_bar_remove_hover_timeout (self);
+ g_free (self->primary_label);
+ g_free (self->details_label);
+ g_clear_object (&self->motion_controller);
+
+ G_OBJECT_CLASS (nautilus_floating_bar_parent_class)->finalize (obj);
+}
+
+static void
+nautilus_floating_bar_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ NautilusFloatingBar *self = NAUTILUS_FLOATING_BAR (object);
+
+ switch (property_id)
+ {
+ case PROP_PRIMARY_LABEL:
+ {
+ g_value_set_string (value, self->primary_label);
+ }
+ break;
+
+ case PROP_DETAILS_LABEL:
+ {
+ g_value_set_string (value, self->details_label);
+ }
+ break;
+
+ case PROP_SHOW_SPINNER:
+ {
+ g_value_set_boolean (value, self->show_spinner);
+ }
+ break;
+
+ case PROP_SHOW_STOP:
+ {
+ g_value_set_boolean (value, self->show_stop);
+ }
+ break;
+
+ default:
+ {
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ }
+ break;
+ }
+}
+
+static void
+nautilus_floating_bar_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ NautilusFloatingBar *self = NAUTILUS_FLOATING_BAR (object);
+
+ switch (property_id)
+ {
+ case PROP_PRIMARY_LABEL:
+ {
+ nautilus_floating_bar_set_primary_label (self, g_value_get_string (value));
+ }
+ break;
+
+ case PROP_DETAILS_LABEL:
+ {
+ nautilus_floating_bar_set_details_label (self, g_value_get_string (value));
+ }
+ break;
+
+ case PROP_SHOW_SPINNER:
+ {
+ nautilus_floating_bar_set_show_spinner (self, g_value_get_boolean (value));
+ }
+ break;
+
+ case PROP_SHOW_STOP:
+ {
+ nautilus_floating_bar_set_show_stop (self, g_value_get_boolean (value));
+ }
+ break;
+
+ default:
+ {
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ }
+ break;
+ }
+}
+
+static void
+update_labels (NautilusFloatingBar *self)
+{
+ gboolean primary_visible, details_visible;
+
+ primary_visible = (self->primary_label != NULL) &&
+ (strlen (self->primary_label) > 0);
+ details_visible = (self->details_label != NULL) &&
+ (strlen (self->details_label) > 0);
+
+ gtk_label_set_text (GTK_LABEL (self->primary_label_widget),
+ self->primary_label);
+ gtk_widget_set_visible (self->primary_label_widget, primary_visible);
+
+ gtk_label_set_text (GTK_LABEL (self->details_label_widget),
+ self->details_label);
+ gtk_widget_set_visible (self->details_label_widget, details_visible);
+}
+
+void
+nautilus_floating_bar_remove_hover_timeout (NautilusFloatingBar *self)
+{
+ if (self->hover_timeout_id != 0)
+ {
+ g_source_remove (self->hover_timeout_id);
+ self->hover_timeout_id = 0;
+ }
+}
+
+typedef struct
+{
+ NautilusFloatingBar *floating_bar;
+ gint x_down_limit;
+ gint x_upper_limit;
+ gint y_down_limit;
+ gint y_upper_limit;
+} CheckPointerData;
+
+static void
+check_pointer_data_free (gpointer data)
+{
+ g_slice_free (CheckPointerData, data);
+}
+
+static gboolean
+check_pointer_timeout (gpointer user_data)
+{
+ CheckPointerData *data = user_data;
+ NautilusFloatingBar *self = data->floating_bar;
+ double pointer_x = self->pointer_x_in_parent_coordinates;
+ double pointer_y = self->pointer_y_in_parent_coordinates;
+
+ if (pointer_x == -1 ||
+ pointer_y == -1 ||
+ pointer_x < data->x_down_limit ||
+ pointer_x > data->x_upper_limit ||
+ pointer_y < data->y_down_limit ||
+ pointer_y > data->y_upper_limit)
+ {
+ gtk_widget_show (GTK_WIDGET (self));
+ self->hover_timeout_id = 0;
+
+ return G_SOURCE_REMOVE;
+ }
+ else
+ {
+ gtk_widget_hide (GTK_WIDGET (self));
+ }
+
+ return G_SOURCE_CONTINUE;
+}
+
+static void
+on_event_controller_motion_motion (GtkEventControllerMotion *controller,
+ double x,
+ double y,
+ gpointer user_data)
+{
+ NautilusFloatingBar *self = NAUTILUS_FLOATING_BAR (user_data);
+ GtkWidget *parent;
+ CheckPointerData *data;
+ gdouble x_pos;
+ gdouble y_pos;
+
+ self->pointer_x_in_parent_coordinates = x;
+ self->pointer_y_in_parent_coordinates = y;
+
+ if (self->is_interactive || !gtk_widget_is_visible (GTK_WIDGET (self)))
+ {
+ return;
+ }
+
+ parent = gtk_event_controller_get_widget (GTK_EVENT_CONTROLLER (controller));
+ gtk_widget_translate_coordinates (GTK_WIDGET (self), parent, 0, 0, &x_pos, &y_pos);
+
+ if (x < x_pos || y < y_pos)
+ {
+ return;
+ }
+
+ if (self->hover_timeout_id != 0)
+ {
+ return;
+ }
+
+ data = g_slice_new (CheckPointerData);
+ data->floating_bar = self;
+ data->x_down_limit = x_pos;
+ data->x_upper_limit = x_pos + gtk_widget_get_allocated_width (GTK_WIDGET (self));
+ data->y_down_limit = y_pos;
+ data->y_upper_limit = y_pos + gtk_widget_get_allocated_height (GTK_WIDGET (self));
+
+ self->hover_timeout_id = g_timeout_add_full (G_PRIORITY_DEFAULT, HOVER_HIDE_TIMEOUT_INTERVAL,
+ check_pointer_timeout, data,
+ check_pointer_data_free);
+
+ g_source_set_name_by_id (self->hover_timeout_id, "[nautilus-floating-bar] on_event_controller_motion_motion");
+}
+
+static void
+on_event_controller_motion_leave (GtkEventControllerMotion *controller,
+ gpointer user_data)
+{
+ NautilusFloatingBar *self = NAUTILUS_FLOATING_BAR (user_data);
+
+ self->pointer_x_in_parent_coordinates = -1;
+ self->pointer_y_in_parent_coordinates = -1;
+}
+
+static void
+on_parent_changed (GObject *object,
+ GParamSpec *pspec,
+ gpointer user_data)
+{
+ NautilusFloatingBar *self = NAUTILUS_FLOATING_BAR (object);
+ GtkWidget *parent;
+
+ parent = gtk_widget_get_parent (GTK_WIDGET (object));
+
+ if (self->motion_controller != NULL)
+ {
+ GtkWidget *old_parent;
+
+ old_parent = gtk_event_controller_get_widget (self->motion_controller);
+ g_warn_if_fail (old_parent != NULL);
+ if (old_parent != NULL)
+ {
+ gtk_widget_remove_controller (old_parent, self->motion_controller);
+ }
+
+ g_object_unref (self->motion_controller);
+ self->motion_controller = NULL;
+ }
+
+ if (parent != NULL)
+ {
+ self->motion_controller = g_object_ref (gtk_event_controller_motion_new ());
+ gtk_widget_add_controller (parent, self->motion_controller);
+
+ gtk_event_controller_set_propagation_phase (self->motion_controller,
+ GTK_PHASE_CAPTURE);
+ g_signal_connect (self->motion_controller, "leave",
+ G_CALLBACK (on_event_controller_motion_leave), self);
+ g_signal_connect (self->motion_controller, "motion",
+ G_CALLBACK (on_event_controller_motion_motion), self);
+ }
+}
+
+static void
+nautilus_floating_bar_constructed (GObject *obj)
+{
+ NautilusFloatingBar *self = NAUTILUS_FLOATING_BAR (obj);
+ GtkWidget *w, *box, *labels_box;
+ GtkStyleContext *context;
+
+ G_OBJECT_CLASS (nautilus_floating_bar_parent_class)->constructed (obj);
+
+ box = GTK_WIDGET (obj);
+
+ w = gtk_spinner_new ();
+ gtk_box_append (GTK_BOX (box), w);
+ gtk_widget_set_visible (w, self->show_spinner);
+ /* As a workaround for https://gitlab.gnome.org/GNOME/gtk/-/issues/1025,
+ * ensure the spinner animates if and only if it's visible, to reduce CPU
+ * usage. */
+ g_object_bind_property (obj, "show-spinner",
+ w, "spinning",
+ G_BINDING_SYNC_CREATE);
+ self->spinner = w;
+
+ gtk_widget_set_size_request (w, 16, 16);
+ gtk_widget_set_margin_start (w, 8);
+
+ labels_box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 6);
+ gtk_box_append (GTK_BOX (box), labels_box);
+ g_object_set (labels_box,
+ "hexpand", TRUE,
+ "margin-top", 2,
+ "margin-bottom", 2,
+ "margin-start", 12,
+ "margin-end", 12,
+ NULL);
+ gtk_widget_show (labels_box);
+
+ w = gtk_label_new (NULL);
+ gtk_label_set_ellipsize (GTK_LABEL (w), PANGO_ELLIPSIZE_MIDDLE);
+ gtk_label_set_single_line_mode (GTK_LABEL (w), TRUE);
+ gtk_box_append (GTK_BOX (labels_box), w);
+ self->primary_label_widget = w;
+ gtk_widget_show (w);
+
+ w = gtk_label_new (NULL);
+ gtk_label_set_single_line_mode (GTK_LABEL (w), TRUE);
+ gtk_box_append (GTK_BOX (labels_box), w);
+ self->details_label_widget = w;
+ gtk_widget_show (w);
+
+ w = gtk_button_new_from_icon_name ("process-stop-symbolic");
+ context = gtk_widget_get_style_context (w);
+ gtk_style_context_add_class (context, "circular");
+ gtk_style_context_add_class (context, "flat");
+ gtk_widget_set_valign (w, GTK_ALIGN_CENTER);
+ gtk_box_append (GTK_BOX (self), w);
+ self->stop_button = w;
+ gtk_widget_set_visible (w, FALSE);
+
+ g_signal_connect (self->stop_button, "clicked",
+ G_CALLBACK (stop_button_clicked_cb), self);
+}
+
+static void
+nautilus_floating_bar_init (NautilusFloatingBar *self)
+{
+ GtkStyleContext *context;
+
+ context = gtk_widget_get_style_context (GTK_WIDGET (self));
+ gtk_style_context_add_class (context, "floating-bar");
+
+ self->motion_controller = NULL;
+ self->pointer_x_in_parent_coordinates = -1;
+ self->pointer_y_in_parent_coordinates = -1;
+
+ g_signal_connect (self,
+ "notify::parent",
+ G_CALLBACK (on_parent_changed),
+ NULL);
+}
+
+static void
+nautilus_floating_bar_class_init (NautilusFloatingBarClass *klass)
+{
+ GObjectClass *oclass = G_OBJECT_CLASS (klass);
+
+ oclass->constructed = nautilus_floating_bar_constructed;
+ oclass->set_property = nautilus_floating_bar_set_property;
+ oclass->get_property = nautilus_floating_bar_get_property;
+ oclass->finalize = nautilus_floating_bar_finalize;
+
+ properties[PROP_PRIMARY_LABEL] =
+ g_param_spec_string ("primary-label",
+ "Bar's primary label",
+ "Primary label displayed by the bar",
+ NULL,
+ G_PARAM_WRITABLE | G_PARAM_STATIC_STRINGS);
+ properties[PROP_DETAILS_LABEL] =
+ g_param_spec_string ("details-label",
+ "Bar's details label",
+ "Details label displayed by the bar",
+ NULL,
+ G_PARAM_WRITABLE | G_PARAM_STATIC_STRINGS);
+ properties[PROP_SHOW_SPINNER] =
+ g_param_spec_boolean ("show-spinner",
+ "Show spinner",
+ "Whether a spinner should be shown in the floating bar",
+ FALSE,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+ properties[PROP_SHOW_STOP] =
+ g_param_spec_boolean ("show-stop",
+ "Show stop button",
+ "Whether a stop button should be shown in the floating bar",
+ FALSE,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+
+ signals[STOP] = g_signal_new ("stop",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST,
+ 0, NULL, NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+
+ g_object_class_install_properties (oclass, NUM_PROPERTIES, properties);
+}
+
+void
+nautilus_floating_bar_set_primary_label (NautilusFloatingBar *self,
+ const gchar *label)
+{
+ if (g_strcmp0 (self->primary_label, label) != 0)
+ {
+ g_free (self->primary_label);
+ self->primary_label = g_strdup (label);
+
+ g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_PRIMARY_LABEL]);
+
+ update_labels (self);
+ }
+}
+
+void
+nautilus_floating_bar_set_details_label (NautilusFloatingBar *self,
+ const gchar *label)
+{
+ if (g_strcmp0 (self->details_label, label) != 0)
+ {
+ g_free (self->details_label);
+ self->details_label = g_strdup (label);
+
+ g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_DETAILS_LABEL]);
+
+ update_labels (self);
+ }
+}
+
+void
+nautilus_floating_bar_set_labels (NautilusFloatingBar *self,
+ const gchar *primary_label,
+ const gchar *details_label)
+{
+ nautilus_floating_bar_set_primary_label (self, primary_label);
+ nautilus_floating_bar_set_details_label (self, details_label);
+}
+
+void
+nautilus_floating_bar_set_show_spinner (NautilusFloatingBar *self,
+ gboolean show_spinner)
+{
+ if (self->show_spinner != show_spinner)
+ {
+ self->show_spinner = show_spinner;
+ gtk_widget_set_visible (self->spinner,
+ show_spinner);
+
+ g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_SHOW_SPINNER]);
+ }
+}
+
+void
+nautilus_floating_bar_set_show_stop (NautilusFloatingBar *self,
+ gboolean show_stop)
+{
+ if (self->show_stop != show_stop)
+ {
+ self->show_stop = show_stop;
+ gtk_widget_set_visible (self->stop_button,
+ show_stop);
+ self->is_interactive = show_stop;
+
+ g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_SHOW_STOP]);
+ }
+}
+
+GtkWidget *
+nautilus_floating_bar_new (const gchar *primary_label,
+ const gchar *details_label,
+ gboolean show_spinner)
+{
+ return g_object_new (NAUTILUS_TYPE_FLOATING_BAR,
+ "primary-label", primary_label,
+ "details-label", details_label,
+ "show-spinner", show_spinner,
+ "orientation", GTK_ORIENTATION_HORIZONTAL,
+ "spacing", 8,
+ NULL);
+}
diff --git a/src/nautilus-floating-bar.h b/src/nautilus-floating-bar.h
new file mode 100644
index 0000000..61256da
--- /dev/null
+++ b/src/nautilus-floating-bar.h
@@ -0,0 +1,48 @@
+
+/* Nautilus - Floating status bar.
+ *
+ * Copyright (C) 2011 Red Hat Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Cosimo Cecchi <cosimoc@redhat.com>
+ *
+ */
+
+#pragma once
+
+#include <gtk/gtk.h>
+
+#define NAUTILUS_FLOATING_BAR_ACTION_ID_STOP 1
+
+#define NAUTILUS_TYPE_FLOATING_BAR nautilus_floating_bar_get_type()
+G_DECLARE_FINAL_TYPE (NautilusFloatingBar, nautilus_floating_bar, NAUTILUS, FLOATING_BAR, GtkBox)
+
+GtkWidget * nautilus_floating_bar_new (const gchar *primary_label,
+ const gchar *details_label,
+ gboolean show_spinner);
+
+void nautilus_floating_bar_set_primary_label (NautilusFloatingBar *self,
+ const gchar *label);
+void nautilus_floating_bar_set_details_label (NautilusFloatingBar *self,
+ const gchar *label);
+void nautilus_floating_bar_set_labels (NautilusFloatingBar *self,
+ const gchar *primary,
+ const gchar *detail);
+void nautilus_floating_bar_set_show_spinner (NautilusFloatingBar *self,
+ gboolean show_spinner);
+void nautilus_floating_bar_set_show_stop (NautilusFloatingBar *self,
+ gboolean show_spinner);
+
+void nautilus_floating_bar_remove_hover_timeout (NautilusFloatingBar *self);
diff --git a/src/nautilus-freedesktop-dbus.c b/src/nautilus-freedesktop-dbus.c
new file mode 100644
index 0000000..2eaebcc
--- /dev/null
+++ b/src/nautilus-freedesktop-dbus.c
@@ -0,0 +1,329 @@
+/*
+ * nautilus-freedesktop-dbus: Implementation for the org.freedesktop DBus file-management interfaces
+ *
+ * Nautilus is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * Nautilus is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Akshay Gupta <kitallis@gmail.com>
+ * Federico Mena Quintero <federico@gnome.org>
+ */
+
+#include "nautilus-freedesktop-dbus.h"
+
+/* We share the same debug domain as nautilus-dbus-manager */
+#define DEBUG_FLAG NAUTILUS_DEBUG_DBUS
+#include "nautilus-debug.h"
+
+#include "nautilus-application.h"
+#include "nautilus-file.h"
+#include "nautilus-freedesktop-generated.h"
+#include "nautilus-properties-window.h"
+
+#include <gio/gio.h>
+
+struct _NautilusFreedesktopDBus
+{
+ GObject parent;
+
+ /* Id from g_dbus_own_name() */
+ guint owner_id;
+
+ /* Our DBus implementation skeleton */
+ NautilusFreedesktopFileManager1 *skeleton;
+
+ GStrv pending_open_locations;
+ GVariant *pending_open_windows_with_locations;
+
+ gboolean name_lost;
+};
+
+G_DEFINE_TYPE (NautilusFreedesktopDBus, nautilus_freedesktop_dbus, G_TYPE_OBJECT);
+
+static gboolean
+skeleton_handle_show_items_cb (NautilusFreedesktopFileManager1 *object,
+ GDBusMethodInvocation *invocation,
+ const gchar * const *uris,
+ const gchar *startup_id,
+ gpointer data)
+{
+ NautilusApplication *application;
+ int i;
+
+ application = NAUTILUS_APPLICATION (g_application_get_default ());
+
+ for (i = 0; uris[i] != NULL; i++)
+ {
+ g_autoptr (GFile) file = NULL;
+ g_autoptr (GFile) parent = NULL;
+
+ file = g_file_new_for_uri (uris[i]);
+ parent = g_file_get_parent (file);
+
+ if (parent != NULL)
+ {
+ nautilus_application_open_location (application, parent, file, startup_id);
+ }
+ else
+ {
+ nautilus_application_open_location (application, file, NULL, startup_id);
+ }
+ }
+
+ nautilus_freedesktop_file_manager1_complete_show_items (object, invocation);
+ return TRUE;
+}
+
+static gboolean
+skeleton_handle_show_folders_cb (NautilusFreedesktopFileManager1 *object,
+ GDBusMethodInvocation *invocation,
+ const gchar * const *uris,
+ const gchar *startup_id,
+ gpointer data)
+{
+ NautilusApplication *application;
+ int i;
+
+ application = NAUTILUS_APPLICATION (g_application_get_default ());
+
+ for (i = 0; uris[i] != NULL; i++)
+ {
+ g_autoptr (GFile) file = NULL;
+
+ file = g_file_new_for_uri (uris[i]);
+
+ nautilus_application_open_location (application, file, NULL, startup_id);
+ }
+
+ nautilus_freedesktop_file_manager1_complete_show_folders (object, invocation);
+ return TRUE;
+}
+
+static void
+properties_window_on_finished (gpointer user_data)
+{
+ g_application_release (g_application_get_default ());
+}
+
+static gboolean
+skeleton_handle_show_item_properties_cb (NautilusFreedesktopFileManager1 *object,
+ GDBusMethodInvocation *invocation,
+ const gchar * const *uris,
+ const gchar *startup_id,
+ gpointer data)
+{
+ GList *files;
+ int i;
+
+ files = NULL;
+
+ for (i = 0; uris[i] != NULL; i++)
+ {
+ files = g_list_prepend (files, nautilus_file_get_by_uri (uris[i]));
+ }
+
+ files = g_list_reverse (files);
+
+ g_application_hold (g_application_get_default ());
+ nautilus_properties_window_present (files, NULL, startup_id,
+ properties_window_on_finished, NULL);
+
+ nautilus_file_list_free (files);
+
+ nautilus_freedesktop_file_manager1_complete_show_item_properties (object, invocation);
+ return TRUE;
+}
+
+static void
+bus_acquired_cb (GDBusConnection *conn,
+ const gchar *name,
+ gpointer user_data)
+{
+ NautilusFreedesktopDBus *fdb = user_data;
+
+ DEBUG ("Bus acquired at %s", name);
+
+ fdb->skeleton = nautilus_freedesktop_file_manager1_skeleton_new ();
+
+ g_signal_connect (fdb->skeleton, "handle-show-items",
+ G_CALLBACK (skeleton_handle_show_items_cb), fdb);
+ g_signal_connect (fdb->skeleton, "handle-show-folders",
+ G_CALLBACK (skeleton_handle_show_folders_cb), fdb);
+ g_signal_connect (fdb->skeleton, "handle-show-item-properties",
+ G_CALLBACK (skeleton_handle_show_item_properties_cb), fdb);
+
+ g_dbus_interface_skeleton_export (G_DBUS_INTERFACE_SKELETON (fdb->skeleton), conn, NAUTILUS_FDO_DBUS_PATH, NULL);
+
+ if (G_UNLIKELY (fdb->pending_open_locations != NULL))
+ {
+ g_auto (GStrv) locations = NULL;
+
+ locations = g_steal_pointer (&fdb->pending_open_locations);
+
+ nautilus_freedesktop_dbus_set_open_locations (fdb, (const gchar **) locations);
+ }
+
+ if (G_UNLIKELY (fdb->pending_open_windows_with_locations != NULL))
+ {
+ g_autoptr (GVariant) locations = NULL;
+
+ locations = g_steal_pointer (&fdb->pending_open_windows_with_locations);
+
+ nautilus_freedesktop_dbus_set_open_windows_with_locations (fdb, locations);
+ }
+}
+
+static void
+name_acquired_cb (GDBusConnection *connection,
+ const gchar *name,
+ gpointer user_data)
+{
+ DEBUG ("Acquired the name %s on the session message bus\n", name);
+}
+
+static void
+name_lost_cb (GDBusConnection *connection,
+ const gchar *name,
+ gpointer user_data)
+{
+ NautilusFreedesktopDBus *fdb;
+
+ DEBUG ("Lost (or failed to acquire) the name %s on the session message bus\n", name);
+
+ fdb = NAUTILUS_FREEDESKTOP_DBUS (user_data);
+
+ fdb->name_lost = TRUE;
+}
+
+static void
+nautilus_freedesktop_dbus_dispose (GObject *object)
+{
+ NautilusFreedesktopDBus *fdb = (NautilusFreedesktopDBus *) object;
+
+ if (fdb->owner_id != 0)
+ {
+ g_bus_unown_name (fdb->owner_id);
+ fdb->owner_id = 0;
+ }
+
+ if (fdb->skeleton != NULL)
+ {
+ g_dbus_interface_skeleton_unexport (G_DBUS_INTERFACE_SKELETON (fdb->skeleton));
+ g_object_unref (fdb->skeleton);
+ fdb->skeleton = NULL;
+ }
+
+ G_OBJECT_CLASS (nautilus_freedesktop_dbus_parent_class)->dispose (object);
+}
+
+static void
+nautilus_freedesktop_dbus_finalize (GObject *object)
+{
+ NautilusFreedesktopDBus *fdb;
+
+ fdb = NAUTILUS_FREEDESKTOP_DBUS (object);
+
+ g_clear_pointer (&fdb->pending_open_locations, g_strfreev);
+ g_clear_pointer (&fdb->pending_open_windows_with_locations, g_variant_unref);
+
+ G_OBJECT_CLASS (nautilus_freedesktop_dbus_parent_class)->finalize (object);
+}
+
+static void
+nautilus_freedesktop_dbus_class_init (NautilusFreedesktopDBusClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->dispose = nautilus_freedesktop_dbus_dispose;
+ object_class->finalize = nautilus_freedesktop_dbus_finalize;
+}
+
+static void
+nautilus_freedesktop_dbus_init (NautilusFreedesktopDBus *fdb)
+{
+ fdb->owner_id = g_bus_own_name (G_BUS_TYPE_SESSION,
+ NAUTILUS_FDO_DBUS_NAME,
+ G_BUS_NAME_OWNER_FLAGS_NONE,
+ bus_acquired_cb,
+ name_acquired_cb,
+ name_lost_cb,
+ fdb,
+ NULL);
+ fdb->skeleton = NULL;
+ fdb->pending_open_locations = NULL;
+ fdb->pending_open_windows_with_locations = NULL;
+ fdb->name_lost = FALSE;
+}
+
+void
+nautilus_freedesktop_dbus_set_open_locations (NautilusFreedesktopDBus *fdb,
+ const gchar **locations)
+{
+ g_return_if_fail (NAUTILUS_IS_FREEDESKTOP_DBUS (fdb));
+
+ if (G_UNLIKELY (fdb->skeleton == NULL))
+ {
+ if (G_LIKELY (fdb->name_lost))
+ {
+ return;
+ }
+
+ g_clear_pointer (&fdb->pending_open_locations, g_strfreev);
+
+ fdb->pending_open_locations = g_strdupv ((gchar **) locations);
+ }
+ else
+ {
+ nautilus_freedesktop_file_manager1_set_open_locations (fdb->skeleton, locations);
+ }
+}
+
+/**
+ * nautilus_freedesktop_dbus_set_open_windows_with_locations:
+ * fdb: The skeleton for the dbus interface
+ * locations: Mapping of windows to locations open in each window
+ *
+ * This allows the application to publish the locations that are open in each window.
+ * It is used by shell extensions like dash-to-dock/ubuntu-dock to match special dock
+ * icons to the windows where the icon's location is open. For example, the Trash or
+ * a removable device.
+ */
+void
+nautilus_freedesktop_dbus_set_open_windows_with_locations (NautilusFreedesktopDBus *fdb,
+ GVariant *locations)
+{
+ g_return_if_fail (NAUTILUS_IS_FREEDESKTOP_DBUS (fdb));
+
+ if (G_UNLIKELY (fdb->skeleton == NULL))
+ {
+ if (G_LIKELY (fdb->name_lost))
+ {
+ return;
+ }
+
+ g_clear_pointer (&fdb->pending_open_windows_with_locations, g_variant_unref);
+
+ fdb->pending_open_windows_with_locations = g_variant_ref (locations);
+ }
+ else
+ {
+ nautilus_freedesktop_file_manager1_set_open_windows_with_locations (fdb->skeleton,
+ locations);
+ }
+}
+
+/* Tries to own the org.freedesktop.FileManager1 service name */
+NautilusFreedesktopDBus *
+nautilus_freedesktop_dbus_new (void)
+{
+ return g_object_new (NAUTILUS_TYPE_FREEDESKTOP_DBUS, NULL);
+}
diff --git a/src/nautilus-freedesktop-dbus.h b/src/nautilus-freedesktop-dbus.h
new file mode 100644
index 0000000..416900e
--- /dev/null
+++ b/src/nautilus-freedesktop-dbus.h
@@ -0,0 +1,37 @@
+/*
+ * nautilus-freedesktop-dbus: Implementation for the org.freedesktop DBus file-management interfaces
+ *
+ * Nautilus is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * Nautilus is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Akshay Gupta <kitallis@gmail.com>
+ * Federico Mena Quintero <federico@gnome.org>
+ */
+
+#pragma once
+
+#include <glib-object.h>
+
+#define NAUTILUS_FDO_DBUS_IFACE "org.freedesktop.FileManager1"
+#define NAUTILUS_FDO_DBUS_NAME "org.freedesktop.FileManager1"
+#define NAUTILUS_FDO_DBUS_PATH "/org/freedesktop/FileManager1"
+
+#define NAUTILUS_TYPE_FREEDESKTOP_DBUS nautilus_freedesktop_dbus_get_type()
+
+G_DECLARE_FINAL_TYPE (NautilusFreedesktopDBus, nautilus_freedesktop_dbus, NAUTILUS, FREEDESKTOP_DBUS, GObject);
+
+NautilusFreedesktopDBus * nautilus_freedesktop_dbus_new (void);
+
+void nautilus_freedesktop_dbus_set_open_locations (NautilusFreedesktopDBus *fdb, const gchar **locations);
+
+void nautilus_freedesktop_dbus_set_open_windows_with_locations (NautilusFreedesktopDBus *fdb, GVariant *locations);
diff --git a/src/nautilus-global-preferences.c b/src/nautilus-global-preferences.c
new file mode 100644
index 0000000..6bc9919
--- /dev/null
+++ b/src/nautilus-global-preferences.c
@@ -0,0 +1,67 @@
+/* nautilus-global-preferences.c - Nautilus specific preference keys and
+ * functions.
+ *
+ * Copyright (C) 1999, 2000, 2001 Eazel, Inc.
+ *
+ * The Gnome Library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * The Gnome Library 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
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with the Gnome Library; see the file COPYING.LIB. If not,
+ * see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Ramiro Estrugo <ramiro@eazel.com>
+ */
+
+#include <config.h>
+#include "nautilus-global-preferences.h"
+
+#include "nautilus-file-utilities.h"
+#include "nautilus-file.h"
+#include "src/nautilus-files-view.h"
+#include <eel/eel-glib-extensions.h>
+#include <eel/eel-stock-dialogs.h>
+#include <eel/eel-string.h>
+#include <glib/gi18n.h>
+
+GSettings *nautilus_preferences;
+GSettings *nautilus_compression_preferences;
+GSettings *nautilus_icon_view_preferences;
+GSettings *nautilus_list_view_preferences;
+GSettings *nautilus_window_state;
+GSettings *gtk_filechooser_preferences;
+GSettings *gnome_lockdown_preferences;
+GSettings *gnome_interface_preferences;
+GSettings *gnome_privacy_preferences;
+
+void
+nautilus_global_preferences_init (void)
+{
+ static gboolean initialized = FALSE;
+
+ if (initialized)
+ {
+ return;
+ }
+
+ initialized = TRUE;
+
+ nautilus_preferences = g_settings_new ("org.gnome.nautilus.preferences");
+ nautilus_compression_preferences = g_settings_new ("org.gnome.nautilus.compression");
+ nautilus_window_state = g_settings_new ("org.gnome.nautilus.window-state");
+ nautilus_icon_view_preferences = g_settings_new ("org.gnome.nautilus.icon-view");
+ nautilus_list_view_preferences = g_settings_new ("org.gnome.nautilus.list-view");
+ /* Some settings such as show hidden files are shared between Nautilus and GTK file chooser */
+ gtk_filechooser_preferences = g_settings_new_with_path ("org.gtk.gtk4.Settings.FileChooser",
+ "/org/gtk/gtk4/settings/file-chooser/");
+ gnome_lockdown_preferences = g_settings_new ("org.gnome.desktop.lockdown");
+ gnome_interface_preferences = g_settings_new ("org.gnome.desktop.interface");
+ gnome_privacy_preferences = g_settings_new ("org.gnome.desktop.privacy");
+}
diff --git a/src/nautilus-global-preferences.h b/src/nautilus-global-preferences.h
new file mode 100644
index 0000000..5a73717
--- /dev/null
+++ b/src/nautilus-global-preferences.h
@@ -0,0 +1,145 @@
+
+/* nautilus-global-preferences.h - Nautilus specific preference keys and
+ functions.
+
+ Copyright (C) 1999, 2000, 2001 Eazel, Inc.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Library 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
+ Library General Public License for more details.
+
+ You should have received a copy of the GNU Library General Public
+ License along with this program; see the file COPYING.LIB. If not,
+ see <http://www.gnu.org/licenses/>.
+
+ Authors: Ramiro Estrugo <ramiro@eazel.com>
+*/
+
+#pragma once
+
+#include "nautilus-global-preferences.h"
+#include <gio/gio.h>
+
+G_BEGIN_DECLS
+
+/* Display */
+#define NAUTILUS_PREFERENCES_SHOW_HIDDEN_FILES "show-hidden"
+
+/* Mouse */
+#define NAUTILUS_PREFERENCES_MOUSE_USE_EXTRA_BUTTONS "mouse-use-extra-buttons"
+#define NAUTILUS_PREFERENCES_MOUSE_FORWARD_BUTTON "mouse-forward-button"
+#define NAUTILUS_PREFERENCES_MOUSE_BACK_BUTTON "mouse-back-button"
+
+/* Single/Double click preference */
+#define NAUTILUS_PREFERENCES_CLICK_POLICY "click-policy"
+
+/* Drag and drop preferences */
+#define NAUTILUS_PREFERENCES_OPEN_FOLDER_ON_DND_HOVER "open-folder-on-dnd-hover"
+
+/* Installing new packages when unknown mime type activated */
+#define NAUTILUS_PREFERENCES_INSTALL_MIME_ACTIVATION "install-mime-activation"
+
+/* Spatial or browser mode */
+#define NAUTILUS_PREFERENCES_NEW_TAB_POSITION "tabs-open-position"
+
+#define NAUTILUS_PREFERENCES_ALWAYS_USE_LOCATION_ENTRY "always-use-location-entry"
+
+/* Which views should be displayed for new windows */
+#define NAUTILUS_WINDOW_STATE_INITIAL_SIZE "initial-size"
+#define NAUTILUS_WINDOW_STATE_MAXIMIZED "maximized"
+
+/* Sorting order */
+#define NAUTILUS_PREFERENCES_SORT_DIRECTORIES_FIRST "sort-directories-first"
+#define NAUTILUS_PREFERENCES_DEFAULT_SORT_ORDER "default-sort-order"
+#define NAUTILUS_PREFERENCES_DEFAULT_SORT_IN_REVERSE_ORDER "default-sort-in-reverse-order"
+
+/* The default folder viewer - one of the two enums below */
+#define NAUTILUS_PREFERENCES_DEFAULT_FOLDER_VIEWER "default-folder-viewer"
+
+/* Compression */
+#define NAUTILUS_PREFERENCES_DEFAULT_COMPRESSION_FORMAT "default-compression-format"
+
+typedef enum
+{
+ NAUTILUS_COMPRESSION_ZIP = 0,
+ NAUTILUS_COMPRESSION_TAR_XZ,
+ NAUTILUS_COMPRESSION_7ZIP,
+ NAUTILUS_COMPRESSION_ENCRYPTED_ZIP
+} NautilusCompressionFormat;
+
+/* Icon View */
+#define NAUTILUS_PREFERENCES_ICON_VIEW_DEFAULT_ZOOM_LEVEL "default-zoom-level"
+
+/* Which text attributes appear beneath icon names */
+#define NAUTILUS_PREFERENCES_ICON_VIEW_CAPTIONS "captions"
+
+/* List View */
+#define NAUTILUS_PREFERENCES_LIST_VIEW_DEFAULT_ZOOM_LEVEL "default-zoom-level"
+#define NAUTILUS_PREFERENCES_LIST_VIEW_DEFAULT_VISIBLE_COLUMNS "default-visible-columns"
+#define NAUTILUS_PREFERENCES_LIST_VIEW_DEFAULT_COLUMN_ORDER "default-column-order"
+#define NAUTILUS_PREFERENCES_LIST_VIEW_USE_TREE "use-tree-view"
+
+enum
+{
+ NAUTILUS_CLICK_POLICY_SINGLE,
+ NAUTILUS_CLICK_POLICY_DOUBLE
+};
+
+typedef enum
+{
+ NAUTILUS_SPEED_TRADEOFF_ALWAYS,
+ NAUTILUS_SPEED_TRADEOFF_LOCAL_ONLY,
+ NAUTILUS_SPEED_TRADEOFF_NEVER
+} NautilusSpeedTradeoffValue;
+
+#define NAUTILUS_PREFERENCES_SHOW_DIRECTORY_ITEM_COUNTS "show-directory-item-counts"
+#define NAUTILUS_PREFERENCES_SHOW_FILE_THUMBNAILS "show-image-thumbnails"
+#define NAUTILUS_PREFERENCES_FILE_THUMBNAIL_LIMIT "thumbnail-limit"
+
+typedef enum
+{
+ NAUTILUS_COMPLEX_SEARCH_BAR,
+ NAUTILUS_SIMPLE_SEARCH_BAR
+} NautilusSearchBarMode;
+
+/* Lockdown */
+#define NAUTILUS_PREFERENCES_LOCKDOWN_COMMAND_LINE "disable-command-line"
+
+/* Recent files */
+#define NAUTILUS_PREFERENCES_RECENT_FILES_ENABLED "remember-recent-files"
+
+/* Default view when searching */
+#define NAUTILUS_PREFERENCES_SEARCH_VIEW "search-view"
+
+/* Search behaviour */
+#define NAUTILUS_PREFERENCES_RECURSIVE_SEARCH "recursive-search"
+
+/* Context menu options */
+#define NAUTILUS_PREFERENCES_SHOW_DELETE_PERMANENTLY "show-delete-permanently"
+#define NAUTILUS_PREFERENCES_SHOW_CREATE_LINK "show-create-link"
+
+/* Full Text Search enabled */
+#define NAUTILUS_PREFERENCES_FTS_ENABLED "fts-enabled"
+
+/* Gtk settings migration happened */
+#define NAUTILUS_PREFERENCES_MIGRATED_GTK_SETTINGS "migrated-gtk-settings"
+
+void nautilus_global_preferences_init (void);
+
+extern GSettings *nautilus_preferences;
+extern GSettings *nautilus_compression_preferences;
+extern GSettings *nautilus_icon_view_preferences;
+extern GSettings *nautilus_list_view_preferences;
+extern GSettings *nautilus_window_state;
+extern GSettings *gtk_filechooser_preferences;
+extern GSettings *gnome_lockdown_preferences;
+extern GSettings *gnome_interface_preferences;
+extern GSettings *gnome_privacy_preferences;
+
+G_END_DECLS
diff --git a/src/nautilus-grid-cell.c b/src/nautilus-grid-cell.c
new file mode 100644
index 0000000..9da7b78
--- /dev/null
+++ b/src/nautilus-grid-cell.c
@@ -0,0 +1,282 @@
+/*
+ * Copyright (C) 2022 The GNOME project contributors
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#include "nautilus-grid-cell.h"
+
+struct _NautilusGridCell
+{
+ NautilusViewCell parent_instance;
+
+ GSignalGroup *item_signal_group;
+
+ GQuark *caption_attributes;
+
+ GtkWidget *fixed_height_box;
+ GtkWidget *icon;
+ GtkWidget *emblems_box;
+ GtkWidget *label;
+ GtkWidget *first_caption;
+ GtkWidget *second_caption;
+ GtkWidget *third_caption;
+};
+
+G_DEFINE_TYPE (NautilusGridCell, nautilus_grid_cell, NAUTILUS_TYPE_VIEW_CELL)
+
+static void
+update_icon (NautilusGridCell *self)
+{
+ g_autoptr (NautilusViewItem) item = NULL;
+ NautilusFileIconFlags flags;
+ g_autoptr (GdkPaintable) icon_paintable = NULL;
+ GtkStyleContext *style_context;
+ NautilusFile *file;
+ guint icon_size;
+ gint scale_factor;
+ g_autofree gchar *thumbnail_path = NULL;
+
+ item = nautilus_view_cell_get_item (NAUTILUS_VIEW_CELL (self));
+ g_return_if_fail (item != NULL);
+ file = nautilus_view_item_get_file (item);
+ icon_size = nautilus_view_item_get_icon_size (item);
+ scale_factor = gtk_widget_get_scale_factor (GTK_WIDGET (self));
+ flags = NAUTILUS_FILE_ICON_FLAGS_USE_THUMBNAILS;
+
+ icon_paintable = nautilus_file_get_icon_paintable (file, icon_size, scale_factor, flags);
+ gtk_picture_set_paintable (GTK_PICTURE (self->icon), icon_paintable);
+
+ /* Set the same height and width for all icons regardless of aspect ratio.
+ */
+ gtk_widget_set_size_request (self->fixed_height_box, icon_size, icon_size);
+ style_context = gtk_widget_get_style_context (self->icon);
+ thumbnail_path = nautilus_file_get_thumbnail_path (file);
+ if (thumbnail_path != NULL &&
+ nautilus_file_should_show_thumbnail (file))
+ {
+ gtk_style_context_add_class (style_context, "thumbnail");
+ }
+ else
+ {
+ gtk_style_context_remove_class (style_context, "thumbnail");
+ }
+}
+
+static void
+update_captions (NautilusGridCell *self)
+{
+ g_autoptr (NautilusViewItem) item = NULL;
+ NautilusFile *file;
+ GtkWidget * const caption_labels[] =
+ {
+ self->first_caption,
+ self->second_caption,
+ self->third_caption
+ };
+ G_STATIC_ASSERT (G_N_ELEMENTS (caption_labels) == NAUTILUS_GRID_CELL_N_CAPTIONS);
+
+ item = nautilus_view_cell_get_item (NAUTILUS_VIEW_CELL (self));
+ g_return_if_fail (item != NULL);
+ file = nautilus_view_item_get_file (item);
+ for (guint i = 0; i < NAUTILUS_GRID_CELL_N_CAPTIONS; i++)
+ {
+ GQuark attribute_q = self->caption_attributes[i];
+ gboolean show_caption;
+
+ show_caption = (attribute_q != 0);
+ gtk_widget_set_visible (caption_labels[i], show_caption);
+ if (show_caption)
+ {
+ g_autofree gchar *string = NULL;
+ string = nautilus_file_get_string_attribute_q (file, attribute_q);
+ gtk_label_set_text (GTK_LABEL (caption_labels[i]), string);
+ }
+ }
+}
+
+static void
+update_emblems (NautilusGridCell *self)
+{
+ g_autoptr (NautilusViewItem) item = NULL;
+ NautilusFile *file;
+ GtkWidget *child;
+ GtkIconTheme *theme;
+ g_autolist (GIcon) emblems = NULL;
+
+ item = nautilus_view_cell_get_item (NAUTILUS_VIEW_CELL (self));
+ g_return_if_fail (item != NULL);
+ file = nautilus_view_item_get_file (item);
+
+ /* Remove old emblems. */
+ while ((child = gtk_widget_get_first_child (self->emblems_box)) != NULL)
+ {
+ gtk_box_remove (GTK_BOX (self->emblems_box), child);
+ }
+
+ theme = gtk_icon_theme_get_for_display (gdk_display_get_default ());
+ emblems = nautilus_file_get_emblem_icons (file);
+ for (GList *l = emblems; l != NULL; l = l->next)
+ {
+ if (!gtk_icon_theme_has_gicon (theme, l->data))
+ {
+ g_autofree gchar *icon_string = g_icon_to_string (l->data);
+ g_warning ("Failed to add emblem. “%s” not found in the icon theme",
+ icon_string);
+ continue;
+ }
+
+ gtk_box_append (GTK_BOX (self->emblems_box),
+ gtk_image_new_from_gicon (l->data));
+ }
+}
+
+
+static void
+on_file_changed (NautilusGridCell *self)
+{
+ g_autoptr (NautilusViewItem) item = NULL;
+ NautilusFile *file;
+ g_autofree gchar *name = NULL;
+
+ item = nautilus_view_cell_get_item (NAUTILUS_VIEW_CELL (self));
+ g_return_if_fail (item != NULL);
+ file = nautilus_view_item_get_file (item);
+
+ update_icon (self);
+ update_emblems (self);
+
+ name = nautilus_file_get_display_name (file);
+
+ gtk_label_set_text (GTK_LABEL (self->label), name);
+ update_captions (self);
+}
+
+static void
+on_item_size_changed (NautilusGridCell *self)
+{
+ update_icon (self);
+ update_captions (self);
+}
+
+static void
+on_item_is_cut_changed (NautilusGridCell *self)
+{
+ gboolean is_cut;
+ g_autoptr (NautilusViewItem) item = NULL;
+
+ item = nautilus_view_cell_get_item (NAUTILUS_VIEW_CELL (self));
+ g_object_get (item,
+ "is-cut", &is_cut,
+ NULL);
+ if (is_cut)
+ {
+ gtk_widget_add_css_class (self->icon, "cut");
+ }
+ else
+ {
+ gtk_widget_remove_css_class (self->icon, "cut");
+ }
+}
+
+static gboolean
+on_label_query_tooltip (GtkWidget *widget,
+ int x,
+ int y,
+ gboolean keyboard_tip,
+ GtkTooltip *tooltip,
+ gpointer user_data)
+{
+ GtkLabel *label = GTK_LABEL (widget);
+
+ if (pango_layout_is_ellipsized (gtk_label_get_layout (label)))
+ {
+ gtk_tooltip_set_markup (tooltip, gtk_label_get_label (label));
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static void
+finalize (GObject *object)
+{
+ NautilusGridCell *self = (NautilusGridCell *) object;
+
+ g_object_unref (self->item_signal_group);
+ G_OBJECT_CLASS (nautilus_grid_cell_parent_class)->finalize (object);
+}
+
+static void
+nautilus_grid_cell_class_init (NautilusGridCellClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+
+ object_class->finalize = finalize;
+
+ gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/nautilus/ui/nautilus-grid-cell.ui");
+
+ gtk_widget_class_bind_template_child (widget_class, NautilusGridCell, fixed_height_box);
+ gtk_widget_class_bind_template_child (widget_class, NautilusGridCell, icon);
+ gtk_widget_class_bind_template_child (widget_class, NautilusGridCell, emblems_box);
+ gtk_widget_class_bind_template_child (widget_class, NautilusGridCell, label);
+ gtk_widget_class_bind_template_child (widget_class, NautilusGridCell, first_caption);
+ gtk_widget_class_bind_template_child (widget_class, NautilusGridCell, second_caption);
+ gtk_widget_class_bind_template_child (widget_class, NautilusGridCell, third_caption);
+
+ gtk_widget_class_set_accessible_role (widget_class, GTK_ACCESSIBLE_ROLE_GRID_CELL);
+}
+
+static void
+nautilus_grid_cell_init (NautilusGridCell *self)
+{
+ gtk_widget_init_template (GTK_WIDGET (self));
+
+ g_signal_connect (self->label, "query-tooltip",
+ G_CALLBACK (on_label_query_tooltip), NULL);
+
+ /* Connect automatically to an item. */
+ self->item_signal_group = g_signal_group_new (NAUTILUS_TYPE_VIEW_ITEM);
+ g_signal_group_connect_swapped (self->item_signal_group, "notify::icon-size",
+ (GCallback) on_item_size_changed, self);
+ g_signal_group_connect_swapped (self->item_signal_group, "notify::is-cut",
+ (GCallback) on_item_is_cut_changed, self);
+ g_signal_group_connect_swapped (self->item_signal_group, "file-changed",
+ (GCallback) on_file_changed, self);
+ g_signal_connect_object (self->item_signal_group, "bind",
+ (GCallback) on_file_changed, self,
+ G_CONNECT_SWAPPED);
+
+ g_object_bind_property (self, "item",
+ self->item_signal_group, "target",
+ G_BINDING_SYNC_CREATE);
+
+#if PANGO_VERSION_CHECK (1, 44, 4)
+ {
+ PangoAttrList *attr_list;
+
+ /* GTK4 TODO: This attribute is set in the UI file but GTK 3 ignores it.
+ * Remove this block after the switch to GTK 4. */
+ attr_list = pango_attr_list_new ();
+ pango_attr_list_insert (attr_list, pango_attr_insert_hyphens_new (FALSE));
+ gtk_label_set_attributes (GTK_LABEL (self->label), attr_list);
+ pango_attr_list_unref (attr_list);
+ }
+#endif
+}
+
+NautilusGridCell *
+nautilus_grid_cell_new (NautilusListBase *view)
+{
+ return g_object_new (NAUTILUS_TYPE_GRID_CELL,
+ "view", view,
+ NULL);
+}
+
+void
+nautilus_grid_cell_set_caption_attributes (NautilusGridCell *self,
+ GQuark *attrs)
+{
+ self->caption_attributes = attrs;
+}
diff --git a/src/nautilus-grid-cell.h b/src/nautilus-grid-cell.h
new file mode 100644
index 0000000..52da1f8
--- /dev/null
+++ b/src/nautilus-grid-cell.h
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2022 The GNOME project contributors
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#pragma once
+
+#include "nautilus-view-cell.h"
+
+G_BEGIN_DECLS
+
+enum
+{
+ NAUTILUS_GRID_CELL_FIRST_CAPTION,
+ NAUTILUS_GRID_CELL_SECOND_CAPTION,
+ NAUTILUS_GRID_CELL_THIRD_CAPTION,
+ NAUTILUS_GRID_CELL_N_CAPTIONS
+};
+
+#define NAUTILUS_TYPE_GRID_CELL (nautilus_grid_cell_get_type())
+
+G_DECLARE_FINAL_TYPE (NautilusGridCell, nautilus_grid_cell, NAUTILUS, GRID_CELL, NautilusViewCell)
+
+NautilusGridCell * nautilus_grid_cell_new (NautilusListBase *view);
+void nautilus_grid_cell_set_caption_attributes (NautilusGridCell *self,
+ GQuark *attrs);
+
+G_END_DECLS
diff --git a/src/nautilus-grid-view.c b/src/nautilus-grid-view.c
new file mode 100644
index 0000000..8e38d7c
--- /dev/null
+++ b/src/nautilus-grid-view.c
@@ -0,0 +1,604 @@
+/*
+ * Copyright (C) 2022 The GNOME project contributors
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#include "nautilus-list-base-private.h"
+#include "nautilus-grid-view.h"
+
+#include "nautilus-grid-cell.h"
+#include "nautilus-global-preferences.h"
+
+struct _NautilusGridView
+{
+ NautilusListBase parent_instance;
+
+ GtkGridView *view_ui;
+
+ GActionGroup *action_group;
+ gint zoom_level;
+
+ gboolean directories_first;
+
+ GQuark caption_attributes[NAUTILUS_GRID_CELL_N_CAPTIONS];
+
+ NautilusFileSortType sort_type;
+ gboolean reversed;
+};
+
+G_DEFINE_TYPE (NautilusGridView, nautilus_grid_view, NAUTILUS_TYPE_LIST_BASE)
+
+static guint get_icon_size_for_zoom_level (NautilusGridZoomLevel zoom_level);
+
+static gint
+nautilus_grid_view_sort (gconstpointer a,
+ gconstpointer b,
+ gpointer user_data)
+{
+ NautilusGridView *self = user_data;
+ NautilusFile *file_a;
+ NautilusFile *file_b;
+
+ file_a = nautilus_view_item_get_file (NAUTILUS_VIEW_ITEM ((gpointer) a));
+ file_b = nautilus_view_item_get_file (NAUTILUS_VIEW_ITEM ((gpointer) b));
+
+ return nautilus_file_compare_for_sort (file_a, file_b,
+ self->sort_type,
+ self->directories_first,
+ self->reversed);
+}
+
+static void
+real_bump_zoom_level (NautilusFilesView *files_view,
+ int zoom_increment)
+{
+ NautilusGridView *self = NAUTILUS_GRID_VIEW (files_view);
+ NautilusGridZoomLevel new_level;
+
+ new_level = self->zoom_level + zoom_increment;
+
+ if (new_level >= NAUTILUS_GRID_ZOOM_LEVEL_SMALL &&
+ new_level <= NAUTILUS_GRID_ZOOM_LEVEL_EXTRA_LARGE)
+ {
+ g_action_group_change_action_state (self->action_group,
+ "zoom-to-level",
+ g_variant_new_int32 (new_level));
+ }
+}
+
+static guint
+get_icon_size_for_zoom_level (NautilusGridZoomLevel zoom_level)
+{
+ switch (zoom_level)
+ {
+ case NAUTILUS_GRID_ZOOM_LEVEL_SMALL:
+ {
+ return NAUTILUS_GRID_ICON_SIZE_SMALL;
+ }
+ break;
+
+ case NAUTILUS_GRID_ZOOM_LEVEL_SMALL_PLUS:
+ {
+ return NAUTILUS_GRID_ICON_SIZE_SMALL_PLUS;
+ }
+ break;
+
+ case NAUTILUS_GRID_ZOOM_LEVEL_MEDIUM:
+ {
+ return NAUTILUS_GRID_ICON_SIZE_MEDIUM;
+ }
+ break;
+
+ case NAUTILUS_GRID_ZOOM_LEVEL_LARGE:
+ {
+ return NAUTILUS_GRID_ICON_SIZE_LARGE;
+ }
+ break;
+
+ case NAUTILUS_GRID_ZOOM_LEVEL_EXTRA_LARGE:
+ {
+ return NAUTILUS_GRID_ICON_SIZE_EXTRA_LARGE;
+ }
+ break;
+ }
+ g_return_val_if_reached (NAUTILUS_GRID_ICON_SIZE_MEDIUM);
+}
+
+static gint
+get_default_zoom_level (void)
+{
+ NautilusGridZoomLevel default_zoom_level;
+
+ default_zoom_level = g_settings_get_enum (nautilus_icon_view_preferences,
+ NAUTILUS_PREFERENCES_ICON_VIEW_DEFAULT_ZOOM_LEVEL);
+
+ /* Sanitize preference value */
+ return CLAMP (default_zoom_level,
+ NAUTILUS_GRID_ZOOM_LEVEL_SMALL,
+ NAUTILUS_GRID_ZOOM_LEVEL_EXTRA_LARGE);
+}
+
+static void
+set_captions_from_preferences (NautilusGridView *self)
+{
+ g_auto (GStrv) value = NULL;
+ gint n_captions_for_zoom_level;
+
+ value = g_settings_get_strv (nautilus_icon_view_preferences,
+ NAUTILUS_PREFERENCES_ICON_VIEW_CAPTIONS);
+
+ /* Set a celling on the number of captions depending on the zoom level. */
+ n_captions_for_zoom_level = MIN (1 + self->zoom_level,
+ G_N_ELEMENTS (self->caption_attributes));
+
+ /* Reset array to zeros beforehand, as we may not refill all elements. */
+ memset (&self->caption_attributes, 0, sizeof (self->caption_attributes));
+ for (gint i = 0, quark_i = 0;
+ value[i] != NULL && quark_i < n_captions_for_zoom_level;
+ i++)
+ {
+ if (g_strcmp0 (value[i], "none") == 0)
+ {
+ continue;
+ }
+
+ /* Convert to quarks in advance, otherwise each NautilusFile attribute
+ * getter would call g_quark_from_string() once for each file. */
+ self->caption_attributes[quark_i] = g_quark_from_string (value[i]);
+ quark_i++;
+ }
+}
+
+static void
+set_zoom_level (NautilusGridView *self,
+ guint new_level)
+{
+ self->zoom_level = new_level;
+
+ /* The zoom level may change how many captions are allowed. Update it before
+ * setting the icon size, under the assumption that NautilusGridCell
+ * updates captions whenever the icon size is set*/
+ set_captions_from_preferences (self);
+
+ nautilus_list_base_set_icon_size (NAUTILUS_LIST_BASE (self),
+ get_icon_size_for_zoom_level (new_level));
+
+ nautilus_files_view_update_toolbar_menus (NAUTILUS_FILES_VIEW (self));
+}
+
+static void
+real_restore_standard_zoom_level (NautilusFilesView *files_view)
+{
+ NautilusGridView *self;
+
+ self = NAUTILUS_GRID_VIEW (files_view);
+ g_action_group_change_action_state (self->action_group,
+ "zoom-to-level",
+ g_variant_new_int32 (NAUTILUS_GRID_ZOOM_LEVEL_MEDIUM));
+}
+
+static gboolean
+real_is_zoom_level_default (NautilusFilesView *files_view)
+{
+ NautilusGridView *self;
+ guint icon_size;
+
+ self = NAUTILUS_GRID_VIEW (files_view);
+ icon_size = get_icon_size_for_zoom_level (self->zoom_level);
+
+ return icon_size == NAUTILUS_GRID_ICON_SIZE_MEDIUM;
+}
+
+static gboolean
+real_can_zoom_in (NautilusFilesView *files_view)
+{
+ NautilusGridView *self = NAUTILUS_GRID_VIEW (files_view);
+
+ return self->zoom_level < NAUTILUS_GRID_ZOOM_LEVEL_EXTRA_LARGE;
+}
+
+static gboolean
+real_can_zoom_out (NautilusFilesView *files_view)
+{
+ NautilusGridView *self = NAUTILUS_GRID_VIEW (files_view);
+
+ return self->zoom_level > NAUTILUS_GRID_ZOOM_LEVEL_SMALL;
+}
+
+/* The generic implementation in src/nautilus-list-base.c doesn't allow the
+ * 2-dimensional movements expected from a grid. Let's hack GTK here. */
+static void
+real_preview_selection_event (NautilusFilesView *files_view,
+ GtkDirectionType direction)
+{
+ NautilusGridView *self = NAUTILUS_GRID_VIEW (files_view);
+ guint direction_keyval;
+ g_autoptr (GtkShortcutTrigger) direction_trigger = NULL;
+ g_autoptr (GListModel) controllers = NULL;
+ gboolean success = FALSE;
+
+ /* We want the same behavior as when the user presses the arrow keys while
+ * the focus is in the view. So, let's get the matching arrow key. */
+ switch (direction)
+ {
+ case GTK_DIR_UP:
+ {
+ direction_keyval = GDK_KEY_Up;
+ }
+ break;
+
+ case GTK_DIR_DOWN:
+ {
+ direction_keyval = GDK_KEY_Down;
+ }
+ break;
+
+ case GTK_DIR_LEFT:
+ {
+ direction_keyval = GDK_KEY_Left;
+ }
+ break;
+
+ case GTK_DIR_RIGHT:
+ {
+ direction_keyval = GDK_KEY_Right;
+ }
+ break;
+
+ default:
+ {
+ g_return_if_reached ();
+ }
+ }
+
+ /* We cannot simulate a click, but we can find the shortcut it triggers and
+ * activate its action programatically.
+ *
+ * First, we create out would-be trigger.*/
+ direction_trigger = gtk_keyval_trigger_new (direction_keyval, 0);
+
+ /* Then we iterate over the shortcut installed in GtkGridView until we find
+ * a matching trigger. There may be multiple shortcut controllers, and each
+ * shortcut controller may hold multiple shortcuts each. Let's loop. */
+ controllers = gtk_widget_observe_controllers (GTK_WIDGET (self->view_ui));
+ for (guint i = 0; i < g_list_model_get_n_items (controllers); i++)
+ {
+ g_autoptr (GtkEventController) controller = g_list_model_get_item (controllers, i);
+
+ if (!GTK_IS_SHORTCUT_CONTROLLER (controller))
+ {
+ continue;
+ }
+
+ for (guint j = 0; j < g_list_model_get_n_items (G_LIST_MODEL (controller)); j++)
+ {
+ g_autoptr (GtkShortcut) shortcut = g_list_model_get_item (G_LIST_MODEL (controller), j);
+ GtkShortcutTrigger *trigger = gtk_shortcut_get_trigger (shortcut);
+
+ if (gtk_shortcut_trigger_equal (trigger, direction_trigger))
+ {
+ /* Match found. Activate the action to move cursor. */
+ success = gtk_shortcut_action_activate (gtk_shortcut_get_action (shortcut),
+ 0,
+ GTK_WIDGET (self->view_ui),
+ gtk_shortcut_get_arguments (shortcut));
+ break;
+ }
+ }
+ }
+
+ /* If the hack fails (GTK may change it's internal behavior), fallback. */
+ if (!success)
+ {
+ NAUTILUS_FILES_VIEW_CLASS (nautilus_grid_view_parent_class)->preview_selection_event (files_view, direction);
+ }
+}
+
+/* We only care about the keyboard activation part that GtkGridView provides,
+ * but we don't need any special filtering here. Indeed, we ask GtkGridView
+ * to not activate on single click, and we get to handle double clicks before
+ * GtkGridView does (as one of widget subclassing's goal is to modify the parent
+ * class's behavior), while claiming the click gestures, so it means GtkGridView
+ * will never react to a click event to emit this signal. So we should be pretty
+ * safe here with regards to our custom item click handling.
+ */
+static void
+on_grid_view_item_activated (GtkGridView *grid_view,
+ guint position,
+ gpointer user_data)
+{
+ NautilusGridView *self = NAUTILUS_GRID_VIEW (user_data);
+
+ nautilus_files_view_activate_selection (NAUTILUS_FILES_VIEW (self));
+}
+
+static guint
+real_get_icon_size (NautilusListBase *list_base_view)
+{
+ NautilusGridView *self = NAUTILUS_GRID_VIEW (list_base_view);
+
+ return get_icon_size_for_zoom_level (self->zoom_level);
+}
+
+static GtkWidget *
+real_get_view_ui (NautilusListBase *list_base_view)
+{
+ NautilusGridView *self = NAUTILUS_GRID_VIEW (list_base_view);
+
+ return GTK_WIDGET (self->view_ui);
+}
+
+static void
+real_scroll_to_item (NautilusListBase *list_base_view,
+ guint position)
+{
+ NautilusGridView *self = NAUTILUS_GRID_VIEW (list_base_view);
+
+ gtk_widget_activate_action (GTK_WIDGET (self->view_ui),
+ "list.scroll-to-item",
+ "u",
+ position);
+}
+
+static void
+real_sort_directories_first_changed (NautilusFilesView *files_view)
+{
+ NautilusGridView *self;
+ NautilusViewModel *model;
+ g_autoptr (GtkCustomSorter) sorter = NULL;
+
+ self = NAUTILUS_GRID_VIEW (files_view);
+ self->directories_first = nautilus_files_view_should_sort_directories_first (NAUTILUS_FILES_VIEW (self));
+
+ model = nautilus_list_base_get_model (NAUTILUS_LIST_BASE (self));
+ sorter = gtk_custom_sorter_new (nautilus_grid_view_sort, self, NULL);
+ nautilus_view_model_set_sorter (model, GTK_SORTER (sorter));
+}
+
+static void
+action_sort_order_changed (GSimpleAction *action,
+ GVariant *value,
+ gpointer user_data)
+{
+ const gchar *target_name;
+ NautilusGridView *self = NAUTILUS_GRID_VIEW (user_data);
+ NautilusViewModel *model;
+ g_autoptr (GtkCustomSorter) sorter = NULL;
+
+ /* Don't resort if the action is in the same state as before */
+ if (g_variant_equal (value, g_action_get_state (G_ACTION (action))))
+ {
+ return;
+ }
+
+ g_variant_get (value, "(&sb)", &target_name, &self->reversed);
+ self->sort_type = get_sorts_type_from_metadata_text (target_name);
+
+ sorter = gtk_custom_sorter_new (nautilus_grid_view_sort, self, NULL);
+ model = nautilus_list_base_get_model (NAUTILUS_LIST_BASE (self));
+ nautilus_view_model_set_sorter (model, GTK_SORTER (sorter));
+ set_directory_sort_metadata (nautilus_files_view_get_directory_as_file (NAUTILUS_FILES_VIEW (self)),
+ target_name,
+ self->reversed);
+
+ g_simple_action_set_state (action, value);
+}
+
+static guint
+real_get_view_id (NautilusFilesView *files_view)
+{
+ return NAUTILUS_VIEW_GRID_ID;
+}
+
+static void
+action_zoom_to_level (GSimpleAction *action,
+ GVariant *state,
+ gpointer user_data)
+{
+ NautilusGridView *self = NAUTILUS_GRID_VIEW (user_data);
+ int zoom_level;
+
+ zoom_level = g_variant_get_int32 (state);
+ set_zoom_level (self, zoom_level);
+ g_simple_action_set_state (G_SIMPLE_ACTION (action), state);
+
+ if (g_settings_get_enum (nautilus_icon_view_preferences,
+ NAUTILUS_PREFERENCES_ICON_VIEW_DEFAULT_ZOOM_LEVEL) != zoom_level)
+ {
+ g_settings_set_enum (nautilus_icon_view_preferences,
+ NAUTILUS_PREFERENCES_ICON_VIEW_DEFAULT_ZOOM_LEVEL,
+ zoom_level);
+ }
+}
+
+static void
+on_captions_preferences_changed (NautilusGridView *self)
+{
+ set_captions_from_preferences (self);
+
+ /* Hack: this relies on the assumption that NautilusGridCell updates
+ * captions whenever the icon size is set (even if it's the same value). */
+ nautilus_list_base_set_icon_size (NAUTILUS_LIST_BASE (self),
+ get_icon_size_for_zoom_level (self->zoom_level));
+}
+
+static void
+dispose (GObject *object)
+{
+ G_OBJECT_CLASS (nautilus_grid_view_parent_class)->dispose (object);
+}
+
+static void
+finalize (GObject *object)
+{
+ G_OBJECT_CLASS (nautilus_grid_view_parent_class)->finalize (object);
+}
+
+static void
+bind_cell (GtkSignalListItemFactory *factory,
+ GtkListItem *listitem,
+ gpointer user_data)
+{
+ GtkWidget *cell;
+ NautilusViewItem *item;
+
+ cell = gtk_list_item_get_child (listitem);
+ item = NAUTILUS_VIEW_ITEM (gtk_list_item_get_item (listitem));
+
+ nautilus_view_item_set_item_ui (item, cell);
+
+ if (nautilus_view_cell_once (NAUTILUS_VIEW_CELL (cell)))
+ {
+ GtkWidget *parent;
+
+ /* At the time of ::setup emission, the item ui has got no parent yet,
+ * that's why we need to complete the widget setup process here, on the
+ * first time ::bind is emitted. */
+ parent = gtk_widget_get_parent (cell);
+ gtk_widget_set_halign (parent, GTK_ALIGN_CENTER);
+ gtk_widget_set_valign (parent, GTK_ALIGN_START);
+ gtk_widget_set_margin_top (parent, 3);
+ gtk_widget_set_margin_bottom (parent, 3);
+ gtk_widget_set_margin_start (parent, 3);
+ gtk_widget_set_margin_end (parent, 3);
+
+ gtk_accessible_update_relation (GTK_ACCESSIBLE (parent),
+ GTK_ACCESSIBLE_RELATION_LABELLED_BY, cell, NULL,
+ -1);
+ }
+}
+
+static void
+unbind_cell (GtkSignalListItemFactory *factory,
+ GtkListItem *listitem,
+ gpointer user_data)
+{
+ NautilusViewItem *item;
+
+ item = NAUTILUS_VIEW_ITEM (gtk_list_item_get_item (listitem));
+
+ nautilus_view_item_set_item_ui (item, NULL);
+}
+
+static void
+setup_cell (GtkSignalListItemFactory *factory,
+ GtkListItem *listitem,
+ gpointer user_data)
+{
+ NautilusGridView *self = NAUTILUS_GRID_VIEW (user_data);
+ NautilusGridCell *cell;
+
+ cell = nautilus_grid_cell_new (NAUTILUS_LIST_BASE (self));
+ setup_cell_common (listitem, NAUTILUS_VIEW_CELL (cell));
+
+ nautilus_grid_cell_set_caption_attributes (cell, self->caption_attributes);
+}
+
+static GtkGridView *
+create_view_ui (NautilusGridView *self)
+{
+ NautilusViewModel *model;
+ GtkListItemFactory *factory;
+ GtkWidget *widget;
+
+ model = nautilus_list_base_get_model (NAUTILUS_LIST_BASE (self));
+
+ factory = gtk_signal_list_item_factory_new ();
+ g_signal_connect (factory, "setup", G_CALLBACK (setup_cell), self);
+ g_signal_connect (factory, "bind", G_CALLBACK (bind_cell), self);
+ g_signal_connect (factory, "unbind", G_CALLBACK (unbind_cell), self);
+
+ widget = gtk_grid_view_new (GTK_SELECTION_MODEL (model), factory);
+
+ /* We don't use the built-in child activation feature for clicks because it
+ * doesn't fill all our needs nor does it match our expected behavior.
+ * Instead, we roll our own event handling and double/single click mode.
+ * However, GtkGridView:single-click-activate has other effects besides
+ * activation, as it affects the selection behavior as well (e.g. selects on
+ * hover). Setting it to FALSE gives us the expected behavior. */
+ gtk_grid_view_set_single_click_activate (GTK_GRID_VIEW (widget), FALSE);
+ gtk_grid_view_set_max_columns (GTK_GRID_VIEW (widget), 20);
+ gtk_grid_view_set_enable_rubberband (GTK_GRID_VIEW (widget), TRUE);
+
+ /* While we don't want to use GTK's click activation, we'll let it handle
+ * the key activation part (with Enter).
+ */
+ g_signal_connect (widget, "activate", G_CALLBACK (on_grid_view_item_activated), self);
+
+ return GTK_GRID_VIEW (widget);
+}
+
+const GActionEntry view_icon_actions[] =
+{
+ { "sort", NULL, "(sb)", "('invalid',false)", action_sort_order_changed },
+ { "zoom-to-level", NULL, NULL, "100", action_zoom_to_level }
+};
+
+static void
+nautilus_grid_view_class_init (NautilusGridViewClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ NautilusFilesViewClass *files_view_class = NAUTILUS_FILES_VIEW_CLASS (klass);
+ NautilusListBaseClass *list_base_view_class = NAUTILUS_LIST_BASE_CLASS (klass);
+
+ object_class->dispose = dispose;
+ object_class->finalize = finalize;
+
+ files_view_class->bump_zoom_level = real_bump_zoom_level;
+ files_view_class->can_zoom_in = real_can_zoom_in;
+ files_view_class->can_zoom_out = real_can_zoom_out;
+ files_view_class->sort_directories_first_changed = real_sort_directories_first_changed;
+ files_view_class->get_view_id = real_get_view_id;
+ files_view_class->restore_standard_zoom_level = real_restore_standard_zoom_level;
+ files_view_class->is_zoom_level_default = real_is_zoom_level_default;
+ files_view_class->preview_selection_event = real_preview_selection_event;
+
+ list_base_view_class->get_icon_size = real_get_icon_size;
+ list_base_view_class->get_view_ui = real_get_view_ui;
+ list_base_view_class->scroll_to_item = real_scroll_to_item;
+}
+
+static void
+nautilus_grid_view_init (NautilusGridView *self)
+{
+ GtkWidget *content_widget;
+
+ gtk_widget_add_css_class (GTK_WIDGET (self), "nautilus-grid-view");
+
+ set_captions_from_preferences (self);
+ g_signal_connect_object (nautilus_icon_view_preferences,
+ "changed::" NAUTILUS_PREFERENCES_ICON_VIEW_CAPTIONS,
+ G_CALLBACK (on_captions_preferences_changed),
+ self,
+ G_CONNECT_SWAPPED);
+
+ content_widget = nautilus_files_view_get_content_widget (NAUTILUS_FILES_VIEW (self));
+
+ self->view_ui = create_view_ui (self);
+ nautilus_list_base_setup_gestures (NAUTILUS_LIST_BASE (self));
+
+ gtk_scrolled_window_set_child (GTK_SCROLLED_WINDOW (content_widget),
+ GTK_WIDGET (self->view_ui));
+
+ self->action_group = nautilus_files_view_get_action_group (NAUTILUS_FILES_VIEW (self));
+ g_action_map_add_action_entries (G_ACTION_MAP (self->action_group),
+ view_icon_actions,
+ G_N_ELEMENTS (view_icon_actions),
+ self);
+
+ self->directories_first = nautilus_files_view_should_sort_directories_first (NAUTILUS_FILES_VIEW (self));
+
+ self->zoom_level = get_default_zoom_level ();
+ /* Keep the action synced with the actual value, so the toolbar can poll it */
+ g_action_group_change_action_state (nautilus_files_view_get_action_group (NAUTILUS_FILES_VIEW (self)),
+ "zoom-to-level", g_variant_new_int32 (self->zoom_level));
+}
+
+NautilusGridView *
+nautilus_grid_view_new (NautilusWindowSlot *slot)
+{
+ return g_object_new (NAUTILUS_TYPE_GRID_VIEW,
+ "window-slot", slot,
+ NULL);
+}
diff --git a/src/nautilus-grid-view.h b/src/nautilus-grid-view.h
new file mode 100644
index 0000000..4c8835f
--- /dev/null
+++ b/src/nautilus-grid-view.h
@@ -0,0 +1,20 @@
+/*
+ * Copyright (C) 2022 The GNOME project contributors
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#pragma once
+
+#include "nautilus-list-base.h"
+#include "nautilus-window-slot.h"
+
+G_BEGIN_DECLS
+
+#define NAUTILUS_TYPE_GRID_VIEW (nautilus_grid_view_get_type())
+
+G_DECLARE_FINAL_TYPE (NautilusGridView, nautilus_grid_view, NAUTILUS, GRID_VIEW, NautilusListBase)
+
+NautilusGridView *nautilus_grid_view_new (NautilusWindowSlot *slot);
+
+G_END_DECLS
diff --git a/src/nautilus-history-controls.c b/src/nautilus-history-controls.c
new file mode 100644
index 0000000..8b14cd6
--- /dev/null
+++ b/src/nautilus-history-controls.c
@@ -0,0 +1,313 @@
+/*
+ * Copyright (C) 2022 The GNOME project contributors
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include "nautilus-history-controls.h"
+
+#include "nautilus-bookmark.h"
+#include "nautilus-window.h"
+
+struct _NautilusHistoryControls
+{
+ AdwBin parent_instance;
+
+ GtkWidget *back_button;
+ GtkWidget *back_menu;
+
+ GtkWidget *forward_button;
+ GtkWidget *forward_menu;
+
+ NautilusWindowSlot *window_slot;
+};
+
+G_DEFINE_FINAL_TYPE (NautilusHistoryControls, nautilus_history_controls, ADW_TYPE_BIN)
+
+enum
+{
+ PROP_0,
+ PROP_WINDOW_SLOT,
+ N_PROPS
+};
+
+static GParamSpec *properties[N_PROPS];
+
+static void
+fill_menu (NautilusHistoryControls *self,
+ GMenu *menu,
+ gboolean back)
+{
+ guint index;
+ GList *list;
+ const gchar *name;
+
+ list = back ? nautilus_window_slot_get_back_history (self->window_slot) :
+ nautilus_window_slot_get_forward_history (self->window_slot);
+
+ index = 0;
+ while (list != NULL)
+ {
+ g_autoptr (GMenuItem) item = NULL;
+
+ name = nautilus_bookmark_get_name (NAUTILUS_BOOKMARK (list->data));
+ item = g_menu_item_new (name, NULL);
+ g_menu_item_set_action_and_target (item,
+ back ? "win.back-n" : "win.forward-n",
+ "u", index);
+ g_menu_append_item (menu, item);
+
+ list = g_list_next (list);
+ ++index;
+ }
+}
+
+static void
+show_menu (NautilusHistoryControls *self,
+ GtkWidget *widget)
+{
+ g_autoptr (GMenu) menu = NULL;
+ NautilusNavigationDirection direction;
+ GtkPopoverMenu *popover;
+
+ menu = g_menu_new ();
+
+ direction = GPOINTER_TO_UINT (g_object_get_data (G_OBJECT (widget),
+ "nav-direction"));
+
+ switch (direction)
+ {
+ case NAUTILUS_NAVIGATION_DIRECTION_FORWARD:
+ {
+ fill_menu (self, menu, FALSE);
+ popover = GTK_POPOVER_MENU (self->forward_menu);
+ }
+ break;
+
+ case NAUTILUS_NAVIGATION_DIRECTION_BACK:
+ {
+ fill_menu (self, menu, TRUE);
+ popover = GTK_POPOVER_MENU (self->back_menu);
+ }
+ break;
+
+ default:
+ {
+ g_assert_not_reached ();
+ }
+ break;
+ }
+
+ gtk_popover_menu_set_menu_model (popover, G_MENU_MODEL (menu));
+ gtk_popover_popup (GTK_POPOVER (popover));
+}
+
+static void
+navigation_button_press_cb (GtkGestureClick *gesture,
+ gint n_press,
+ gdouble x,
+ gdouble y,
+ gpointer user_data)
+{
+ NautilusHistoryControls *self;
+ NautilusWindow *window;
+ GtkWidget *widget;
+ guint button;
+
+ self = NAUTILUS_HISTORY_CONTROLS (user_data);
+ button = gtk_gesture_single_get_current_button (GTK_GESTURE_SINGLE (gesture));
+ widget = gtk_event_controller_get_widget (GTK_EVENT_CONTROLLER (gesture));
+ window = NAUTILUS_WINDOW (gtk_widget_get_root (GTK_WIDGET (self)));
+
+ if (button == GDK_BUTTON_PRIMARY)
+ {
+ /* Don't do anything, primary click is handled through activate */
+ gtk_gesture_set_state (GTK_GESTURE (gesture), GTK_EVENT_SEQUENCE_DENIED);
+ return;
+ }
+ else if (button == GDK_BUTTON_MIDDLE)
+ {
+ NautilusNavigationDirection direction;
+
+ direction = GPOINTER_TO_UINT (g_object_get_data (G_OBJECT (widget),
+ "nav-direction"));
+
+ nautilus_window_back_or_forward_in_new_tab (window, direction);
+ }
+ else if (button == GDK_BUTTON_SECONDARY)
+ {
+ show_menu (self, widget);
+ }
+}
+
+static void
+back_button_longpress_cb (GtkGestureLongPress *gesture,
+ double x,
+ double y,
+ gpointer user_data)
+{
+ NautilusHistoryControls *self = user_data;
+
+ show_menu (self, self->back_button);
+}
+
+static void
+forward_button_longpress_cb (GtkGestureLongPress *gesture,
+ double x,
+ double y,
+ gpointer user_data)
+{
+ NautilusHistoryControls *self = user_data;
+
+ show_menu (self, self->forward_button);
+}
+
+
+static void
+nautilus_history_controls_contructed (GObject *object)
+{
+ NautilusHistoryControls *self;
+ GtkEventController *controller;
+
+ self = NAUTILUS_HISTORY_CONTROLS (object);
+
+ controller = GTK_EVENT_CONTROLLER (gtk_gesture_long_press_new ());
+ gtk_widget_add_controller (self->back_button, controller);
+ g_signal_connect (controller, "pressed",
+ G_CALLBACK (back_button_longpress_cb), self);
+
+ controller = GTK_EVENT_CONTROLLER (gtk_gesture_long_press_new ());
+ gtk_widget_add_controller (self->forward_button, controller);
+ g_signal_connect (controller, "pressed",
+ G_CALLBACK (forward_button_longpress_cb), self);
+
+ g_object_set_data (G_OBJECT (self->back_button), "nav-direction",
+ GUINT_TO_POINTER (NAUTILUS_NAVIGATION_DIRECTION_BACK));
+ g_object_set_data (G_OBJECT (self->forward_button), "nav-direction",
+ GUINT_TO_POINTER (NAUTILUS_NAVIGATION_DIRECTION_FORWARD));
+
+ controller = GTK_EVENT_CONTROLLER (gtk_gesture_click_new ());
+ gtk_widget_add_controller (self->back_button, controller);
+ gtk_gesture_single_set_button (GTK_GESTURE_SINGLE (controller), 0);
+ g_signal_connect (controller, "pressed",
+ G_CALLBACK (navigation_button_press_cb), self);
+
+ controller = GTK_EVENT_CONTROLLER (gtk_gesture_click_new ());
+ gtk_widget_add_controller (self->forward_button, controller);
+ gtk_gesture_single_set_button (GTK_GESTURE_SINGLE (controller), 0);
+ g_signal_connect (controller, "pressed",
+ G_CALLBACK (navigation_button_press_cb), self);
+}
+
+static void
+nautilus_history_controls_dispose (GObject *object)
+{
+ NautilusHistoryControls *self;
+
+ self = NAUTILUS_HISTORY_CONTROLS (object);
+
+ g_clear_pointer (&self->back_menu, gtk_widget_unparent);
+ g_clear_pointer (&self->forward_menu, gtk_widget_unparent);
+
+ G_OBJECT_CLASS (nautilus_history_controls_parent_class)->dispose (object);
+}
+
+static void
+nautilus_history_controls_set_window_slot (NautilusHistoryControls *self,
+ NautilusWindowSlot *window_slot)
+{
+ g_return_if_fail (NAUTILUS_IS_HISTORY_CONTROLS (self));
+ g_return_if_fail (window_slot == NULL || NAUTILUS_IS_WINDOW_SLOT (window_slot));
+
+ if (self->window_slot == window_slot)
+ {
+ return;
+ }
+
+ self->window_slot = window_slot;
+
+ g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_WINDOW_SLOT]);
+}
+
+static void
+nautilus_history_controls_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ NautilusHistoryControls *self = NAUTILUS_HISTORY_CONTROLS (object);
+
+ switch (prop_id)
+ {
+ case PROP_WINDOW_SLOT:
+ {
+ g_value_set_object (value, G_OBJECT (self->window_slot));
+ break;
+ }
+
+ default:
+ {
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+ }
+}
+
+static void
+nautilus_history_controls_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ NautilusHistoryControls *self = NAUTILUS_HISTORY_CONTROLS (object);
+
+ switch (prop_id)
+ {
+ case PROP_WINDOW_SLOT:
+ {
+ nautilus_history_controls_set_window_slot (self, NAUTILUS_WINDOW_SLOT (g_value_get_object (value)));
+ break;
+ }
+
+ default:
+ {
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+ }
+}
+
+static void
+nautilus_history_controls_class_init (NautilusHistoryControlsClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+
+ object_class->constructed = nautilus_history_controls_contructed;
+ object_class->dispose = nautilus_history_controls_dispose;
+ object_class->set_property = nautilus_history_controls_set_property;
+ object_class->get_property = nautilus_history_controls_get_property;
+
+ properties[PROP_WINDOW_SLOT] = g_param_spec_object ("window-slot",
+ NULL, NULL,
+ NAUTILUS_TYPE_WINDOW_SLOT,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_EXPLICIT_NOTIFY);
+
+ g_object_class_install_properties (object_class, N_PROPS, properties);
+
+ gtk_widget_class_set_template_from_resource (widget_class,
+ "/org/gnome/nautilus/ui/nautilus-history-controls.ui");
+ gtk_widget_class_bind_template_child (widget_class, NautilusHistoryControls, back_button);
+ gtk_widget_class_bind_template_child (widget_class, NautilusHistoryControls, back_menu);
+ gtk_widget_class_bind_template_child (widget_class, NautilusHistoryControls, forward_button);
+ gtk_widget_class_bind_template_child (widget_class, NautilusHistoryControls, forward_menu);
+}
+
+static void
+nautilus_history_controls_init (NautilusHistoryControls *self)
+{
+ gtk_widget_init_template (GTK_WIDGET (self));
+
+ gtk_widget_set_parent (self->back_menu, self->back_button);
+ g_signal_connect (self->back_menu, "destroy", G_CALLBACK (gtk_widget_unparent), NULL);
+ gtk_widget_set_parent (self->forward_menu, self->forward_button);
+ g_signal_connect (self->forward_menu, "destroy", G_CALLBACK (gtk_widget_unparent), NULL);
+}
diff --git a/src/nautilus-history-controls.h b/src/nautilus-history-controls.h
new file mode 100644
index 0000000..26d484d
--- /dev/null
+++ b/src/nautilus-history-controls.h
@@ -0,0 +1,20 @@
+/*
+ * Copyright (C) 2022 The GNOME project contributors
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#pragma once
+
+#include <gtk/gtk.h>
+#include <libadwaita-1/adwaita.h>
+
+#include "nautilus-window-slot.h"
+
+G_BEGIN_DECLS
+
+#define NAUTILUS_TYPE_HISTORY_CONTROLS nautilus_history_controls_get_type()
+
+G_DECLARE_FINAL_TYPE (NautilusHistoryControls, nautilus_history_controls, NAUTILUS, HISTORY_CONTROLS, AdwBin)
+
+G_END_DECLS
diff --git a/src/nautilus-icon-info.c b/src/nautilus-icon-info.c
new file mode 100644
index 0000000..c6b1e9a
--- /dev/null
+++ b/src/nautilus-icon-info.c
@@ -0,0 +1,505 @@
+/* nautilus-icon-info.c
+ * Copyright (C) 2007 Red Hat, Inc., Alexander Larsson <alexl@redhat.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "nautilus-icon-info.h"
+
+#include "nautilus-enums.h"
+
+struct _NautilusIconInfo
+{
+ GObject parent;
+
+ gboolean sole_owner;
+ gint64 last_use_time;
+ GdkPaintable *paintable;
+
+ char *icon_name;
+};
+
+static void schedule_reap_cache (void);
+
+G_DEFINE_TYPE (NautilusIconInfo,
+ nautilus_icon_info,
+ G_TYPE_OBJECT);
+
+static void
+nautilus_icon_info_init (NautilusIconInfo *icon)
+{
+ icon->last_use_time = g_get_monotonic_time ();
+ icon->sole_owner = TRUE;
+}
+
+gboolean
+nautilus_icon_info_is_fallback (NautilusIconInfo *icon)
+{
+ return icon->paintable == NULL;
+}
+
+static void
+paintable_toggle_notify (gpointer info,
+ GObject *object,
+ gboolean is_last_ref)
+{
+ NautilusIconInfo *icon = info;
+
+ if (is_last_ref)
+ {
+ icon->sole_owner = TRUE;
+ g_object_remove_toggle_ref (object,
+ paintable_toggle_notify,
+ info);
+ icon->last_use_time = g_get_monotonic_time ();
+ schedule_reap_cache ();
+ }
+}
+
+static void
+nautilus_icon_info_finalize (GObject *object)
+{
+ NautilusIconInfo *icon;
+
+ icon = NAUTILUS_ICON_INFO (object);
+
+ if (!icon->sole_owner && icon->paintable)
+ {
+ g_object_remove_toggle_ref (G_OBJECT (icon->paintable),
+ paintable_toggle_notify,
+ icon);
+ }
+
+ if (icon->paintable)
+ {
+ g_object_unref (icon->paintable);
+ }
+ g_free (icon->icon_name);
+
+ G_OBJECT_CLASS (nautilus_icon_info_parent_class)->finalize (object);
+}
+
+static void
+nautilus_icon_info_class_init (NautilusIconInfoClass *icon_info_class)
+{
+ GObjectClass *gobject_class;
+
+ gobject_class = (GObjectClass *) icon_info_class;
+
+ gobject_class->finalize = nautilus_icon_info_finalize;
+}
+
+NautilusIconInfo *
+nautilus_icon_info_new_for_paintable (GdkPaintable *paintable,
+ gint scale)
+{
+ NautilusIconInfo *icon;
+
+ icon = g_object_new (NAUTILUS_TYPE_ICON_INFO, NULL);
+
+ if (paintable != NULL)
+ {
+ icon->paintable = g_object_ref (paintable);
+ }
+
+ return icon;
+}
+
+static NautilusIconInfo *
+nautilus_icon_info_new_for_icon_paintable (GtkIconPaintable *icon_paintable,
+ gint scale)
+{
+ NautilusIconInfo *icon;
+ g_autoptr (GFile) file = NULL;
+ char *basename, *p;
+
+ icon = nautilus_icon_info_new_for_paintable (GDK_PAINTABLE (icon_paintable), scale);
+
+ file = gtk_icon_paintable_get_file (icon_paintable);
+ if (file != NULL)
+ {
+ basename = g_file_get_basename (file);
+ p = strrchr (basename, '.');
+ if (p)
+ {
+ *p = 0;
+ }
+ icon->icon_name = basename;
+ }
+ else
+ {
+ icon->icon_name = g_strdup (gtk_icon_paintable_get_icon_name (icon_paintable));
+ }
+
+ return icon;
+}
+
+
+typedef struct
+{
+ GIcon *icon;
+ int scale;
+ int size;
+} LoadableIconKey;
+
+typedef struct
+{
+ char *icon_name;
+ int scale;
+ int size;
+} ThemedIconKey;
+
+static GHashTable *loadable_icon_cache = NULL;
+static GHashTable *themed_icon_cache = NULL;
+static guint reap_cache_timeout = 0;
+
+#define MICROSEC_PER_SEC ((guint64) 1000000L)
+
+static guint time_now;
+
+static gboolean
+reap_old_icon (gpointer key,
+ gpointer value,
+ gpointer user_info)
+{
+ NautilusIconInfo *icon = value;
+ gboolean *reapable_icons_left = user_info;
+
+ if (icon->sole_owner)
+ {
+ if (time_now - icon->last_use_time > 30 * MICROSEC_PER_SEC)
+ {
+ /* This went unused 30 secs ago. reap */
+ return TRUE;
+ }
+ else
+ {
+ /* We can reap this soon */
+ *reapable_icons_left = TRUE;
+ }
+ }
+
+ return FALSE;
+}
+
+static gboolean
+reap_cache (gpointer data)
+{
+ gboolean reapable_icons_left;
+
+ reapable_icons_left = TRUE;
+
+ time_now = g_get_monotonic_time ();
+
+ if (loadable_icon_cache)
+ {
+ g_hash_table_foreach_remove (loadable_icon_cache,
+ reap_old_icon,
+ &reapable_icons_left);
+ }
+
+ if (themed_icon_cache)
+ {
+ g_hash_table_foreach_remove (themed_icon_cache,
+ reap_old_icon,
+ &reapable_icons_left);
+ }
+
+ if (reapable_icons_left)
+ {
+ return TRUE;
+ }
+ else
+ {
+ reap_cache_timeout = 0;
+ return FALSE;
+ }
+}
+
+static void
+schedule_reap_cache (void)
+{
+ if (reap_cache_timeout == 0)
+ {
+ reap_cache_timeout = g_timeout_add_seconds_full (0, 5,
+ reap_cache,
+ NULL, NULL);
+ }
+}
+
+void
+nautilus_icon_info_clear_caches (void)
+{
+ if (loadable_icon_cache)
+ {
+ g_hash_table_remove_all (loadable_icon_cache);
+ }
+
+ if (themed_icon_cache)
+ {
+ g_hash_table_remove_all (themed_icon_cache);
+ }
+}
+
+static guint
+loadable_icon_key_hash (LoadableIconKey *key)
+{
+ return g_icon_hash (key->icon) ^ key->scale ^ key->size;
+}
+
+static gboolean
+loadable_icon_key_equal (const LoadableIconKey *a,
+ const LoadableIconKey *b)
+{
+ return a->size == b->size &&
+ a->scale == b->scale &&
+ g_icon_equal (a->icon, b->icon);
+}
+
+static LoadableIconKey *
+loadable_icon_key_new (GIcon *icon,
+ int scale,
+ int size)
+{
+ LoadableIconKey *key;
+
+ key = g_slice_new (LoadableIconKey);
+ key->icon = g_object_ref (icon);
+ key->scale = scale;
+ key->size = size;
+
+ return key;
+}
+
+static void
+loadable_icon_key_free (LoadableIconKey *key)
+{
+ g_object_unref (key->icon);
+ g_slice_free (LoadableIconKey, key);
+}
+
+static guint
+themed_icon_key_hash (ThemedIconKey *key)
+{
+ return g_str_hash (key->icon_name) ^ key->size;
+}
+
+static gboolean
+themed_icon_key_equal (const ThemedIconKey *a,
+ const ThemedIconKey *b)
+{
+ return a->size == b->size &&
+ a->scale == b->scale &&
+ g_str_equal (a->icon_name, b->icon_name);
+}
+
+static ThemedIconKey *
+themed_icon_key_new (const char *icon_name,
+ int scale,
+ int size)
+{
+ ThemedIconKey *key;
+
+ key = g_slice_new (ThemedIconKey);
+ key->icon_name = g_strdup (icon_name);
+ key->scale = scale;
+ key->size = size;
+
+ return key;
+}
+
+static void
+themed_icon_key_free (ThemedIconKey *key)
+{
+ g_free (key->icon_name);
+ g_slice_free (ThemedIconKey, key);
+}
+
+NautilusIconInfo *
+nautilus_icon_info_lookup (GIcon *icon,
+ int size,
+ int scale)
+{
+ NautilusIconInfo *icon_info;
+ g_autoptr (GtkIconPaintable) icon_paintable = NULL;
+
+ if (G_IS_LOADABLE_ICON (icon))
+ {
+ g_autoptr (GdkPixbuf) pixbuf = NULL;
+ g_autoptr (GdkPaintable) paintable = NULL;
+ LoadableIconKey lookup_key;
+ LoadableIconKey *key;
+ GInputStream *stream;
+
+ if (loadable_icon_cache == NULL)
+ {
+ loadable_icon_cache =
+ g_hash_table_new_full ((GHashFunc) loadable_icon_key_hash,
+ (GEqualFunc) loadable_icon_key_equal,
+ (GDestroyNotify) loadable_icon_key_free,
+ (GDestroyNotify) g_object_unref);
+ }
+
+ lookup_key.icon = icon;
+ lookup_key.scale = scale;
+ lookup_key.size = size * scale;
+
+ icon_info = g_hash_table_lookup (loadable_icon_cache, &lookup_key);
+ if (icon_info)
+ {
+ return g_object_ref (icon_info);
+ }
+
+ stream = g_loadable_icon_load (G_LOADABLE_ICON (icon),
+ size * scale,
+ NULL, NULL, NULL);
+ if (stream)
+ {
+ pixbuf = gdk_pixbuf_new_from_stream_at_scale (stream,
+ size * scale, size * scale,
+ TRUE,
+ NULL, NULL);
+ g_input_stream_close (stream, NULL, NULL);
+ g_object_unref (stream);
+ }
+
+ if (pixbuf != NULL)
+ {
+ double width = gdk_pixbuf_get_width (pixbuf) / scale;
+ double height = gdk_pixbuf_get_height (pixbuf) / scale;
+ g_autoptr (GdkTexture) texture = gdk_texture_new_for_pixbuf (pixbuf);
+ g_autoptr (GtkSnapshot) snapshot = gtk_snapshot_new ();
+
+ gdk_paintable_snapshot (GDK_PAINTABLE (texture),
+ GDK_SNAPSHOT (snapshot),
+ width, height);
+ paintable = gtk_snapshot_to_paintable (snapshot, NULL);
+ }
+
+ icon_info = nautilus_icon_info_new_for_paintable (paintable, scale);
+
+ key = loadable_icon_key_new (icon, scale, size);
+ g_hash_table_insert (loadable_icon_cache, key, icon_info);
+
+ return g_object_ref (icon_info);
+ }
+
+ icon_paintable = gtk_icon_theme_lookup_by_gicon (gtk_icon_theme_get_for_display (gdk_display_get_default ()),
+ icon, size, scale, GTK_TEXT_DIR_NONE, 0);
+ if (icon_paintable == NULL)
+ {
+ return nautilus_icon_info_new_for_paintable (NULL, scale);
+ }
+
+ if (G_IS_THEMED_ICON (icon))
+ {
+ ThemedIconKey lookup_key;
+ ThemedIconKey *key;
+ const char *icon_name;
+
+ if (themed_icon_cache == NULL)
+ {
+ themed_icon_cache =
+ g_hash_table_new_full ((GHashFunc) themed_icon_key_hash,
+ (GEqualFunc) themed_icon_key_equal,
+ (GDestroyNotify) themed_icon_key_free,
+ (GDestroyNotify) g_object_unref);
+ }
+
+ icon_name = gtk_icon_paintable_get_icon_name (icon_paintable);
+
+ lookup_key.icon_name = (char *) icon_name;
+ lookup_key.scale = scale;
+ lookup_key.size = size;
+
+ icon_info = g_hash_table_lookup (themed_icon_cache, &lookup_key);
+ if (!icon_info)
+ {
+ icon_info = nautilus_icon_info_new_for_icon_paintable (icon_paintable, scale);
+
+ key = themed_icon_key_new (icon_name, scale, size);
+ g_hash_table_insert (themed_icon_cache, key, icon_info);
+ }
+
+ return g_object_ref (icon_info);
+ }
+ else
+ {
+ return nautilus_icon_info_new_for_icon_paintable (icon_paintable, scale);
+ }
+}
+
+static GdkPaintable *
+nautilus_icon_info_get_paintable_nodefault (NautilusIconInfo *icon)
+{
+ GdkPaintable *res;
+
+ if (icon->paintable == NULL)
+ {
+ res = NULL;
+ }
+ else
+ {
+ res = g_object_ref (icon->paintable);
+
+ if (icon->sole_owner)
+ {
+ icon->sole_owner = FALSE;
+ g_object_add_toggle_ref (G_OBJECT (res),
+ paintable_toggle_notify,
+ icon);
+ }
+ }
+
+ return res;
+}
+
+GdkPaintable *
+nautilus_icon_info_get_paintable (NautilusIconInfo *icon)
+{
+ GdkPaintable *res;
+
+ res = nautilus_icon_info_get_paintable_nodefault (icon);
+ if (res == NULL)
+ {
+ res = GDK_PAINTABLE (gdk_texture_new_from_resource ("/org/gnome/nautilus/text-x-preview.png"));
+ }
+
+ return res;
+}
+
+GdkTexture *
+nautilus_icon_info_get_texture (NautilusIconInfo *icon)
+{
+ g_autoptr (GdkPaintable) paintable = NULL;
+ GdkTexture *res;
+
+ paintable = nautilus_icon_info_get_paintable_nodefault (icon);
+ if (GDK_IS_TEXTURE (paintable))
+ {
+ res = GDK_TEXTURE (g_steal_pointer (&paintable));
+ }
+ else
+ {
+ res = gdk_texture_new_from_resource ("/org/gnome/nautilus/text-x-preview.png");
+ }
+
+ return res;
+}
+
+const char *
+nautilus_icon_info_get_used_name (NautilusIconInfo *icon)
+{
+ return icon->icon_name;
+}
diff --git a/src/nautilus-icon-info.h b/src/nautilus-icon-info.h
new file mode 100644
index 0000000..727d3b2
--- /dev/null
+++ b/src/nautilus-icon-info.h
@@ -0,0 +1,29 @@
+#pragma once
+
+#include <glib-object.h>
+#include <gdk-pixbuf/gdk-pixbuf.h>
+#include <gdk/gdk.h>
+#include <gio/gio.h>
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+/* Maximum size of an icon that the icon factory will ever produce */
+#define NAUTILUS_ICON_MAXIMUM_SIZE 320
+
+#define NAUTILUS_TYPE_ICON_INFO (nautilus_icon_info_get_type ())
+G_DECLARE_FINAL_TYPE (NautilusIconInfo, nautilus_icon_info, NAUTILUS, ICON_INFO, GObject)
+
+NautilusIconInfo * nautilus_icon_info_new_for_paintable (GdkPaintable *paintable,
+ int scale);
+NautilusIconInfo * nautilus_icon_info_lookup (GIcon *icon,
+ int size,
+ int scale);
+gboolean nautilus_icon_info_is_fallback (NautilusIconInfo *icon);
+GdkPaintable * nautilus_icon_info_get_paintable (NautilusIconInfo *icon);
+GdkTexture * nautilus_icon_info_get_texture (NautilusIconInfo *icon);
+const char * nautilus_icon_info_get_used_name (NautilusIconInfo *icon);
+
+void nautilus_icon_info_clear_caches (void);
+
+G_END_DECLS
diff --git a/src/nautilus-icon-names.h b/src/nautilus-icon-names.h
new file mode 100644
index 0000000..4e2624c
--- /dev/null
+++ b/src/nautilus-icon-names.h
@@ -0,0 +1,26 @@
+#pragma once
+
+/* Icons for places */
+#define NAUTILUS_ICON_FILESYSTEM "drive-harddisk-symbolic"
+#define NAUTILUS_ICON_FOLDER "folder-symbolic"
+#define NAUTILUS_ICON_FOLDER_REMOTE "folder-remote-symbolic"
+#define NAUTILUS_ICON_HOME "user-home-symbolic"
+
+#define NAUTILUS_ICON_FOLDER_DOCUMENTS "folder-documents-symbolic"
+#define NAUTILUS_ICON_FOLDER_DOWNLOAD "folder-download-symbolic"
+#define NAUTILUS_ICON_FOLDER_MUSIC "folder-music-symbolic"
+#define NAUTILUS_ICON_FOLDER_PICTURES "folder-pictures-symbolic"
+#define NAUTILUS_ICON_FOLDER_PUBLIC_SHARE "folder-publicshare-symbolic"
+#define NAUTILUS_ICON_FOLDER_TEMPLATES "folder-templates-symbolic"
+#define NAUTILUS_ICON_FOLDER_VIDEOS "folder-videos-symbolic"
+
+/* Fullcolor icons */
+#define NAUTILUS_ICON_FULLCOLOR_FOLDER "folder"
+#define NAUTILUS_ICON_FULLCOLOR_FOLDER_REMOTE "folder-remote"
+#define NAUTILUS_ICON_FULLCOLOR_FOLDER_DOCUMENTS "folder-documents"
+#define NAUTILUS_ICON_FULLCOLOR_FOLDER_DOWNLOAD "folder-download"
+#define NAUTILUS_ICON_FULLCOLOR_FOLDER_MUSIC "folder-music"
+#define NAUTILUS_ICON_FULLCOLOR_FOLDER_PICTURES "folder-pictures"
+#define NAUTILUS_ICON_FULLCOLOR_FOLDER_PUBLIC_SHARE "folder-publicshare"
+#define NAUTILUS_ICON_FULLCOLOR_FOLDER_TEMPLATES "folder-templates"
+#define NAUTILUS_ICON_FULLCOLOR_FOLDER_VIDEOS "folder-videos" \ No newline at end of file
diff --git a/src/nautilus-keyfile-metadata.c b/src/nautilus-keyfile-metadata.c
new file mode 100644
index 0000000..76b4fae
--- /dev/null
+++ b/src/nautilus-keyfile-metadata.c
@@ -0,0 +1,343 @@
+/*
+ * Nautilus
+ *
+ * Copyright (C) 2011 Red Hat, Inc.
+ *
+ * Nautilus is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * Nautilus is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; see the file COPYING. If not,
+ * see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Cosimo Cecchi <cosimoc@redhat.com>
+ */
+
+#include <config.h>
+
+#include "nautilus-keyfile-metadata.h"
+
+#include "nautilus-directory-notify.h"
+#include "nautilus-file-private.h"
+#include "nautilus-file-utilities.h"
+
+#include <glib/gstdio.h>
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+
+typedef struct
+{
+ GKeyFile *keyfile;
+ guint save_in_idle_id;
+} KeyfileMetadataData;
+
+static GHashTable *data_hash = NULL;
+
+static KeyfileMetadataData *
+keyfile_metadata_data_new (const char *keyfile_filename)
+{
+ KeyfileMetadataData *data;
+ GKeyFile *retval;
+ GError *error = NULL;
+
+ retval = g_key_file_new ();
+
+ g_key_file_load_from_file (retval,
+ keyfile_filename,
+ G_KEY_FILE_NONE,
+ &error);
+
+ if (error != NULL)
+ {
+ if (!g_error_matches (error,
+ G_FILE_ERROR,
+ G_FILE_ERROR_NOENT))
+ {
+ g_print ("Unable to open the desktop metadata keyfile: %s\n",
+ error->message);
+ }
+
+ g_error_free (error);
+ }
+
+ data = g_slice_new0 (KeyfileMetadataData);
+ data->keyfile = retval;
+
+ return data;
+}
+
+static void
+keyfile_metadata_data_free (KeyfileMetadataData *data)
+{
+ g_key_file_unref (data->keyfile);
+
+ if (data->save_in_idle_id != 0)
+ {
+ g_source_remove (data->save_in_idle_id);
+ }
+
+ g_slice_free (KeyfileMetadataData, data);
+}
+
+static GKeyFile *
+get_keyfile (const char *keyfile_filename)
+{
+ KeyfileMetadataData *data;
+
+ if (data_hash == NULL)
+ {
+ data_hash = g_hash_table_new_full (g_str_hash,
+ g_str_equal,
+ g_free,
+ (GDestroyNotify) keyfile_metadata_data_free);
+ }
+
+ data = g_hash_table_lookup (data_hash, keyfile_filename);
+
+ if (data == NULL)
+ {
+ data = keyfile_metadata_data_new (keyfile_filename);
+
+ g_hash_table_insert (data_hash,
+ g_strdup (keyfile_filename),
+ data);
+ }
+
+ return data->keyfile;
+}
+
+static gboolean
+save_in_idle_cb (const gchar *keyfile_filename)
+{
+ KeyfileMetadataData *data;
+ gchar *contents;
+ gsize length;
+ GError *error = NULL;
+
+ data = g_hash_table_lookup (data_hash, keyfile_filename);
+ data->save_in_idle_id = 0;
+
+ contents = g_key_file_to_data (data->keyfile, &length, NULL);
+
+ if (contents != NULL)
+ {
+ g_file_set_contents (keyfile_filename,
+ contents, length,
+ &error);
+ g_free (contents);
+ }
+
+ if (error != NULL)
+ {
+ g_warning ("Couldn't save the desktop metadata keyfile to disk: %s",
+ error->message);
+ g_error_free (error);
+ }
+
+ return FALSE;
+}
+
+static void
+save_in_idle (const char *keyfile_filename)
+{
+ KeyfileMetadataData *data;
+
+ g_return_if_fail (data_hash != NULL);
+
+ data = g_hash_table_lookup (data_hash, keyfile_filename);
+ g_return_if_fail (data != NULL);
+
+ if (data->save_in_idle_id != 0)
+ {
+ return;
+ }
+
+ data->save_in_idle_id = g_idle_add_full (G_PRIORITY_DEFAULT_IDLE,
+ (GSourceFunc) save_in_idle_cb,
+ g_strdup (keyfile_filename),
+ g_free);
+}
+
+void
+nautilus_keyfile_metadata_set_string (NautilusFile *file,
+ const char *keyfile_filename,
+ const gchar *name,
+ const gchar *key,
+ const gchar *string)
+{
+ GKeyFile *keyfile;
+
+ keyfile = get_keyfile (keyfile_filename);
+
+ g_key_file_set_string (keyfile,
+ name,
+ key,
+ string);
+
+ save_in_idle (keyfile_filename);
+
+ if (nautilus_keyfile_metadata_update_from_keyfile (file, keyfile_filename, name))
+ {
+ nautilus_file_changed (file);
+ }
+}
+
+#define STRV_TERMINATOR "@x-nautilus-desktop-metadata-term@"
+
+void
+nautilus_keyfile_metadata_set_stringv (NautilusFile *file,
+ const char *keyfile_filename,
+ const char *name,
+ const char *key,
+ const char * const *stringv)
+{
+ GKeyFile *keyfile;
+ guint length;
+ gchar **actual_stringv = NULL;
+ gboolean free_strv = FALSE;
+
+ keyfile = get_keyfile (keyfile_filename);
+
+ /* if we would be setting a single-length strv, append a fake
+ * terminator to the array, to be able to differentiate it later from
+ * the single string case
+ */
+ length = g_strv_length ((gchar **) stringv);
+
+ if (length == 1)
+ {
+ actual_stringv = g_malloc0 (3 * sizeof (gchar *));
+ actual_stringv[0] = (gchar *) stringv[0];
+ actual_stringv[1] = STRV_TERMINATOR;
+ actual_stringv[2] = NULL;
+
+ length = 2;
+ free_strv = TRUE;
+ }
+ else
+ {
+ actual_stringv = (gchar **) stringv;
+ }
+
+ g_key_file_set_string_list (keyfile,
+ name,
+ key,
+ (const gchar **) actual_stringv,
+ length);
+
+ save_in_idle (keyfile_filename);
+
+ if (nautilus_keyfile_metadata_update_from_keyfile (file, keyfile_filename, name))
+ {
+ nautilus_file_changed (file);
+ }
+
+ if (free_strv)
+ {
+ g_free (actual_stringv);
+ }
+}
+
+gboolean
+nautilus_keyfile_metadata_update_from_keyfile (NautilusFile *file,
+ const char *keyfile_filename,
+ const gchar *name)
+{
+ gchar **keys, **values;
+ const gchar *actual_values[2];
+ const gchar *key, *value;
+ gchar *gio_key;
+ gsize length, values_length;
+ GKeyFile *keyfile;
+ GFileInfo *info;
+ gint idx;
+ gboolean res;
+
+ keyfile = get_keyfile (keyfile_filename);
+
+ keys = g_key_file_get_keys (keyfile,
+ name,
+ &length,
+ NULL);
+
+ if (keys == NULL)
+ {
+ return FALSE;
+ }
+
+ info = g_file_info_new ();
+
+ for (idx = 0; idx < length; idx++)
+ {
+ key = keys[idx];
+ values = g_key_file_get_string_list (keyfile,
+ name,
+ key,
+ &values_length,
+ NULL);
+
+ gio_key = g_strconcat ("metadata::", key, NULL);
+
+ if (values_length < 1)
+ {
+ continue;
+ }
+ else if (values_length == 1)
+ {
+ g_file_info_set_attribute_string (info,
+ gio_key,
+ values[0]);
+ }
+ else if (values_length == 2)
+ {
+ /* deal with the fact that single-length strv are stored
+ * with an additional terminator in the keyfile string, to differentiate
+ * them from the regular string case.
+ */
+ value = values[1];
+
+ if (g_strcmp0 (value, STRV_TERMINATOR) == 0)
+ {
+ /* if the 2nd value is the terminator, remove it */
+ actual_values[0] = values[0];
+ actual_values[1] = NULL;
+
+ g_file_info_set_attribute_stringv (info,
+ gio_key,
+ (gchar **) actual_values);
+ }
+ else
+ {
+ /* otherwise, set it as a regular strv */
+ g_file_info_set_attribute_stringv (info,
+ gio_key,
+ values);
+ }
+ }
+ else
+ {
+ g_file_info_set_attribute_stringv (info,
+ gio_key,
+ values);
+ }
+
+ g_free (gio_key);
+ g_strfreev (values);
+ }
+
+ res = nautilus_file_update_metadata_from_info (file, info);
+
+ g_strfreev (keys);
+ g_object_unref (info);
+
+ return res;
+}
diff --git a/src/nautilus-keyfile-metadata.h b/src/nautilus-keyfile-metadata.h
new file mode 100644
index 0000000..9f471ca
--- /dev/null
+++ b/src/nautilus-keyfile-metadata.h
@@ -0,0 +1,43 @@
+/*
+ * Nautilus
+ *
+ * Copyright (C) 2011 Red Hat, Inc.
+ *
+ * Nautilus is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * Nautilus is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; see the file COPYING. If not,
+ * see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Cosimo Cecchi <cosimoc@redhat.com>
+ */
+
+#pragma once
+
+#include <glib.h>
+
+#include "nautilus-types.h"
+
+void nautilus_keyfile_metadata_set_string (NautilusFile *file,
+ const char *keyfile_filename,
+ const gchar *name,
+ const gchar *key,
+ const gchar *string);
+
+void nautilus_keyfile_metadata_set_stringv (NautilusFile *file,
+ const char *keyfile_filename,
+ const char *name,
+ const char *key,
+ const char * const *stringv);
+
+gboolean nautilus_keyfile_metadata_update_from_keyfile (NautilusFile *file,
+ const char *keyfile_filename,
+ const gchar *name);
diff --git a/src/nautilus-label-cell.c b/src/nautilus-label-cell.c
new file mode 100644
index 0000000..e8b62df
--- /dev/null
+++ b/src/nautilus-label-cell.c
@@ -0,0 +1,158 @@
+/*
+ * Copyright (C) 2022 António Fernandes <antoniof@gnome.org>
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+/* Needed for NautilusColumn (full GType). */
+#include <nautilus-extension.h>
+
+#include "nautilus-label-cell.h"
+
+struct _NautilusLabelCell
+{
+ NautilusViewCell parent_instance;
+
+ GSignalGroup *item_signal_group;
+
+ NautilusColumn *column;
+ GQuark attribute_q;
+
+ GtkLabel *label;
+
+ gboolean show_snippet;
+};
+
+G_DEFINE_TYPE (NautilusLabelCell, nautilus_label_cell, NAUTILUS_TYPE_VIEW_CELL)
+
+enum
+{
+ PROP_0,
+ PROP_COLUMN,
+ N_PROPS
+};
+
+static GParamSpec *properties[N_PROPS] = { NULL, };
+
+static void
+on_file_changed (NautilusLabelCell *self)
+{
+ g_autoptr (NautilusViewItem) item = NULL;
+ NautilusFile *file;
+ g_autofree gchar *string = NULL;
+
+ item = nautilus_view_cell_get_item (NAUTILUS_VIEW_CELL (self));
+ g_return_if_fail (item != NULL);
+ file = nautilus_view_item_get_file (item);
+
+ string = nautilus_file_get_string_attribute_q (file, self->attribute_q);
+ gtk_label_set_text (self->label, string);
+}
+
+static void
+nautilus_label_cell_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ NautilusLabelCell *self = NAUTILUS_LABEL_CELL (object);
+
+ switch (prop_id)
+ {
+ case PROP_COLUMN:
+ {
+ self->column = g_value_get_object (value);
+ }
+ break;
+
+ default:
+ {
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+ }
+}
+
+static void
+nautilus_label_cell_init (NautilusLabelCell *self)
+{
+ GtkWidget *child;
+
+ child = gtk_label_new (NULL);
+ adw_bin_set_child (ADW_BIN (self), child);
+ gtk_widget_set_valign (child, GTK_ALIGN_CENTER);
+ gtk_widget_add_css_class (child, "dim-label");
+ self->label = GTK_LABEL (child);
+
+ /* Connect automatically to an item. */
+ self->item_signal_group = g_signal_group_new (NAUTILUS_TYPE_VIEW_ITEM);
+ g_signal_group_connect_swapped (self->item_signal_group, "file-changed",
+ (GCallback) on_file_changed, self);
+ g_signal_connect_object (self->item_signal_group, "bind",
+ (GCallback) on_file_changed, self,
+ G_CONNECT_SWAPPED);
+ g_object_bind_property (self, "item",
+ self->item_signal_group, "target",
+ G_BINDING_SYNC_CREATE);
+}
+
+static void
+nautilus_label_cell_constructed (GObject *object)
+{
+ NautilusLabelCell *self = NAUTILUS_LABEL_CELL (object);
+ g_autofree gchar *column_name = NULL;
+ gfloat xalign;
+
+ G_OBJECT_CLASS (nautilus_label_cell_parent_class)->constructed (object);
+
+ g_object_get (self->column,
+ "attribute_q", &self->attribute_q,
+ "name", &column_name,
+ "xalign", &xalign,
+ NULL);
+ gtk_label_set_xalign (self->label, xalign);
+
+ if (g_strcmp0 (column_name, "permissions") == 0)
+ {
+ gtk_widget_add_css_class (GTK_WIDGET (self->label), "monospace");
+ }
+ else
+ {
+ gtk_widget_add_css_class (GTK_WIDGET (self->label), "numeric");
+ }
+}
+
+static void
+nautilus_label_cell_finalize (GObject *object)
+{
+ NautilusLabelCell *self = (NautilusLabelCell *) object;
+
+ g_object_unref (self->item_signal_group);
+ G_OBJECT_CLASS (nautilus_label_cell_parent_class)->finalize (object);
+}
+
+static void
+nautilus_label_cell_class_init (NautilusLabelCellClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->set_property = nautilus_label_cell_set_property;
+ object_class->constructed = nautilus_label_cell_constructed;
+ object_class->finalize = nautilus_label_cell_finalize;
+
+ properties[PROP_COLUMN] = g_param_spec_object ("column",
+ "", "",
+ NAUTILUS_TYPE_COLUMN,
+ G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
+
+ g_object_class_install_properties (object_class, N_PROPS, properties);
+}
+
+NautilusViewCell *
+nautilus_label_cell_new (NautilusListBase *view,
+ NautilusColumn *column)
+{
+ return NAUTILUS_VIEW_CELL (g_object_new (NAUTILUS_TYPE_LABEL_CELL,
+ "view", view,
+ "column", column,
+ NULL));
+}
diff --git a/src/nautilus-label-cell.h b/src/nautilus-label-cell.h
new file mode 100644
index 0000000..b476ef9
--- /dev/null
+++ b/src/nautilus-label-cell.h
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2022 António Fernandes <antoniof@gnome.org>
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#pragma once
+
+/* Needed for NautilusColumn (typedef only). */
+#include "nautilus-types.h"
+
+#include "nautilus-view-cell.h"
+
+G_BEGIN_DECLS
+
+#define NAUTILUS_TYPE_LABEL_CELL (nautilus_label_cell_get_type())
+
+G_DECLARE_FINAL_TYPE (NautilusLabelCell, nautilus_label_cell, NAUTILUS, LABEL_CELL, NautilusViewCell)
+
+NautilusViewCell * nautilus_label_cell_new (NautilusListBase *view,
+ NautilusColumn *column);
+
+G_END_DECLS
diff --git a/src/nautilus-lib-self-check-functions.c b/src/nautilus-lib-self-check-functions.c
new file mode 100644
index 0000000..11f9288
--- /dev/null
+++ b/src/nautilus-lib-self-check-functions.c
@@ -0,0 +1,35 @@
+/*
+ * nautilus-lib-self-check-functions.c: Wrapper for all self check functions
+ * in Nautilus proper.
+ *
+ * Copyright (C) 2000 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/>.
+ *
+ * Author: Darin Adler <darin@bentspoon.com>
+ */
+
+#include <config.h>
+
+#if !defined (NAUTILUS_OMIT_SELF_CHECK)
+
+#include "nautilus-lib-self-check-functions.h"
+
+void
+nautilus_run_lib_self_checks (void)
+{
+ NAUTILUS_LIB_FOR_EACH_SELF_CHECK_FUNCTION (EEL_CALL_SELF_CHECK_FUNCTION)
+}
+
+#endif /* ! NAUTILUS_OMIT_SELF_CHECK */
diff --git a/src/nautilus-lib-self-check-functions.h b/src/nautilus-lib-self-check-functions.h
new file mode 100644
index 0000000..93850c9
--- /dev/null
+++ b/src/nautilus-lib-self-check-functions.h
@@ -0,0 +1,48 @@
+/*
+ nautilus-lib-self-check-functions.h: Wrapper and prototypes for all
+ self-check functions in libnautilus.
+
+ Copyright (C) 2000 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/>.
+
+ Author: Darin Adler <darin@bentspoon.com>
+*/
+
+#pragma once
+
+#include <eel/eel-self-checks.h>
+
+void nautilus_run_lib_self_checks (void);
+
+/* Putting the prototypes for these self-check functions in each
+ header file for the files they are defined in would make compiling
+ the self-check framework take way too long (since one file would
+ have to include everything).
+
+ So we put the list of functions here instead.
+
+ Instead of just putting prototypes here, we put this macro that
+ can be used to do operations on the whole list of functions.
+*/
+
+#define NAUTILUS_LIB_FOR_EACH_SELF_CHECK_FUNCTION(macro) \
+ macro (nautilus_self_check_file_utilities) \
+ macro (nautilus_self_check_file_operations) \
+ macro (nautilus_self_check_directory) \
+ macro (nautilus_self_check_file) \
+/* Add new self-check functions to the list above this line. */
+
+/* Generate prototypes for all the functions. */
+NAUTILUS_LIB_FOR_EACH_SELF_CHECK_FUNCTION (EEL_SELF_CHECK_FUNCTION_PROTOTYPE)
diff --git a/src/nautilus-list-base-private.h b/src/nautilus-list-base-private.h
new file mode 100644
index 0000000..96944d5
--- /dev/null
+++ b/src/nautilus-list-base-private.h
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2022 The GNOME project contributors
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#pragma once
+
+#include "nautilus-list-base.h"
+#include "nautilus-view-model.h"
+#include "nautilus-view-cell.h"
+
+/*
+ * Private header to be included only by subclasses.
+ */
+
+G_BEGIN_DECLS
+
+/* Methods */
+NautilusViewModel *nautilus_list_base_get_model (NautilusListBase *self);
+void nautilus_list_base_set_icon_size (NautilusListBase *self,
+ gint icon_size);
+void nautilus_list_base_setup_gestures (NautilusListBase *self);
+
+/* Shareable helpers */
+void set_directory_sort_metadata (NautilusFile *file,
+ const gchar *metadata_name,
+ gboolean reversed);
+const NautilusFileSortType get_sorts_type_from_metadata_text (const char *metadata_name);
+void setup_cell_common (GtkListItem *listitem,
+ NautilusViewCell *cell);
+
+G_END_DECLS
diff --git a/src/nautilus-list-base.c b/src/nautilus-list-base.c
new file mode 100644
index 0000000..6b8d2f6
--- /dev/null
+++ b/src/nautilus-list-base.c
@@ -0,0 +1,1892 @@
+/*
+ * Copyright (C) 2022 The GNOME project contributors
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#include "nautilus-list-base-private.h"
+
+#include "nautilus-clipboard.h"
+#include "nautilus-dnd.h"
+#include "nautilus-view-cell.h"
+#include "nautilus-view-item.h"
+#include "nautilus-view-model.h"
+#include "nautilus-files-view.h"
+#include "nautilus-files-view-dnd.h"
+#include "nautilus-file.h"
+#include "nautilus-file-operations.h"
+#include "nautilus-metadata.h"
+#include "nautilus-global-preferences.h"
+#include "nautilus-thumbnails.h"
+
+#ifdef GDK_WINDOWING_X11
+#include <gdk/x11/gdkx.h>
+#endif
+
+/**
+ * NautilusListBase:
+ *
+ * Abstract class containing shared code for #NautilusFilesView implementations
+ * using a #GtkListBase-derived widget (e.g. GtkGridView, GtkColumnView) which
+ * takes a #NautilusViewModel instance as its model and and a #NautilusViewCell
+ * instance as #GtkListItem:child.
+ *
+ * It has been has been created to avoid code duplication in implementations,
+ * while keeping #NautilusFilesView implementation-agnostic (should the need for
+ * non-#GtkListBase views arise).
+ */
+
+typedef struct _NautilusListBasePrivate NautilusListBasePrivate;
+struct _NautilusListBasePrivate
+{
+ NautilusViewModel *model;
+
+ GList *cut_files;
+
+ guint scroll_to_file_handle_id;
+ guint prioritize_thumbnailing_handle_id;
+ GtkAdjustment *vadjustment;
+
+ gboolean single_click_mode;
+ gboolean activate_on_release;
+ gboolean deny_background_click;
+
+ GdkDragAction drag_item_action;
+ GdkDragAction drag_view_action;
+ graphene_point_t hover_start_point;
+ guint hover_timer_id;
+ GtkDropTarget *view_drop_target;
+};
+
+G_DEFINE_ABSTRACT_TYPE_WITH_PRIVATE (NautilusListBase, nautilus_list_base, NAUTILUS_TYPE_FILES_VIEW)
+
+typedef struct
+{
+ const NautilusFileSortType sort_type;
+ const gchar *metadata_name;
+} SortConstants;
+
+static const SortConstants sorts_constants[] =
+{
+ {
+ NAUTILUS_FILE_SORT_BY_DISPLAY_NAME,
+ "name",
+ },
+ {
+ NAUTILUS_FILE_SORT_BY_SIZE,
+ "size",
+ },
+ {
+ NAUTILUS_FILE_SORT_BY_TYPE,
+ "type",
+ },
+ {
+ NAUTILUS_FILE_SORT_BY_MTIME,
+ "date_modified",
+ },
+ {
+ NAUTILUS_FILE_SORT_BY_ATIME,
+ "date_accessed",
+ },
+ {
+ NAUTILUS_FILE_SORT_BY_BTIME,
+ "date_created",
+ },
+ {
+ NAUTILUS_FILE_SORT_BY_TRASHED_TIME,
+ "trashed_on",
+ },
+ {
+ NAUTILUS_FILE_SORT_BY_SEARCH_RELEVANCE,
+ "search_relevance",
+ },
+ {
+ NAUTILUS_FILE_SORT_BY_RECENCY,
+ "recency",
+ },
+};
+
+static inline NautilusViewItem *
+get_view_item (GListModel *model,
+ guint position)
+{
+ return NAUTILUS_VIEW_ITEM (g_list_model_get_item (model, position));
+}
+
+static const SortConstants *
+get_sorts_constants_from_sort_type (NautilusFileSortType sort_type)
+{
+ guint i;
+
+ for (i = 0; i < G_N_ELEMENTS (sorts_constants); i++)
+ {
+ if (sort_type == sorts_constants[i].sort_type)
+ {
+ return &sorts_constants[i];
+ }
+ }
+
+ return &sorts_constants[0];
+}
+
+static const SortConstants *
+get_sorts_constants_from_metadata_text (const char *metadata_name)
+{
+ guint i;
+
+ for (i = 0; i < G_N_ELEMENTS (sorts_constants); i++)
+ {
+ if (g_strcmp0 (sorts_constants[i].metadata_name, metadata_name) == 0)
+ {
+ return &sorts_constants[i];
+ }
+ }
+
+ return &sorts_constants[0];
+}
+
+const NautilusFileSortType
+get_sorts_type_from_metadata_text (const char *metadata_name)
+{
+ return get_sorts_constants_from_metadata_text (metadata_name)->sort_type;
+}
+
+static const SortConstants *
+get_default_sort_order (NautilusFile *file,
+ gboolean *reversed)
+{
+ NautilusFileSortType sort_type;
+
+ sort_type = nautilus_file_get_default_sort_type (file, reversed);
+
+ return get_sorts_constants_from_sort_type (sort_type);
+}
+
+static const SortConstants *
+get_directory_sort_by (NautilusFile *file,
+ gboolean *reversed)
+{
+ const SortConstants *default_sort;
+ g_autofree char *sort_by = NULL;
+
+ default_sort = get_default_sort_order (file, reversed);
+ g_return_val_if_fail (default_sort != NULL, NULL);
+
+ if (default_sort->sort_type == NAUTILUS_FILE_SORT_BY_RECENCY ||
+ default_sort->sort_type == NAUTILUS_FILE_SORT_BY_TRASHED_TIME ||
+ default_sort->sort_type == NAUTILUS_FILE_SORT_BY_SEARCH_RELEVANCE)
+ {
+ /* These defaults are important. Ignore metadata. */
+ return default_sort;
+ }
+
+ sort_by = nautilus_file_get_metadata (file,
+ NAUTILUS_METADATA_KEY_ICON_VIEW_SORT_BY,
+ default_sort->metadata_name);
+
+ *reversed = nautilus_file_get_boolean_metadata (file,
+ NAUTILUS_METADATA_KEY_ICON_VIEW_SORT_REVERSED,
+ *reversed);
+
+ return get_sorts_constants_from_metadata_text (sort_by);
+}
+
+void
+set_directory_sort_metadata (NautilusFile *file,
+ const gchar *metadata_name,
+ gboolean reversed)
+{
+ const SortConstants *default_sort;
+ gboolean default_reversed;
+
+ default_sort = get_default_sort_order (file, &default_reversed);
+
+ nautilus_file_set_metadata (file,
+ NAUTILUS_METADATA_KEY_ICON_VIEW_SORT_BY,
+ default_sort->metadata_name,
+ metadata_name);
+ nautilus_file_set_boolean_metadata (file,
+ NAUTILUS_METADATA_KEY_ICON_VIEW_SORT_REVERSED,
+ default_reversed,
+ reversed);
+}
+
+static void
+update_sort_order_from_metadata_and_preferences (NautilusListBase *self)
+{
+ const SortConstants *default_directory_sort;
+ GActionGroup *view_action_group;
+ gboolean reversed;
+
+ default_directory_sort = get_directory_sort_by (nautilus_files_view_get_directory_as_file (NAUTILUS_FILES_VIEW (self)),
+ &reversed);
+ view_action_group = nautilus_files_view_get_action_group (NAUTILUS_FILES_VIEW (self));
+ g_action_group_change_action_state (view_action_group,
+ "sort",
+ g_variant_new ("(sb)",
+ default_directory_sort->metadata_name,
+ reversed));
+}
+
+void
+nautilus_list_base_set_icon_size (NautilusListBase *self,
+ gint icon_size)
+{
+ GListModel *model;
+ guint n_items;
+
+ model = G_LIST_MODEL (nautilus_list_base_get_model (self));
+
+ n_items = g_list_model_get_n_items (model);
+ for (guint i = 0; i < n_items; i++)
+ {
+ g_autoptr (NautilusViewItem) current_item = NULL;
+
+ current_item = get_view_item (model, i);
+ nautilus_view_item_set_icon_size (current_item, icon_size);
+ }
+}
+
+static void
+set_focus_item (NautilusListBase *self,
+ NautilusViewItem *item)
+{
+ NautilusListBasePrivate *priv = nautilus_list_base_get_instance_private (self);
+ GtkWidget *item_widget = nautilus_view_item_get_item_ui (item);
+ GtkWidget *parent;
+
+ if (item_widget == NULL)
+ {
+ /* We can't set the focus if the item isn't created yet. Return early to prevent a crash */
+ return;
+ }
+
+ parent = gtk_widget_get_parent (item_widget);
+
+ if (!gtk_widget_grab_focus (parent))
+ {
+ /* In GtkColumnView, the parent is a cell; its parent is the row. */
+ gtk_widget_grab_focus (gtk_widget_get_parent (parent));
+ }
+
+ /* HACK: Grabbing focus is not enough for the listbase item tracker to
+ * acknowledge it. So, poke the internal actions to fix the bug reported
+ * in https://gitlab.gnome.org/GNOME/nautilus/-/issues/2294 */
+ gtk_widget_activate_action (item_widget,
+ "list.select-item",
+ "(ubb)",
+ nautilus_view_model_get_index (priv->model, item),
+ FALSE, FALSE);
+}
+
+static guint
+nautilus_list_base_get_icon_size (NautilusListBase *self)
+{
+ return NAUTILUS_LIST_BASE_CLASS (G_OBJECT_GET_CLASS (self))->get_icon_size (self);
+}
+
+/* GtkListBase changes selection only with the primary button, and only after
+ * release. But we need to anticipate selection earlier if we are to activate it
+ * or open its context menu. This helper should be used in these situations if
+ * it's desirable to act on a multi-item selection, because it preserves it. */
+static void
+select_single_item_if_not_selected (NautilusListBase *self,
+ NautilusViewItem *item)
+{
+ NautilusViewModel *model;
+ guint position;
+
+ model = nautilus_list_base_get_model (self);
+ position = nautilus_view_model_get_index (model, item);
+ if (!gtk_selection_model_is_selected (GTK_SELECTION_MODEL (model), position))
+ {
+ gtk_selection_model_select_item (GTK_SELECTION_MODEL (model), position, TRUE);
+ set_focus_item (self, item);
+ }
+}
+
+static void
+activate_selection_on_click (NautilusListBase *self,
+ gboolean open_in_new_tab)
+{
+ g_autolist (NautilusFile) selection = NULL;
+ NautilusOpenFlags flags = 0;
+ NautilusFilesView *files_view = NAUTILUS_FILES_VIEW (self);
+
+ selection = nautilus_view_get_selection (NAUTILUS_VIEW (self));
+ if (open_in_new_tab)
+ {
+ flags |= NAUTILUS_OPEN_FLAG_NEW_TAB;
+ flags |= NAUTILUS_OPEN_FLAG_DONT_MAKE_ACTIVE;
+ }
+ nautilus_files_view_activate_files (files_view, selection, flags, TRUE);
+}
+
+static void
+open_context_menu_on_press (NautilusListBase *self,
+ NautilusViewCell *cell,
+ gdouble x,
+ gdouble y)
+{
+ g_autoptr (NautilusViewItem) item = NULL;
+ gdouble view_x, view_y;
+
+ item = nautilus_view_cell_get_item (cell);
+ g_return_if_fail (item != NULL);
+
+ /* Antecipate selection, if necessary. */
+ select_single_item_if_not_selected (self, item);
+
+ gtk_widget_translate_coordinates (GTK_WIDGET (cell), GTK_WIDGET (self),
+ x, y,
+ &view_x, &view_y);
+ nautilus_files_view_pop_up_selection_context_menu (NAUTILUS_FILES_VIEW (self),
+ view_x, view_y);
+}
+
+static void
+rubberband_set_state (NautilusListBase *self,
+ gboolean enabled)
+{
+ /* This is a temporary workaround to deal with the rubberbanding issues
+ * during a drag and drop. Disable rubberband on item press and enable
+ * rubberbnad on item release/stop. The permanent solution for this is
+ * https://gitlab.gnome.org/GNOME/gtk/-/merge_requests/4831 */
+
+ GtkWidget *view;
+
+ view = NAUTILUS_LIST_BASE_CLASS (G_OBJECT_GET_CLASS (self))->get_view_ui (self);
+ if (GTK_IS_GRID_VIEW (view))
+ {
+ gtk_grid_view_set_enable_rubberband (GTK_GRID_VIEW (view), enabled);
+ }
+ else if (GTK_IS_COLUMN_VIEW (view))
+ {
+ gtk_column_view_set_enable_rubberband (GTK_COLUMN_VIEW (view), enabled);
+ }
+}
+
+static void
+on_item_click_pressed (GtkGestureClick *gesture,
+ gint n_press,
+ gdouble x,
+ gdouble y,
+ gpointer user_data)
+{
+ NautilusViewCell *cell = user_data;
+ g_autoptr (NautilusListBase) self = nautilus_view_cell_get_view (cell);
+ NautilusListBasePrivate *priv = nautilus_list_base_get_instance_private (self);
+ guint button;
+ GdkModifierType modifiers;
+ gboolean selection_mode;
+
+ button = gtk_gesture_single_get_current_button (GTK_GESTURE_SINGLE (gesture));
+ modifiers = gtk_event_controller_get_current_event_state (GTK_EVENT_CONTROLLER (gesture));
+ selection_mode = (modifiers & (GDK_CONTROL_MASK | GDK_SHIFT_MASK));
+
+ /* Before anything else, store event state to be read by other handlers. */
+ priv->deny_background_click = TRUE;
+ priv->activate_on_release = (priv->single_click_mode &&
+ button == GDK_BUTTON_PRIMARY &&
+ n_press == 1 &&
+ !selection_mode);
+
+ rubberband_set_state (self, FALSE);
+
+ /* It's safe to claim event sequence on press in the following cases because
+ * they don't interfere with touch scrolling. */
+ if (button == GDK_BUTTON_PRIMARY && n_press == 2 && !priv->single_click_mode)
+ {
+ /* If Ctrl + Shift are held, we don't want to activate selection. But
+ * we still need to claim the event, otherwise GtkListBase's default
+ * gesture is going to trigger activation. */
+ if (!selection_mode)
+ {
+ activate_selection_on_click (self, FALSE);
+ }
+ gtk_gesture_set_state (GTK_GESTURE (gesture), GTK_EVENT_SEQUENCE_CLAIMED);
+ }
+ else if (button == GDK_BUTTON_MIDDLE && n_press == 1)
+ {
+ g_autoptr (NautilusViewItem) item = nautilus_view_cell_get_item (cell);
+ g_return_if_fail (item != NULL);
+
+ /* Anticipate selection, if necessary, to activate it. */
+ select_single_item_if_not_selected (self, item);
+ activate_selection_on_click (self, TRUE);
+ gtk_gesture_set_state (GTK_GESTURE (gesture), GTK_EVENT_SEQUENCE_CLAIMED);
+ }
+ else if (button == GDK_BUTTON_SECONDARY && n_press == 1)
+ {
+ open_context_menu_on_press (self, cell, x, y);
+ gtk_gesture_set_state (GTK_GESTURE (gesture), GTK_EVENT_SEQUENCE_CLAIMED);
+ }
+}
+
+static void
+on_item_click_released (GtkGestureClick *gesture,
+ gint n_press,
+ gdouble x,
+ gdouble y,
+ gpointer user_data)
+{
+ NautilusViewCell *cell = user_data;
+ g_autoptr (NautilusListBase) self = nautilus_view_cell_get_view (cell);
+ NautilusListBasePrivate *priv = nautilus_list_base_get_instance_private (self);
+
+ if (priv->activate_on_release)
+ {
+ NautilusViewModel *model;
+ g_autoptr (NautilusViewItem) item = NULL;
+ guint i;
+
+ model = nautilus_list_base_get_model (self);
+ item = nautilus_view_cell_get_item (cell);
+ g_return_if_fail (item != NULL);
+ i = nautilus_view_model_get_index (model, item);
+
+ /* Anticipate selection, enforcing single selection of target item. */
+ gtk_selection_model_select_item (GTK_SELECTION_MODEL (model), i, TRUE);
+
+ activate_selection_on_click (self, FALSE);
+ }
+
+ rubberband_set_state (self, TRUE);
+ priv->activate_on_release = FALSE;
+ priv->deny_background_click = FALSE;
+}
+
+static void
+on_item_click_stopped (GtkGestureClick *gesture,
+ gpointer user_data)
+{
+ NautilusViewCell *cell = user_data;
+ g_autoptr (NautilusListBase) self = nautilus_view_cell_get_view (cell);
+ NautilusListBasePrivate *priv = nautilus_list_base_get_instance_private (self);
+
+ rubberband_set_state (self, TRUE);
+ priv->activate_on_release = FALSE;
+ priv->deny_background_click = FALSE;
+}
+
+static void
+on_view_click_pressed (GtkGestureClick *gesture,
+ gint n_press,
+ gdouble x,
+ gdouble y,
+ gpointer user_data)
+{
+ NautilusListBase *self = user_data;
+ NautilusListBasePrivate *priv = nautilus_list_base_get_instance_private (self);
+ guint button;
+ GdkModifierType modifiers;
+ gboolean selection_mode;
+
+ if (priv->deny_background_click)
+ {
+ /* Item was clicked. */
+ gtk_gesture_set_state (GTK_GESTURE (gesture), GTK_EVENT_SEQUENCE_DENIED);
+ return;
+ }
+
+ /* We are overriding many of the gestures for the views so let's make sure to
+ * grab the focus in order to make rubberbanding and background click work */
+ gtk_widget_grab_focus (GTK_WIDGET (self));
+
+ /* Don't interfere with GtkListBase default selection handling when
+ * holding Ctrl and Shift. */
+ modifiers = gtk_event_controller_get_current_event_state (GTK_EVENT_CONTROLLER (gesture));
+ selection_mode = (modifiers & (GDK_CONTROL_MASK | GDK_SHIFT_MASK));
+ if (!selection_mode)
+ {
+ nautilus_view_set_selection (NAUTILUS_VIEW (self), NULL);
+ }
+
+ button = gtk_gesture_single_get_current_button (GTK_GESTURE_SINGLE (gesture));
+ if (button == GDK_BUTTON_SECONDARY)
+ {
+ GtkWidget *event_widget;
+ gdouble view_x;
+ gdouble view_y;
+
+ event_widget = gtk_event_controller_get_widget (GTK_EVENT_CONTROLLER (gesture));
+ gtk_widget_translate_coordinates (event_widget, GTK_WIDGET (self),
+ x, y,
+ &view_x, &view_y);
+ nautilus_files_view_pop_up_background_context_menu (NAUTILUS_FILES_VIEW (self),
+ view_x, view_y);
+ }
+}
+
+static void
+on_item_longpress_pressed (GtkGestureLongPress *gesture,
+ gdouble x,
+ gdouble y,
+ gpointer user_data)
+{
+ NautilusViewCell *cell = user_data;
+ g_autoptr (NautilusListBase) self = nautilus_view_cell_get_view (cell);
+
+ open_context_menu_on_press (self, cell, x, y);
+ gtk_gesture_set_state (GTK_GESTURE (gesture), GTK_EVENT_SEQUENCE_CLAIMED);
+}
+
+static void
+on_view_longpress_pressed (GtkGestureLongPress *gesture,
+ gdouble x,
+ gdouble y,
+ gpointer user_data)
+{
+ NautilusListBase *self = NAUTILUS_LIST_BASE (user_data);
+ GtkWidget *event_widget;
+ gdouble view_x;
+ gdouble view_y;
+
+ event_widget = gtk_event_controller_get_widget (GTK_EVENT_CONTROLLER (gesture));
+
+ gtk_widget_translate_coordinates (event_widget,
+ GTK_WIDGET (self),
+ x, y, &view_x, &view_y);
+
+ nautilus_view_set_selection (NAUTILUS_VIEW (self), NULL);
+ nautilus_files_view_pop_up_background_context_menu (NAUTILUS_FILES_VIEW (self),
+ view_x, view_y);
+}
+
+static GdkContentProvider *
+on_item_drag_prepare (GtkDragSource *source,
+ double x,
+ double y,
+ gpointer user_data)
+{
+ NautilusViewCell *cell = user_data;
+ g_autoptr (NautilusListBase) self = nautilus_view_cell_get_view (cell);
+ GtkWidget *view_ui;
+ g_autolist (NautilusFile) selection = NULL;
+ g_autoslist (GFile) file_list = NULL;
+ g_autoptr (GdkPaintable) paintable = NULL;
+ g_autoptr (NautilusViewItem) item = nautilus_view_cell_get_item (cell);
+ GdkDragAction actions;
+ gint scale_factor;
+
+ /* Anticipate selection, if necessary, for dragging the clicked item. */
+ select_single_item_if_not_selected (self, item);
+
+ selection = nautilus_view_get_selection (NAUTILUS_VIEW (self));
+ g_return_val_if_fail (selection != NULL, NULL);
+
+ gtk_gesture_set_state (GTK_GESTURE (source), GTK_EVENT_SEQUENCE_CLAIMED);
+
+ actions = GDK_ACTION_ALL | GDK_ACTION_ASK;
+
+ for (GList *l = selection; l != NULL; l = l->next)
+ {
+ /* Convert to GTK_TYPE_FILE_LIST, which is assumed to be a GSList<GFile>. */
+ file_list = g_slist_prepend (file_list, nautilus_file_get_activation_location (l->data));
+
+ if (!nautilus_file_can_delete (l->data))
+ {
+ actions &= ~GDK_ACTION_MOVE;
+ }
+ }
+ file_list = g_slist_reverse (file_list);
+
+ gtk_drag_source_set_actions (source, actions);
+
+ scale_factor = gtk_widget_get_scale_factor (GTK_WIDGET (self));
+ paintable = get_paintable_for_drag_selection (selection, scale_factor);
+
+ view_ui = NAUTILUS_LIST_BASE_CLASS (G_OBJECT_GET_CLASS (self))->get_view_ui (self);
+ if (GTK_IS_GRID_VIEW (view_ui))
+ {
+ x = x * NAUTILUS_DRAG_SURFACE_ICON_SIZE / nautilus_list_base_get_icon_size (self);
+ y = y * NAUTILUS_DRAG_SURFACE_ICON_SIZE / nautilus_list_base_get_icon_size (self);
+ }
+ else
+ {
+ x = 0;
+ y = 0;
+ }
+
+ gtk_drag_source_set_icon (source, paintable, x, y);
+
+ return gdk_content_provider_new_typed (GDK_TYPE_FILE_LIST, file_list);
+}
+
+static gboolean
+hover_timer (gpointer user_data)
+{
+ NautilusViewCell *cell = user_data;
+ g_autoptr (NautilusListBase) self = nautilus_view_cell_get_view (cell);
+ NautilusListBasePrivate *priv = nautilus_list_base_get_instance_private (self);
+ g_autoptr (NautilusViewItem) item = nautilus_view_cell_get_item (cell);
+ g_autofree gchar *uri = NULL;
+
+ priv->hover_timer_id = 0;
+
+ if (priv->drag_item_action == 0)
+ {
+ /* If we aren't able to dropped don't change the location. This stops
+ * drops onto themselves, and another unnecessary drops. */
+ return G_SOURCE_REMOVE;
+ }
+
+ uri = nautilus_file_get_uri (nautilus_view_item_get_file (item));
+ nautilus_files_view_handle_hover (NAUTILUS_FILES_VIEW (self), uri);
+
+ return G_SOURCE_REMOVE;
+}
+
+static void
+on_item_drag_hover_enter (GtkDropControllerMotion *controller,
+ gdouble x,
+ gdouble y,
+ gpointer user_data)
+{
+ NautilusViewCell *cell = user_data;
+ g_autoptr (NautilusListBase) self = nautilus_view_cell_get_view (cell);
+ NautilusListBasePrivate *priv = nautilus_list_base_get_instance_private (self);
+
+ priv->hover_start_point.x = x;
+ priv->hover_start_point.y = y;
+}
+
+static void
+on_item_drag_hover_leave (GtkDropControllerMotion *controller,
+ gpointer user_data)
+{
+ NautilusViewCell *cell = user_data;
+ g_autoptr (NautilusListBase) self = nautilus_view_cell_get_view (cell);
+ NautilusListBasePrivate *priv = nautilus_list_base_get_instance_private (self);
+
+ g_clear_handle_id (&priv->hover_timer_id, g_source_remove);
+}
+
+static void
+on_item_drag_hover_motion (GtkDropControllerMotion *controller,
+ gdouble x,
+ gdouble y,
+ gpointer user_data)
+{
+ NautilusViewCell *cell = user_data;
+ g_autoptr (NautilusListBase) self = nautilus_view_cell_get_view (cell);
+ NautilusListBasePrivate *priv = nautilus_list_base_get_instance_private (self);
+ graphene_point_t start = priv->hover_start_point;
+
+ /* This condition doubles in two roles:
+ * - If the timeout hasn't started yet, to ensure the pointer has entered
+ * deep enough into the cell before starting the timeout to switch;
+ * - If the timeout has already started, to reset it if the pointer is
+ * moving a lot.
+ * Both serve to prevent accidental triggering of switch-on-hover. */
+ if (gtk_drag_check_threshold (GTK_WIDGET (cell), start.x, start.y, x, y))
+ {
+ g_clear_handle_id (&priv->hover_timer_id, g_source_remove);
+ priv->hover_timer_id = g_timeout_add (HOVER_TIMEOUT, hover_timer, cell);
+ priv->hover_start_point.x = x;
+ priv->hover_start_point.y = y;
+ }
+}
+
+static GdkDragAction
+get_preferred_action (NautilusFile *target_file,
+ const GValue *value)
+{
+ GdkDragAction action = 0;
+
+ if (value == NULL)
+ {
+ action = nautilus_dnd_get_preferred_action (target_file, NULL);
+ }
+ else if (G_VALUE_HOLDS (value, GDK_TYPE_FILE_LIST))
+ {
+ GSList *source_file_list = g_value_get_boxed (value);
+ if (source_file_list != NULL)
+ {
+ action = nautilus_dnd_get_preferred_action (target_file, source_file_list->data);
+ }
+ else
+ {
+ action = nautilus_dnd_get_preferred_action (target_file, NULL);
+ }
+ }
+ else if (G_VALUE_HOLDS (value, G_TYPE_STRING))
+ {
+ action = GDK_ACTION_COPY;
+ }
+
+ return action;
+}
+
+static void
+real_perform_drop (NautilusListBase *self,
+ const GValue *value,
+ GdkDragAction action,
+ GFile *target_location)
+{
+ g_autofree gchar *target_uri = g_file_get_uri (target_location);
+
+ if (!gdk_drag_action_is_unique (action))
+ {
+ /* TODO: Implement */
+ }
+ else if (G_VALUE_HOLDS (value, G_TYPE_STRING))
+ {
+ nautilus_files_view_handle_text_drop (NAUTILUS_FILES_VIEW (self),
+ g_value_get_string (value),
+ target_uri, action);
+ }
+ else if (G_VALUE_HOLDS (value, GDK_TYPE_FILE_LIST))
+ {
+ GSList *source_file_list = g_value_get_boxed (value);
+ GList *source_uri_list = NULL;
+
+ for (GSList *l = source_file_list; l != NULL; l = l->next)
+ {
+ source_uri_list = g_list_prepend (source_uri_list, g_file_get_uri (l->data));
+ }
+ source_uri_list = g_list_reverse (source_uri_list);
+
+ nautilus_files_view_drop_proxy_received_uris (NAUTILUS_FILES_VIEW (self),
+ source_uri_list,
+ target_uri,
+ action);
+ g_list_free_full (source_uri_list, g_free);
+ }
+}
+
+static GdkDragAction
+on_item_drag_enter (GtkDropTarget *target,
+ double x,
+ double y,
+ gpointer user_data)
+{
+ NautilusViewCell *cell = user_data;
+ g_autoptr (NautilusListBase) self = nautilus_view_cell_get_view (cell);
+ NautilusListBasePrivate *priv = nautilus_list_base_get_instance_private (self);
+ g_autoptr (NautilusViewItem) item = NULL;
+ const GValue *value;
+ g_autoptr (NautilusFile) dest_file = NULL;
+
+ /* Reset action cache. */
+ priv->drag_item_action = 0;
+
+ item = nautilus_view_cell_get_item (cell);
+ if (item == NULL)
+ {
+ gtk_drop_target_reject (target);
+ return 0;
+ }
+
+ dest_file = nautilus_file_ref (nautilus_view_item_get_file (item));
+
+ if (!nautilus_file_is_archive (dest_file) && !nautilus_file_is_directory (dest_file))
+ {
+ gtk_drop_target_reject (target);
+ return 0;
+ }
+
+ value = gtk_drop_target_get_value (target);
+ priv->drag_item_action = get_preferred_action (dest_file, value);
+ if (priv->drag_item_action == 0)
+ {
+ gtk_drop_target_reject (target);
+ return 0;
+ }
+
+ nautilus_view_item_set_drag_accept (item, TRUE);
+ return priv->drag_item_action;
+}
+
+static void
+on_item_drag_value_notify (GObject *object,
+ GParamSpec *pspec,
+ gpointer user_data)
+{
+ GtkDropTarget *target = GTK_DROP_TARGET (object);
+ NautilusViewCell *cell = user_data;
+ g_autoptr (NautilusListBase) self = nautilus_view_cell_get_view (cell);
+ NautilusListBasePrivate *priv = nautilus_list_base_get_instance_private (self);
+ const GValue *value;
+ g_autoptr (NautilusViewItem) item = NULL;
+
+ value = gtk_drop_target_get_value (target);
+ if (value == NULL)
+ {
+ return;
+ }
+
+ item = nautilus_view_cell_get_item (cell);
+ g_return_if_fail (NAUTILUS_IS_VIEW_ITEM (item));
+
+ priv->drag_item_action = get_preferred_action (nautilus_view_item_get_file (item), value);
+}
+
+static GdkDragAction
+on_item_drag_motion (GtkDropTarget *target,
+ double x,
+ double y,
+ gpointer user_data)
+{
+ NautilusViewCell *cell = user_data;
+ g_autoptr (NautilusListBase) self = nautilus_view_cell_get_view (cell);
+ NautilusListBasePrivate *priv = nautilus_list_base_get_instance_private (self);
+
+ /* There's a bug in GtkDropTarget where motion overrides enter
+ * so until we fix that let's just return the action that we already
+ * received from enter*/
+
+ return priv->drag_item_action;
+}
+
+static void
+on_item_drag_leave (GtkDropTarget *dest,
+ gpointer user_data)
+{
+ NautilusViewCell *cell = user_data;
+ g_autoptr (NautilusViewItem) item = nautilus_view_cell_get_item (cell);
+
+ nautilus_view_item_set_drag_accept (item, FALSE);
+}
+
+static gboolean
+on_item_drop (GtkDropTarget *target,
+ const GValue *value,
+ double x,
+ double y,
+ gpointer user_data)
+{
+ NautilusViewCell *cell = user_data;
+ g_autoptr (NautilusListBase) self = nautilus_view_cell_get_view (cell);
+ NautilusListBasePrivate *priv = nautilus_list_base_get_instance_private (self);
+ g_autoptr (NautilusViewItem) item = nautilus_view_cell_get_item (cell);
+ GdkDragAction actions;
+ GFile *target_location;
+
+ actions = gdk_drop_get_actions (gtk_drop_target_get_current_drop (target));
+ target_location = nautilus_file_get_location (nautilus_view_item_get_file (item));
+
+ #ifdef GDK_WINDOWING_X11
+ if (GDK_IS_X11_DISPLAY (gtk_widget_get_display (GTK_WIDGET (self))))
+ {
+ /* Temporary workaround until the below GTK MR (or equivalend fix)
+ * is merged. Without this fix, the preferred action isn't set correctly.
+ * https://gitlab.gnome.org/GNOME/gtk/-/merge_requests/4982 */
+ GdkDrag *drag = gdk_drop_get_drag (gtk_drop_target_get_current_drop (target));
+ actions = drag != NULL ? gdk_drag_get_selected_action (drag) : GDK_ACTION_COPY;
+ }
+ #endif
+
+ /* In x11 the leave signal isn't emitted on a drop so we need to clear the timeout */
+ g_clear_handle_id (&priv->hover_timer_id, g_source_remove);
+
+ real_perform_drop (self, value, actions, target_location);
+
+ return TRUE;
+}
+
+static GdkDragAction
+on_view_drag_enter (GtkDropTarget *target,
+ double x,
+ double y,
+ gpointer user_data)
+{
+ NautilusListBase *self = user_data;
+ NautilusListBasePrivate *priv = nautilus_list_base_get_instance_private (self);
+ NautilusFile *dest_file;
+ const GValue *value;
+
+ dest_file = nautilus_files_view_get_directory_as_file (NAUTILUS_FILES_VIEW (self));
+ value = gtk_drop_target_get_value (target);
+ priv->drag_view_action = get_preferred_action (dest_file, value);
+ if (priv->drag_view_action == 0)
+ {
+ /* Don't summarily reject because the view's location might change on
+ * hover, so a DND action may become available. */
+ return 0;
+ }
+
+ return priv->drag_view_action;
+}
+
+static void
+on_view_drag_value_notify (GObject *object,
+ GParamSpec *pspec,
+ gpointer user_data)
+{
+ GtkDropTarget *target = GTK_DROP_TARGET (object);
+ NautilusListBase *self = user_data;
+ NautilusListBasePrivate *priv = nautilus_list_base_get_instance_private (self);
+ const GValue *value;
+ NautilusFile *dest_file;
+
+ value = gtk_drop_target_get_value (target);
+ if (value == NULL)
+ {
+ return;
+ }
+
+ dest_file = nautilus_files_view_get_directory_as_file (NAUTILUS_FILES_VIEW (self));
+ priv->drag_view_action = get_preferred_action (dest_file, value);
+}
+
+static GdkDragAction
+on_view_drag_motion (GtkDropTarget *target,
+ double x,
+ double y,
+ gpointer user_data)
+{
+ NautilusListBase *self = user_data;
+ NautilusListBasePrivate *priv = nautilus_list_base_get_instance_private (self);
+
+ return priv->drag_view_action;
+}
+
+static gboolean
+on_view_drop (GtkDropTarget *target,
+ const GValue *value,
+ double x,
+ double y,
+ gpointer user_data)
+{
+ NautilusListBase *self = user_data;
+ NautilusListBasePrivate *priv = nautilus_list_base_get_instance_private (self);
+ GdkDragAction actions;
+ GFile *target_location;
+
+ if (priv->drag_view_action == 0)
+ {
+ /* We didn't reject earlier because the view's location may change and,
+ * as a result, a drop action might become available. */
+ return FALSE;
+ }
+
+ actions = gdk_drop_get_actions (gtk_drop_target_get_current_drop (target));
+ target_location = nautilus_view_get_location (NAUTILUS_VIEW (self));
+
+ #ifdef GDK_WINDOWING_X11
+ if (GDK_IS_X11_DISPLAY (gtk_widget_get_display (GTK_WIDGET (self))))
+ {
+ /* Temporary workaround until the below GTK MR (or equivalend fix)
+ * is merged. Without this fix, the preferred action isn't set correctly.
+ * https://gitlab.gnome.org/GNOME/gtk/-/merge_requests/4982 */
+ GdkDrag *drag = gdk_drop_get_drag (gtk_drop_target_get_current_drop (target));
+ actions = drag != NULL ? gdk_drag_get_selected_action (drag) : GDK_ACTION_COPY;
+ }
+ #endif
+
+ real_perform_drop (self, value, actions, target_location);
+
+ return TRUE;
+}
+
+void
+setup_cell_common (GtkListItem *listitem,
+ NautilusViewCell *cell)
+{
+ GtkEventController *controller;
+ GtkDropTarget *drop_target;
+
+ g_object_bind_property (listitem, "item",
+ cell, "item",
+ G_BINDING_SYNC_CREATE);
+ gtk_list_item_set_child (listitem, GTK_WIDGET (cell));
+
+ controller = GTK_EVENT_CONTROLLER (gtk_gesture_click_new ());
+ gtk_widget_add_controller (GTK_WIDGET (cell), controller);
+ gtk_event_controller_set_propagation_phase (controller, GTK_PHASE_BUBBLE);
+ gtk_gesture_single_set_button (GTK_GESTURE_SINGLE (controller), 0);
+ g_signal_connect (controller, "pressed", G_CALLBACK (on_item_click_pressed), cell);
+ g_signal_connect (controller, "stopped", G_CALLBACK (on_item_click_stopped), cell);
+ g_signal_connect (controller, "released", G_CALLBACK (on_item_click_released), cell);
+
+ controller = GTK_EVENT_CONTROLLER (gtk_gesture_long_press_new ());
+ gtk_widget_add_controller (GTK_WIDGET (cell), controller);
+ gtk_event_controller_set_propagation_phase (controller, GTK_PHASE_BUBBLE);
+ gtk_gesture_single_set_touch_only (GTK_GESTURE_SINGLE (controller), TRUE);
+ g_signal_connect (controller, "pressed", G_CALLBACK (on_item_longpress_pressed), cell);
+
+ controller = GTK_EVENT_CONTROLLER (gtk_drag_source_new ());
+ gtk_widget_add_controller (GTK_WIDGET (cell), controller);
+ gtk_event_controller_set_propagation_phase (controller, GTK_PHASE_CAPTURE);
+ g_signal_connect (controller, "prepare", G_CALLBACK (on_item_drag_prepare), cell);
+
+ /* TODO: Implement GDK_ACTION_ASK */
+ drop_target = gtk_drop_target_new (G_TYPE_INVALID, GDK_ACTION_ALL);
+ gtk_drop_target_set_preload (drop_target, TRUE);
+ /* TODO: Implement GDK_TYPE_STRING */
+ gtk_drop_target_set_gtypes (drop_target, (GType[2]) { GDK_TYPE_FILE_LIST, G_TYPE_STRING }, 2);
+ g_signal_connect (drop_target, "enter", G_CALLBACK (on_item_drag_enter), cell);
+ g_signal_connect (drop_target, "notify::value", G_CALLBACK (on_item_drag_value_notify), cell);
+ g_signal_connect (drop_target, "leave", G_CALLBACK (on_item_drag_leave), cell);
+ g_signal_connect (drop_target, "motion", G_CALLBACK (on_item_drag_motion), cell);
+ g_signal_connect (drop_target, "drop", G_CALLBACK (on_item_drop), cell);
+ gtk_widget_add_controller (GTK_WIDGET (cell), GTK_EVENT_CONTROLLER (drop_target));
+
+ controller = gtk_drop_controller_motion_new ();
+ gtk_widget_add_controller (GTK_WIDGET (cell), controller);
+ g_signal_connect (controller, "enter", G_CALLBACK (on_item_drag_hover_enter), cell);
+ g_signal_connect (controller, "leave", G_CALLBACK (on_item_drag_hover_leave), cell);
+ g_signal_connect (controller, "motion", G_CALLBACK (on_item_drag_hover_motion), cell);
+}
+
+static void
+nautilus_list_base_scroll_to_item (NautilusListBase *self,
+ guint position)
+{
+ NAUTILUS_LIST_BASE_CLASS (G_OBJECT_GET_CLASS (self))->scroll_to_item (self, position);
+}
+
+static GtkWidget *
+nautilus_list_base_get_view_ui (NautilusListBase *self)
+{
+ return NAUTILUS_LIST_BASE_CLASS (G_OBJECT_GET_CLASS (self))->get_view_ui (self);
+}
+
+typedef struct
+{
+ NautilusListBase *self;
+ GQuark attribute_q;
+} NautilusListBaseSortData;
+
+static void
+real_begin_loading (NautilusFilesView *files_view)
+{
+ NautilusListBase *self = NAUTILUS_LIST_BASE (files_view);
+ NautilusListBasePrivate *priv = nautilus_list_base_get_instance_private (self);
+
+ /* Temporary workaround */
+ rubberband_set_state (self, TRUE);
+
+ /*TODO move this to the files view class begin_loading and hook up? */
+
+
+ /* TODO: This calls sort once, and update_context_menus calls update_actions
+ * which calls the action again
+ */
+ update_sort_order_from_metadata_and_preferences (NAUTILUS_LIST_BASE (files_view));
+
+ /* 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 (files_view);
+ nautilus_files_view_update_toolbar_menus (files_view);
+
+ /* When double clicking on an item this deny_background_click can persist
+ * because the new view interrupts the gesture sequence, so lets reset it.*/
+ priv->deny_background_click = FALSE;
+
+ /* When DnD is used to navigate between directories, the normal callbacks
+ * are ignored. Update DnD variables here upon navigating to a directory*/
+ if (gtk_drop_target_get_current_drop (priv->view_drop_target) != NULL)
+ {
+ priv->drag_view_action = get_preferred_action (nautilus_files_view_get_directory_as_file (files_view),
+ gtk_drop_target_get_value (priv->view_drop_target));
+ priv->drag_item_action = 0;
+ }
+}
+
+static void
+real_clear (NautilusFilesView *files_view)
+{
+ NautilusListBase *self = NAUTILUS_LIST_BASE (files_view);
+ NautilusListBasePrivate *priv = nautilus_list_base_get_instance_private (self);
+
+ g_clear_handle_id (&priv->scroll_to_file_handle_id, g_source_remove);
+ nautilus_view_model_remove_all_items (priv->model);
+}
+
+static void
+set_click_mode_from_settings (NautilusListBase *self)
+{
+ NautilusListBasePrivate *priv = nautilus_list_base_get_instance_private (self);
+ int click_policy;
+
+ click_policy = g_settings_get_enum (nautilus_preferences,
+ NAUTILUS_PREFERENCES_CLICK_POLICY);
+
+ priv->single_click_mode = (click_policy == NAUTILUS_CLICK_POLICY_SINGLE);
+}
+
+static void
+real_click_policy_changed (NautilusFilesView *files_view)
+{
+ set_click_mode_from_settings (NAUTILUS_LIST_BASE (files_view));
+}
+
+static void
+real_file_changed (NautilusFilesView *files_view,
+ NautilusFile *file,
+ NautilusDirectory *directory)
+{
+ NautilusListBase *self = NAUTILUS_LIST_BASE (files_view);
+ NautilusListBasePrivate *priv = nautilus_list_base_get_instance_private (self);
+ NautilusViewItem *item;
+
+ item = nautilus_view_model_get_item_from_file (priv->model, file);
+ nautilus_view_item_file_changed (item);
+}
+
+static GList *
+real_get_selection (NautilusFilesView *files_view)
+{
+ NautilusListBase *self = NAUTILUS_LIST_BASE (files_view);
+ NautilusListBasePrivate *priv = nautilus_list_base_get_instance_private (self);
+ g_autoptr (GtkSelectionFilterModel) selection = NULL;
+ guint n_selected;
+ GList *selected_files = NULL;
+
+ selection = gtk_selection_filter_model_new (GTK_SELECTION_MODEL (priv->model));
+ n_selected = g_list_model_get_n_items (G_LIST_MODEL (selection));
+ for (guint i = 0; i < n_selected; i++)
+ {
+ g_autoptr (NautilusViewItem) item = NULL;
+
+ item = get_view_item (G_LIST_MODEL (selection), i);
+ selected_files = g_list_prepend (selected_files,
+ g_object_ref (nautilus_view_item_get_file (item)));
+ }
+
+ selected_files = g_list_reverse (selected_files);
+
+ return selected_files;
+}
+
+static gboolean
+real_is_empty (NautilusFilesView *files_view)
+{
+ NautilusListBase *self = NAUTILUS_LIST_BASE (files_view);
+ NautilusListBasePrivate *priv = nautilus_list_base_get_instance_private (self);
+
+ return g_list_model_get_n_items (G_LIST_MODEL (priv->model)) == 0;
+}
+
+static void
+real_end_file_changes (NautilusFilesView *files_view)
+{
+}
+
+static void
+real_remove_file (NautilusFilesView *files_view,
+ NautilusFile *file,
+ NautilusDirectory *directory)
+{
+ NautilusListBase *self = NAUTILUS_LIST_BASE (files_view);
+ NautilusListBasePrivate *priv = nautilus_list_base_get_instance_private (self);
+ NautilusViewItem *item;
+
+ item = nautilus_view_model_get_item_from_file (priv->model, file);
+ if (item != NULL)
+ {
+ nautilus_view_model_remove_item (priv->model, item);
+ nautilus_files_view_notify_selection_changed (files_view);
+ }
+}
+
+static GQueue *
+convert_glist_to_queue (GList *list)
+{
+ GList *l;
+ GQueue *queue;
+
+ queue = g_queue_new ();
+ for (l = list; l != NULL; l = l->next)
+ {
+ g_queue_push_tail (queue, l->data);
+ }
+
+ return queue;
+}
+
+static GQueue *
+convert_files_to_items (NautilusListBase *self,
+ GQueue *files)
+{
+ GList *l;
+ GQueue *models;
+
+ models = g_queue_new ();
+ for (l = g_queue_peek_head_link (files); l != NULL; l = l->next)
+ {
+ NautilusViewItem *item;
+
+ item = nautilus_view_item_new (NAUTILUS_FILE (l->data),
+ nautilus_list_base_get_icon_size (self));
+ g_queue_push_tail (models, item);
+ }
+
+ return models;
+}
+
+static void
+real_set_selection (NautilusFilesView *files_view,
+ GList *selection)
+{
+ NautilusListBase *self = NAUTILUS_LIST_BASE (files_view);
+ NautilusListBasePrivate *priv = nautilus_list_base_get_instance_private (self);
+ g_autoptr (GQueue) selection_files = NULL;
+ g_autoptr (GQueue) selection_items = NULL;
+ g_autoptr (GtkBitset) update_set = NULL;
+ g_autoptr (GtkBitset) new_selection_set = NULL;
+ g_autoptr (GtkBitset) old_selection_set = NULL;
+
+ old_selection_set = gtk_selection_model_get_selection (GTK_SELECTION_MODEL (priv->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 */
+ selection_files = convert_glist_to_queue (selection);
+ selection_items = nautilus_view_model_get_items_from_files (priv->model, selection_files);
+ for (GList *l = g_queue_peek_head_link (selection_items); l != NULL; l = l->next)
+ {
+ gtk_bitset_add (new_selection_set,
+ nautilus_view_model_get_index (priv->model, l->data));
+ }
+
+ /* Set focus on the first selected row. */
+ if (!g_queue_is_empty (selection_items))
+ {
+ NautilusViewItem *item = g_queue_peek_head (selection_items);
+ set_focus_item (self, item);
+ }
+
+ gtk_bitset_union (update_set, new_selection_set);
+ gtk_selection_model_set_selection (GTK_SELECTION_MODEL (priv->model),
+ new_selection_set,
+ update_set);
+}
+
+static void
+real_select_all (NautilusFilesView *files_view)
+{
+ NautilusListBase *self = NAUTILUS_LIST_BASE (files_view);
+ NautilusListBasePrivate *priv = nautilus_list_base_get_instance_private (self);
+
+ gtk_selection_model_select_all (GTK_SELECTION_MODEL (priv->model));
+}
+
+static void
+real_invert_selection (NautilusFilesView *files_view)
+{
+ NautilusListBase *self = NAUTILUS_LIST_BASE (files_view);
+ NautilusListBasePrivate *priv = nautilus_list_base_get_instance_private (self);
+ GtkSelectionModel *selection_model = GTK_SELECTION_MODEL (priv->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 (priv->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);
+}
+
+static guint
+get_first_selected_item (NautilusListBase *self)
+{
+ NautilusListBasePrivate *priv = nautilus_list_base_get_instance_private (self);
+ g_autolist (NautilusFile) selection = NULL;
+ NautilusFile *file;
+ NautilusViewItem *item;
+
+ selection = nautilus_view_get_selection (NAUTILUS_VIEW (self));
+ if (selection == NULL)
+ {
+ return G_MAXUINT;
+ }
+
+ file = NAUTILUS_FILE (selection->data);
+ item = nautilus_view_model_get_item_from_file (priv->model, file);
+
+ return nautilus_view_model_get_index (priv->model, item);
+}
+
+static void
+real_reveal_selection (NautilusFilesView *files_view)
+{
+ NautilusListBase *self = NAUTILUS_LIST_BASE (files_view);
+
+ nautilus_list_base_scroll_to_item (self, get_first_selected_item (self));
+}
+
+static void
+on_clipboard_contents_received (GObject *source_object,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ NautilusFilesView *files_view = NAUTILUS_FILES_VIEW (source_object);
+ NautilusListBase *self = NAUTILUS_LIST_BASE (files_view);
+ NautilusListBasePrivate *priv = nautilus_list_base_get_instance_private (self);
+ NautilusClipboard *clip;
+ NautilusViewItem *item;
+
+ for (GList *l = priv->cut_files; l != NULL; l = l->next)
+ {
+ item = nautilus_view_model_get_item_from_file (priv->model, l->data);
+ if (item != NULL)
+ {
+ nautilus_view_item_set_cut (item, FALSE);
+ }
+ }
+ g_clear_list (&priv->cut_files, g_object_unref);
+
+ clip = nautilus_files_view_get_clipboard_finish (files_view, res, NULL);
+ if (clip != NULL && nautilus_clipboard_is_cut (clip))
+ {
+ priv->cut_files = g_list_copy_deep (nautilus_clipboard_peek_files (clip),
+ (GCopyFunc) g_object_ref,
+ NULL);
+ }
+
+ for (GList *l = priv->cut_files; l != NULL; l = l->next)
+ {
+ item = nautilus_view_model_get_item_from_file (priv->model, l->data);
+ if (item != NULL)
+ {
+ nautilus_view_item_set_cut (item, TRUE);
+ }
+ }
+}
+
+static void
+update_clipboard_status (NautilusFilesView *view)
+{
+ nautilus_files_view_get_clipboard_async (view,
+ on_clipboard_contents_received,
+ NULL);
+}
+
+static void
+on_clipboard_owner_changed (GdkClipboard *clipboard,
+ gpointer user_data)
+{
+ update_clipboard_status (NAUTILUS_FILES_VIEW (user_data));
+}
+
+static void
+real_end_loading (NautilusFilesView *files_view,
+ gboolean all_files_seen)
+{
+ update_clipboard_status (files_view);
+}
+
+static guint
+get_first_visible_item (NautilusListBase *self)
+{
+ NautilusListBasePrivate *priv = nautilus_list_base_get_instance_private (self);
+ guint n_items;
+ GtkWidget *view_ui;
+ GtkBorder border = {0};
+
+ n_items = g_list_model_get_n_items (G_LIST_MODEL (priv->model));
+ view_ui = nautilus_list_base_get_view_ui (self);
+ gtk_scrollable_get_border (GTK_SCROLLABLE (view_ui), &border);
+
+ for (guint i = 0; i < n_items; i++)
+ {
+ g_autoptr (NautilusViewItem) item = NULL;
+ GtkWidget *item_ui;
+
+ item = get_view_item (G_LIST_MODEL (priv->model), i);
+ item_ui = nautilus_view_item_get_item_ui (item);
+ if (item_ui != NULL && gtk_widget_get_mapped (item_ui))
+ {
+ GtkWidget *list_item_widget = gtk_widget_get_parent (item_ui);
+ gdouble h = gtk_widget_get_allocated_height (list_item_widget);
+ gdouble y;
+
+ gtk_widget_translate_coordinates (list_item_widget, GTK_WIDGET (self),
+ 0, h, NULL, &y);
+ if (y >= border.top)
+ {
+ return i;
+ }
+ }
+ }
+
+ return G_MAXUINT;
+}
+
+static char *
+real_get_first_visible_file (NautilusFilesView *files_view)
+{
+ NautilusListBase *self = NAUTILUS_LIST_BASE (files_view);
+ NautilusListBasePrivate *priv = nautilus_list_base_get_instance_private (self);
+ guint i;
+ g_autoptr (NautilusViewItem) item = NULL;
+ gchar *uri = NULL;
+
+ i = get_first_visible_item (self);
+ if (i < G_MAXUINT)
+ {
+ item = get_view_item (G_LIST_MODEL (priv->model), i);
+ uri = nautilus_file_get_uri (nautilus_view_item_get_file (item));
+ }
+ return uri;
+}
+
+typedef struct
+{
+ NautilusListBase *view;
+ char *uri;
+} ScrollToFileData;
+
+static void
+scroll_to_file_data_free (ScrollToFileData *data)
+{
+ g_free (data->uri);
+ g_free (data);
+}
+
+static gboolean
+scroll_to_file_on_idle (ScrollToFileData *data)
+{
+ NautilusListBase *self = data->view;
+ NautilusListBasePrivate *priv = nautilus_list_base_get_instance_private (self);
+ g_autoptr (NautilusFile) file = NULL;
+ NautilusViewItem *item;
+ guint i;
+
+ priv->scroll_to_file_handle_id = 0;
+
+ file = nautilus_file_get_existing_by_uri (data->uri);
+ item = nautilus_view_model_get_item_from_file (priv->model, file);
+ g_return_val_if_fail (item != NULL, G_SOURCE_REMOVE);
+
+ i = nautilus_view_model_get_index (priv->model, item);
+ nautilus_list_base_scroll_to_item (self, i);
+
+ return G_SOURCE_REMOVE;
+}
+
+static void
+real_scroll_to_file (NautilusFilesView *files_view,
+ const char *uri)
+{
+ NautilusListBase *self = NAUTILUS_LIST_BASE (files_view);
+ NautilusListBasePrivate *priv = nautilus_list_base_get_instance_private (self);
+ ScrollToFileData *data;
+ guint handle_id;
+
+ data = g_new (ScrollToFileData, 1);
+ data->view = self;
+ data->uri = g_strdup (uri);
+ handle_id = g_idle_add_full (G_PRIORITY_LOW,
+ (GSourceFunc) scroll_to_file_on_idle,
+ data,
+ (GDestroyNotify) scroll_to_file_data_free);
+ priv->scroll_to_file_handle_id = handle_id;
+}
+
+static void
+real_add_files (NautilusFilesView *files_view,
+ GList *files)
+{
+ NautilusListBase *self = NAUTILUS_LIST_BASE (files_view);
+ NautilusListBasePrivate *priv = nautilus_list_base_get_instance_private (self);
+ g_autoptr (GQueue) files_queue = NULL;
+ g_autoptr (GQueue) items = NULL;
+
+ files_queue = convert_glist_to_queue (files);
+ items = convert_files_to_items (self, files_queue);
+ nautilus_view_model_add_items (priv->model, items);
+}
+
+static void
+real_select_first (NautilusFilesView *files_view)
+{
+ NautilusListBase *self = NAUTILUS_LIST_BASE (files_view);
+ NautilusListBasePrivate *priv = nautilus_list_base_get_instance_private (self);
+
+ gtk_selection_model_select_item (GTK_SELECTION_MODEL (priv->model), 0, TRUE);
+}
+
+static GdkRectangle *
+get_rectangle_for_item_ui (NautilusListBase *self,
+ GtkWidget *item_ui)
+{
+ GdkRectangle *rectangle;
+ GtkWidget *content_widget;
+ gdouble view_x;
+ gdouble view_y;
+
+ rectangle = g_new0 (GdkRectangle, 1);
+ gtk_widget_get_allocation (item_ui, rectangle);
+
+ content_widget = nautilus_files_view_get_content_widget (NAUTILUS_FILES_VIEW (self));
+ gtk_widget_translate_coordinates (item_ui, content_widget,
+ rectangle->x, rectangle->y,
+ &view_x, &view_y);
+ rectangle->x = view_x;
+ rectangle->y = view_y;
+
+ return rectangle;
+}
+
+static GdkRectangle *
+real_compute_rename_popover_pointing_to (NautilusFilesView *files_view)
+{
+ NautilusListBase *self = NAUTILUS_LIST_BASE (files_view);
+ NautilusListBasePrivate *priv = nautilus_list_base_get_instance_private (self);
+ g_autoptr (NautilusViewItem) item = NULL;
+ GtkWidget *item_ui;
+
+ /* We only allow one item to be renamed with a popover */
+ item = get_view_item (G_LIST_MODEL (priv->model), get_first_selected_item (self));
+ item_ui = nautilus_view_item_get_item_ui (item);
+ g_return_val_if_fail (item_ui != NULL, NULL);
+
+ return get_rectangle_for_item_ui (self, item_ui);
+}
+
+static GdkRectangle *
+real_reveal_for_selection_context_menu (NautilusFilesView *files_view)
+{
+ NautilusListBase *self = NAUTILUS_LIST_BASE (files_view);
+ NautilusListBasePrivate *priv = nautilus_list_base_get_instance_private (self);
+ g_autoptr (GtkSelectionFilterModel) selection = NULL;
+ guint n_selected;
+ GtkWidget *focus_child;
+ guint i;
+ GtkWidget *item_ui;
+
+ selection = gtk_selection_filter_model_new (GTK_SELECTION_MODEL (priv->model));
+ n_selected = g_list_model_get_n_items (G_LIST_MODEL (selection));
+ g_return_val_if_fail (n_selected > 0, NULL);
+
+ /* Get the focused item_ui, if selected.
+ * Otherwise, get the selected item_ui which is sorted the lowest.*/
+ focus_child = gtk_widget_get_focus_child (nautilus_list_base_get_view_ui (self));
+ for (i = 0; i < n_selected; i++)
+ {
+ g_autoptr (NautilusViewItem) item = NULL;
+
+ item = get_view_item (G_LIST_MODEL (selection), i);
+ item_ui = nautilus_view_item_get_item_ui (item);
+ if (item_ui != NULL && gtk_widget_get_parent (item_ui) == focus_child)
+ {
+ break;
+ }
+ }
+ nautilus_list_base_scroll_to_item (self, i);
+
+ return get_rectangle_for_item_ui (self, item_ui);
+}
+
+static void
+real_preview_selection_event (NautilusFilesView *files_view,
+ GtkDirectionType direction)
+{
+ NautilusListBase *self = NAUTILUS_LIST_BASE (files_view);
+ NautilusListBasePrivate *priv = nautilus_list_base_get_instance_private (self);
+ guint i;
+ gboolean rtl = (gtk_widget_get_direction (GTK_WIDGET (self)) == GTK_TEXT_DIR_RTL);
+
+ i = get_first_selected_item (self);
+ if (direction == GTK_DIR_UP ||
+ direction == (rtl ? GTK_DIR_RIGHT : GTK_DIR_LEFT))
+ {
+ if (i == 0)
+ {
+ return;
+ }
+
+ i--;
+ }
+ else
+ {
+ i++;
+
+ if (i >= g_list_model_get_n_items (G_LIST_MODEL (priv->model)))
+ {
+ return;
+ }
+ }
+
+ gtk_selection_model_select_item (GTK_SELECTION_MODEL (priv->model), i, TRUE);
+ set_focus_item (self, g_list_model_get_item (G_LIST_MODEL (priv->model), i));
+}
+
+static void
+default_sort_order_changed_callback (NautilusListBase *self)
+{
+ update_sort_order_from_metadata_and_preferences (self);
+}
+
+static void
+nautilus_list_base_dispose (GObject *object)
+{
+ NautilusListBase *self = NAUTILUS_LIST_BASE (object);
+ NautilusListBasePrivate *priv = nautilus_list_base_get_instance_private (self);
+
+ g_clear_handle_id (&priv->scroll_to_file_handle_id, g_source_remove);
+ g_clear_handle_id (&priv->prioritize_thumbnailing_handle_id, g_source_remove);
+ g_clear_handle_id (&priv->hover_timer_id, g_source_remove);
+
+ G_OBJECT_CLASS (nautilus_list_base_parent_class)->dispose (object);
+}
+
+static void
+nautilus_list_base_finalize (GObject *object)
+{
+ NautilusListBase *self = NAUTILUS_LIST_BASE (object);
+ NautilusListBasePrivate *priv = nautilus_list_base_get_instance_private (self);
+
+ g_clear_list (&priv->cut_files, g_object_unref);
+
+ G_OBJECT_CLASS (nautilus_list_base_parent_class)->finalize (object);
+}
+
+static gboolean
+prioritize_thumbnailing_on_idle (NautilusListBase *self)
+{
+ NautilusListBasePrivate *priv = nautilus_list_base_get_instance_private (self);
+ gdouble page_size;
+ GtkWidget *first_visible_child;
+ GtkWidget *next_child;
+ guint first_index;
+ guint next_index;
+ gdouble y;
+ guint last_index;
+ g_autoptr (NautilusViewItem) first_item = NULL;
+ NautilusFile *file;
+
+ priv->prioritize_thumbnailing_handle_id = 0;
+
+ page_size = gtk_adjustment_get_page_size (priv->vadjustment);
+ first_index = get_first_visible_item (self);
+ if (first_index == G_MAXUINT)
+ {
+ return G_SOURCE_REMOVE;
+ }
+
+ first_item = get_view_item (G_LIST_MODEL (priv->model), first_index);
+
+ first_visible_child = nautilus_view_item_get_item_ui (first_item);
+
+ for (next_index = first_index + 1; next_index < g_list_model_get_n_items (G_LIST_MODEL (priv->model)); next_index++)
+ {
+ g_autoptr (NautilusViewItem) next_item = NULL;
+
+ next_item = get_view_item (G_LIST_MODEL (priv->model), next_index);
+ next_child = nautilus_view_item_get_item_ui (next_item);
+ if (next_child == NULL)
+ {
+ break;
+ }
+ if (gtk_widget_translate_coordinates (next_child, first_visible_child,
+ 0, 0, NULL, &y))
+ {
+ if (y > page_size)
+ {
+ break;
+ }
+ }
+ }
+ last_index = next_index - 1;
+
+ /* Do the iteration in reverse to give higher priority to the top */
+ for (gint i = 0; i <= last_index - first_index; i++)
+ {
+ g_autoptr (NautilusViewItem) item = NULL;
+
+ item = get_view_item (G_LIST_MODEL (priv->model), last_index - i);
+ g_return_val_if_fail (item != NULL, G_SOURCE_REMOVE);
+
+ file = nautilus_view_item_get_file (NAUTILUS_VIEW_ITEM (item));
+ if (file != NULL && nautilus_file_is_thumbnailing (file))
+ {
+ g_autofree gchar *uri = nautilus_file_get_uri (file);
+ nautilus_thumbnail_prioritize (uri);
+ }
+ }
+
+ return G_SOURCE_REMOVE;
+}
+
+static void
+on_vadjustment_changed (GtkAdjustment *adjustment,
+ gpointer user_data)
+{
+ NautilusListBase *self = NAUTILUS_LIST_BASE (user_data);
+ NautilusListBasePrivate *priv = nautilus_list_base_get_instance_private (self);
+ guint handle_id;
+
+ /* Schedule on idle to rate limit and to avoid delaying scrolling. */
+ if (priv->prioritize_thumbnailing_handle_id == 0)
+ {
+ handle_id = g_idle_add ((GSourceFunc) prioritize_thumbnailing_on_idle, self);
+ priv->prioritize_thumbnailing_handle_id = handle_id;
+ }
+}
+
+static gboolean
+nautilus_list_base_focus (GtkWidget *widget,
+ GtkDirectionType direction)
+{
+ NautilusListBase *self = NAUTILUS_LIST_BASE (widget);
+ g_autolist (NautilusFile) selection = NULL;
+ gboolean no_selection;
+ gboolean handled;
+
+ /* If focus is already inside the view, allow to immediately tab out of it,
+ * instead of having to cycle through every item (potentially many). */
+ if (direction == GTK_DIR_TAB_FORWARD || direction == GTK_DIR_TAB_BACKWARD)
+ {
+ GtkWidget *focus_widget = gtk_root_get_focus (gtk_widget_get_root (widget));
+ if (focus_widget != NULL && gtk_widget_is_ancestor (focus_widget, widget))
+ {
+ return FALSE;
+ }
+ }
+
+ selection = nautilus_view_get_selection (NAUTILUS_VIEW (self));
+ no_selection = (selection == NULL);
+
+ handled = GTK_WIDGET_CLASS (nautilus_list_base_parent_class)->focus (widget, direction);
+
+ if (handled && no_selection)
+ {
+ GtkWidget *focus_widget = gtk_root_get_focus (gtk_widget_get_root (widget));
+
+ /* Workaround for https://gitlab.gnome.org/GNOME/nautilus/-/issues/2489
+ * Also ensures an item gets selected when using <Tab> to focus the view.
+ * Ideally to be fixed in GtkListBase instead. */
+ if (focus_widget != NULL)
+ {
+ gtk_widget_activate_action (focus_widget,
+ "listitem.select",
+ "(bb)",
+ FALSE, FALSE);
+ }
+ }
+
+ return handled;
+}
+
+static void
+nautilus_list_base_class_init (NautilusListBaseClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+ NautilusFilesViewClass *files_view_class = NAUTILUS_FILES_VIEW_CLASS (klass);
+
+ object_class->dispose = nautilus_list_base_dispose;
+ object_class->finalize = nautilus_list_base_finalize;
+
+ widget_class->focus = nautilus_list_base_focus;
+
+ files_view_class->add_files = real_add_files;
+ files_view_class->begin_loading = real_begin_loading;
+ files_view_class->clear = real_clear;
+ files_view_class->click_policy_changed = real_click_policy_changed;
+ files_view_class->file_changed = real_file_changed;
+ files_view_class->get_selection = real_get_selection;
+ /* TODO: remove this get_selection_for_file_transfer, this doesn't even
+ * take into account we could us the view for recursive search :/
+ * CanvasView has the same issue. */
+ files_view_class->get_selection_for_file_transfer = real_get_selection;
+ files_view_class->is_empty = real_is_empty;
+ files_view_class->remove_file = real_remove_file;
+ files_view_class->select_all = real_select_all;
+ files_view_class->set_selection = real_set_selection;
+ files_view_class->invert_selection = real_invert_selection;
+ files_view_class->end_file_changes = real_end_file_changes;
+ files_view_class->end_loading = real_end_loading;
+ files_view_class->get_first_visible_file = real_get_first_visible_file;
+ files_view_class->reveal_selection = real_reveal_selection;
+ files_view_class->scroll_to_file = real_scroll_to_file;
+ files_view_class->select_first = real_select_first;
+ files_view_class->compute_rename_popover_pointing_to = real_compute_rename_popover_pointing_to;
+ files_view_class->reveal_for_selection_context_menu = real_reveal_for_selection_context_menu;
+ files_view_class->preview_selection_event = real_preview_selection_event;
+}
+
+static void
+nautilus_list_base_init (NautilusListBase *self)
+{
+ NautilusListBasePrivate *priv = nautilus_list_base_get_instance_private (self);
+ GtkWidget *content_widget;
+ GtkAdjustment *vadjustment;
+
+ gtk_widget_add_css_class (GTK_WIDGET (self), "view");
+
+ g_signal_connect_object (nautilus_preferences,
+ "changed::" NAUTILUS_PREFERENCES_DEFAULT_SORT_ORDER,
+ G_CALLBACK (default_sort_order_changed_callback),
+ self,
+ G_CONNECT_SWAPPED);
+ g_signal_connect_object (nautilus_preferences,
+ "changed::" NAUTILUS_PREFERENCES_DEFAULT_SORT_IN_REVERSE_ORDER,
+ G_CALLBACK (default_sort_order_changed_callback),
+ self,
+ G_CONNECT_SWAPPED);
+
+ /* React to clipboard changes */
+ g_signal_connect_object (gdk_display_get_clipboard (gdk_display_get_default ()),
+ "changed",
+ G_CALLBACK (on_clipboard_owner_changed), self, 0);
+
+ content_widget = nautilus_files_view_get_content_widget (NAUTILUS_FILES_VIEW (self));
+ vadjustment = gtk_scrolled_window_get_vadjustment (GTK_SCROLLED_WINDOW (content_widget));
+
+ priv->vadjustment = vadjustment;
+ g_signal_connect (vadjustment, "changed", (GCallback) on_vadjustment_changed, self);
+ g_signal_connect (vadjustment, "value-changed", (GCallback) on_vadjustment_changed, self);
+
+ priv->model = nautilus_view_model_new ();
+
+ g_signal_connect_object (GTK_SELECTION_MODEL (priv->model),
+ "selection-changed",
+ G_CALLBACK (nautilus_files_view_notify_selection_changed),
+ NAUTILUS_FILES_VIEW (self),
+ G_CONNECT_SWAPPED);
+
+ set_click_mode_from_settings (self);
+}
+
+NautilusViewModel *
+nautilus_list_base_get_model (NautilusListBase *self)
+{
+ NautilusListBasePrivate *priv = nautilus_list_base_get_instance_private (self);
+
+ return priv->model;
+}
+
+void
+nautilus_list_base_setup_gestures (NautilusListBase *self)
+{
+ NautilusListBasePrivate *priv = nautilus_list_base_get_instance_private (self);
+ GtkEventController *controller;
+ GtkDropTarget *drop_target;
+
+ controller = GTK_EVENT_CONTROLLER (gtk_gesture_click_new ());
+ gtk_widget_add_controller (GTK_WIDGET (self), controller);
+ gtk_gesture_single_set_button (GTK_GESTURE_SINGLE (controller), 0);
+ g_signal_connect (controller, "pressed",
+ G_CALLBACK (on_view_click_pressed), self);
+
+ controller = GTK_EVENT_CONTROLLER (gtk_gesture_long_press_new ());
+ gtk_widget_add_controller (GTK_WIDGET (self), controller);
+ gtk_gesture_single_set_touch_only (GTK_GESTURE_SINGLE (controller), TRUE);
+ g_signal_connect (controller, "pressed",
+ G_CALLBACK (on_view_longpress_pressed), self);
+
+ /* TODO: Implement GDK_ACTION_ASK */
+ drop_target = gtk_drop_target_new (G_TYPE_INVALID, GDK_ACTION_ALL);
+ gtk_drop_target_set_preload (drop_target, TRUE);
+ /* TODO: Implement GDK_TYPE_STRING */
+ gtk_drop_target_set_gtypes (drop_target, (GType[2]) { GDK_TYPE_FILE_LIST, G_TYPE_STRING }, 2);
+ g_signal_connect (drop_target, "enter", G_CALLBACK (on_view_drag_enter), self);
+ g_signal_connect (drop_target, "notify::value", G_CALLBACK (on_view_drag_value_notify), self);
+ g_signal_connect (drop_target, "motion", G_CALLBACK (on_view_drag_motion), self);
+ g_signal_connect (drop_target, "drop", G_CALLBACK (on_view_drop), self);
+ gtk_widget_add_controller (GTK_WIDGET (self), GTK_EVENT_CONTROLLER (drop_target));
+ priv->view_drop_target = drop_target;
+}
diff --git a/src/nautilus-list-base.h b/src/nautilus-list-base.h
new file mode 100644
index 0000000..007ab07
--- /dev/null
+++ b/src/nautilus-list-base.h
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2022 The GNOME project contributors
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#pragma once
+
+#include "nautilus-files-view.h"
+
+G_BEGIN_DECLS
+
+#define NAUTILUS_TYPE_LIST_BASE (nautilus_list_base_get_type())
+
+G_DECLARE_DERIVABLE_TYPE (NautilusListBase, nautilus_list_base, NAUTILUS, LIST_BASE, NautilusFilesView)
+
+struct _NautilusListBaseClass
+{
+ NautilusFilesViewClass parent_class;
+
+ guint (*get_icon_size) (NautilusListBase *self);
+ GtkWidget *(*get_view_ui) (NautilusListBase *self);
+ void (*scroll_to_item) (NautilusListBase *self,
+ guint position);
+};
+
+G_END_DECLS
diff --git a/src/nautilus-list-view.c b/src/nautilus-list-view.c
new file mode 100644
index 0000000..18f860b
--- /dev/null
+++ b/src/nautilus-list-view.c
@@ -0,0 +1,1258 @@
+/*
+ * Copyright (C) 2000 Eazel, Inc.
+ * Copyright (C) 2001, 2002 Anders Carlsson <andersca@gnu.org>
+ * Copyright (C) 2022 GNOME project contributors
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#include <glib/gi18n.h>
+
+/* Needed for NautilusColumn. */
+#include <nautilus-extension.h>
+
+#include "nautilus-list-base-private.h"
+#include "nautilus-list-view.h"
+
+#include "nautilus-column-chooser.h"
+#include "nautilus-column-utilities.h"
+#include "nautilus-directory.h"
+#include "nautilus-file.h"
+#include "nautilus-file-utilities.h"
+#include "nautilus-global-preferences.h"
+#include "nautilus-label-cell.h"
+#include "nautilus-metadata.h"
+#include "nautilus-name-cell.h"
+#include "nautilus-search-directory.h"
+#include "nautilus-star-cell.h"
+#include "nautilus-tag-manager.h"
+
+struct _NautilusListView
+{
+ NautilusListBase parent_instance;
+
+ GtkColumnView *view_ui;
+
+ GActionGroup *action_group;
+ gint zoom_level;
+
+ gboolean directories_first;
+
+ GQuark path_attribute_q;
+ GFile *file_path_base_location;
+
+ GtkColumnViewColumn *star_column;
+ GtkWidget *column_editor;
+ GHashTable *factory_to_column_map;
+
+ GHashTable *all_view_columns_hash;
+
+ /* Column sort hack state */
+ gboolean column_header_was_clicked;
+ GQuark clicked_column_attribute_q;
+};
+
+G_DEFINE_TYPE (NautilusListView, nautilus_list_view, NAUTILUS_TYPE_LIST_BASE)
+
+
+static void on_sorter_changed (GtkSorter *sorter,
+ GtkSorterChange change,
+ gpointer user_data);
+
+static const char *default_columns_for_recent[] =
+{
+ "name", "size", "recency", NULL
+};
+
+static const char *default_columns_for_trash[] =
+{
+ "name", "size", "trashed_on", NULL
+};
+
+static guint
+get_icon_size_for_zoom_level (NautilusListZoomLevel zoom_level)
+{
+ switch (zoom_level)
+ {
+ case NAUTILUS_LIST_ZOOM_LEVEL_SMALL:
+ {
+ return NAUTILUS_LIST_ICON_SIZE_SMALL;
+ }
+ break;
+
+ case NAUTILUS_LIST_ZOOM_LEVEL_MEDIUM:
+ {
+ return NAUTILUS_LIST_ICON_SIZE_MEDIUM;
+ }
+ break;
+
+ case NAUTILUS_LIST_ZOOM_LEVEL_LARGE:
+ {
+ return NAUTILUS_LIST_ICON_SIZE_LARGE;
+ }
+ break;
+ }
+ g_return_val_if_reached (NAUTILUS_LIST_ICON_SIZE_MEDIUM);
+}
+
+static guint
+real_get_icon_size (NautilusListBase *list_base_view)
+{
+ NautilusListView *self = NAUTILUS_LIST_VIEW (list_base_view);
+
+ return get_icon_size_for_zoom_level (self->zoom_level);
+}
+
+static GtkWidget *
+real_get_view_ui (NautilusListBase *list_base_view)
+{
+ NautilusListView *self = NAUTILUS_LIST_VIEW (list_base_view);
+
+ return GTK_WIDGET (self->view_ui);
+}
+
+static void
+apply_columns_settings (NautilusListView *self,
+ char **column_order,
+ char **visible_columns)
+{
+ g_autolist (NautilusColumn) all_columns = NULL;
+ NautilusFile *file;
+ NautilusDirectory *directory;
+ g_autoptr (GFile) location = NULL;
+ g_autoptr (GList) view_columns = NULL;
+ GListModel *old_view_columns;
+ g_autoptr (GHashTable) visible_columns_hash = NULL;
+ int column_i = 0;
+
+ file = nautilus_files_view_get_directory_as_file (NAUTILUS_FILES_VIEW (self));
+ directory = nautilus_files_view_get_model (NAUTILUS_FILES_VIEW (self));
+ if (NAUTILUS_IS_SEARCH_DIRECTORY (directory))
+ {
+ g_autoptr (NautilusQuery) query = NULL;
+
+ query = nautilus_search_directory_get_query (NAUTILUS_SEARCH_DIRECTORY (directory));
+ location = nautilus_query_get_location (query);
+ }
+ else
+ {
+ location = nautilus_file_get_location (file);
+ }
+
+ all_columns = nautilus_get_columns_for_file (file);
+ all_columns = nautilus_sort_columns (all_columns, column_order);
+
+ /* hash table to lookup if a given column should be visible */
+ visible_columns_hash = g_hash_table_new_full (g_str_hash,
+ g_str_equal,
+ (GDestroyNotify) g_free,
+ (GDestroyNotify) g_free);
+ /* always show name column */
+ g_hash_table_insert (visible_columns_hash, g_strdup ("name"), g_strdup ("name"));
+
+ /* always show star column if supported */
+ if (nautilus_tag_manager_can_star_contents (nautilus_tag_manager_get (), location) ||
+ nautilus_is_starred_directory (location))
+ {
+ g_hash_table_insert (visible_columns_hash, g_strdup ("starred"), g_strdup ("starred"));
+ }
+
+ if (visible_columns != NULL)
+ {
+ for (int i = 0; visible_columns[i] != NULL; ++i)
+ {
+ g_hash_table_insert (visible_columns_hash,
+ g_ascii_strdown (visible_columns[i], -1),
+ g_ascii_strdown (visible_columns[i], -1));
+ }
+ }
+
+ old_view_columns = gtk_column_view_get_columns (self->view_ui);
+ for (GList *l = all_columns; l != NULL; l = l->next)
+ {
+ g_autofree char *name = NULL;
+ g_autofree char *lowercase = NULL;
+
+ g_object_get (G_OBJECT (l->data), "name", &name, NULL);
+ lowercase = g_ascii_strdown (name, -1);
+
+ if (g_hash_table_lookup (visible_columns_hash, lowercase) != NULL)
+ {
+ GtkColumnViewColumn *view_column;
+
+ view_column = g_hash_table_lookup (self->all_view_columns_hash, name);
+ if (view_column != NULL)
+ {
+ view_columns = g_list_prepend (view_columns, view_column);
+ }
+ }
+ }
+
+ view_columns = g_list_reverse (view_columns);
+
+ /* hide columns that are not present in the configuration */
+ for (guint i = 0; i < g_list_model_get_n_items (old_view_columns); i++)
+ {
+ g_autoptr (GtkColumnViewColumn) view_column = NULL;
+
+ view_column = g_list_model_get_item (old_view_columns, i);
+ if (g_list_find (view_columns, view_column) == NULL)
+ {
+ gtk_column_view_remove_column (self->view_ui, view_column);
+ }
+ }
+
+ /* place columns in the correct order */
+ for (GList *l = view_columns; l != NULL; l = l->next, column_i++)
+ {
+ gtk_column_view_insert_column (self->view_ui, column_i, l->data);
+ }
+}
+
+static void
+real_scroll_to_item (NautilusListBase *list_base_view,
+ guint position)
+{
+ NautilusListView *self = NAUTILUS_LIST_VIEW (list_base_view);
+ GtkWidget *child;
+
+ child = gtk_widget_get_last_child (GTK_WIDGET (self->view_ui));
+
+ while (child != NULL && !GTK_IS_LIST_VIEW (child))
+ {
+ child = gtk_widget_get_prev_sibling (child);
+ }
+
+ if (child != NULL)
+ {
+ gtk_widget_activate_action (child, "list.scroll-to-item", "u", position);
+ }
+}
+
+typedef struct
+{
+ GQuark attribute;
+ NautilusListView *view;
+} SortData;
+
+static gint
+nautilus_list_view_sort (gconstpointer a,
+ gconstpointer b,
+ gpointer user_data)
+{
+ SortData *data = user_data;
+ NautilusListView *self = data->view;
+ GQuark attribute_q = data->attribute;
+ NautilusFile *file_a = nautilus_view_item_get_file (NAUTILUS_VIEW_ITEM ((gpointer) a));
+ NautilusFile *file_b = nautilus_view_item_get_file (NAUTILUS_VIEW_ITEM ((gpointer) b));
+
+ /* Hack: We don't know what column is being sorted on when the column
+ * headers are clicked. So let's just look at what attribute was most
+ * recently used for sorting.
+ * https://gitlab.gnome.org/GNOME/gtk/-/issues/4833 */
+ if (self->clicked_column_attribute_q == 0 && self->column_header_was_clicked)
+ {
+ self->clicked_column_attribute_q = attribute_q;
+ }
+
+ g_return_val_if_fail (file_a != NULL && file_b != NULL, GTK_ORDERING_EQUAL);
+
+ /* The reversed argument is FALSE because the columnview sorter handles that
+ * itself and if we don't want to reverse the reverse. The directories_first
+ * argument is also FALSE for the same reason: we don't want the columnview
+ * sorter to reverse it (it would display directories last!); instead we
+ * handle directories_first in a separate sorter. */
+ return nautilus_file_compare_for_sort_by_attribute_q (file_a, file_b,
+ attribute_q,
+ FALSE /* directories_first */,
+ FALSE /* reversed */);
+}
+
+static gint
+sort_directories_func (gconstpointer a,
+ gconstpointer b,
+ gpointer user_data)
+{
+ gboolean *directories_first = user_data;
+
+ if (*directories_first)
+ {
+ NautilusFile *file_a = nautilus_view_item_get_file (NAUTILUS_VIEW_ITEM ((gpointer) a));
+ NautilusFile *file_b = nautilus_view_item_get_file (NAUTILUS_VIEW_ITEM ((gpointer) b));
+ gboolean a_is_directory = nautilus_file_is_directory (file_a);
+ gboolean b_is_directory = nautilus_file_is_directory (file_b);
+
+ if (a_is_directory && !b_is_directory)
+ {
+ return GTK_ORDERING_SMALLER;
+ }
+ if (b_is_directory && !a_is_directory)
+ {
+ return GTK_ORDERING_LARGER;
+ }
+ }
+ return GTK_ORDERING_EQUAL;
+}
+
+static char **
+get_default_visible_columns (NautilusListView *self)
+{
+ NautilusFile *file;
+
+ file = nautilus_files_view_get_directory_as_file (NAUTILUS_FILES_VIEW (self));
+
+ if (nautilus_file_is_in_trash (file))
+ {
+ return g_strdupv ((gchar **) default_columns_for_trash);
+ }
+
+ if (nautilus_file_is_in_recent (file))
+ {
+ return g_strdupv ((gchar **) default_columns_for_recent);
+ }
+
+ return g_settings_get_strv (nautilus_list_view_preferences,
+ NAUTILUS_PREFERENCES_LIST_VIEW_DEFAULT_VISIBLE_COLUMNS);
+}
+
+static char **
+get_visible_columns (NautilusListView *self)
+{
+ NautilusFile *file;
+ g_autofree gchar **visible_columns = NULL;
+
+ file = nautilus_files_view_get_directory_as_file (NAUTILUS_FILES_VIEW (self));
+
+ visible_columns = nautilus_file_get_metadata_list (file,
+ NAUTILUS_METADATA_KEY_LIST_VIEW_VISIBLE_COLUMNS);
+ if (visible_columns == NULL || visible_columns[0] == NULL)
+ {
+ return get_default_visible_columns (self);
+ }
+
+ return g_steal_pointer (&visible_columns);
+}
+
+static char **
+get_default_column_order (NautilusListView *self)
+{
+ NautilusFile *file;
+
+ file = nautilus_files_view_get_directory_as_file (NAUTILUS_FILES_VIEW (self));
+
+ if (nautilus_file_is_in_trash (file))
+ {
+ return g_strdupv ((gchar **) default_columns_for_trash);
+ }
+
+ if (nautilus_file_is_in_recent (file))
+ {
+ return g_strdupv ((gchar **) default_columns_for_recent);
+ }
+
+ return g_settings_get_strv (nautilus_list_view_preferences,
+ NAUTILUS_PREFERENCES_LIST_VIEW_DEFAULT_COLUMN_ORDER);
+}
+
+static char **
+get_column_order (NautilusListView *self)
+{
+ NautilusFile *file;
+ g_autofree gchar **column_order = NULL;
+
+ file = nautilus_files_view_get_directory_as_file (NAUTILUS_FILES_VIEW (self));
+
+ column_order = nautilus_file_get_metadata_list (file,
+ NAUTILUS_METADATA_KEY_LIST_VIEW_COLUMN_ORDER);
+
+ if (column_order != NULL && column_order[0] != NULL)
+ {
+ return g_steal_pointer (&column_order);
+ }
+
+ return get_default_column_order (self);
+}
+static void
+update_columns_settings_from_metadata_and_preferences (NautilusListView *self)
+{
+ g_auto (GStrv) column_order = get_column_order (self);
+ g_auto (GStrv) visible_columns = get_visible_columns (self);
+
+ apply_columns_settings (self, column_order, visible_columns);
+}
+
+static GFile *
+get_base_location (NautilusListView *self)
+{
+ NautilusDirectory *directory;
+ GFile *base_location = NULL;
+
+ directory = nautilus_files_view_get_model (NAUTILUS_FILES_VIEW (self));
+ if (NAUTILUS_IS_SEARCH_DIRECTORY (directory))
+ {
+ g_autoptr (NautilusQuery) query = NULL;
+ g_autoptr (GFile) location = NULL;
+
+ query = nautilus_search_directory_get_query (NAUTILUS_SEARCH_DIRECTORY (directory));
+ location = nautilus_query_get_location (query);
+
+ if (!nautilus_is_recent_directory (location) &&
+ !nautilus_is_starred_directory (location) &&
+ !nautilus_is_trash_directory (location))
+ {
+ base_location = g_steal_pointer (&location);
+ }
+ }
+
+ return base_location;
+}
+
+static void
+on_column_view_item_activated (GtkGridView *grid_view,
+ guint position,
+ gpointer user_data)
+{
+ NautilusListView *self = NAUTILUS_LIST_VIEW (user_data);
+
+ nautilus_files_view_activate_selection (NAUTILUS_FILES_VIEW (self));
+}
+
+static GtkColumnView *
+create_view_ui (NautilusListView *self)
+{
+ NautilusViewModel *model;
+ GtkWidget *widget;
+
+ model = nautilus_list_base_get_model (NAUTILUS_LIST_BASE (self));
+ widget = gtk_column_view_new (GTK_SELECTION_MODEL (model));
+
+ gtk_widget_set_hexpand (widget, TRUE);
+
+
+ /* We don't use the built-in child activation feature for click because it
+ * doesn't fill all our needs nor does it match our expected behavior.
+ * Instead, we roll our own event handling and double/single click mode.
+ * However, GtkColumnView:single-click-activate has other effects besides
+ * activation, as it affects the selection behavior as well (e.g. selects on
+ * hover). Setting it to FALSE gives us the expected behavior. */
+ gtk_column_view_set_single_click_activate (GTK_COLUMN_VIEW (widget), FALSE);
+ gtk_column_view_set_enable_rubberband (GTK_COLUMN_VIEW (widget), TRUE);
+
+ /* While we don't want to use GTK's click activation, we'll let it handle
+ * the key activation part (with Enter).
+ */
+ g_signal_connect (widget, "activate", G_CALLBACK (on_column_view_item_activated), self);
+
+ return GTK_COLUMN_VIEW (widget);
+}
+
+static void
+column_chooser_changed_callback (NautilusColumnChooser *chooser,
+ NautilusListView *view)
+{
+ NautilusFile *file;
+ char **visible_columns;
+ char **column_order;
+
+ file = nautilus_files_view_get_directory_as_file (NAUTILUS_FILES_VIEW (view));
+
+ nautilus_column_chooser_get_settings (chooser,
+ &visible_columns,
+ &column_order);
+
+ nautilus_file_set_metadata_list (file,
+ NAUTILUS_METADATA_KEY_LIST_VIEW_VISIBLE_COLUMNS,
+ visible_columns);
+ nautilus_file_set_metadata_list (file,
+ NAUTILUS_METADATA_KEY_LIST_VIEW_COLUMN_ORDER,
+ column_order);
+
+ apply_columns_settings (view, column_order, visible_columns);
+
+ g_strfreev (visible_columns);
+ g_strfreev (column_order);
+}
+
+static void
+column_chooser_set_from_arrays (NautilusColumnChooser *chooser,
+ NautilusListView *view,
+ char **visible_columns,
+ char **column_order)
+{
+ g_signal_handlers_block_by_func
+ (chooser, G_CALLBACK (column_chooser_changed_callback), view);
+
+ nautilus_column_chooser_set_settings (chooser,
+ visible_columns,
+ column_order);
+
+ g_signal_handlers_unblock_by_func
+ (chooser, G_CALLBACK (column_chooser_changed_callback), view);
+}
+
+static void
+column_chooser_set_from_settings (NautilusColumnChooser *chooser,
+ NautilusListView *view)
+{
+ char **visible_columns;
+ char **column_order;
+
+ visible_columns = get_visible_columns (view);
+ column_order = get_column_order (view);
+
+ column_chooser_set_from_arrays (chooser, view,
+ visible_columns, column_order);
+
+ g_strfreev (visible_columns);
+ g_strfreev (column_order);
+}
+
+static void
+column_chooser_use_default_callback (NautilusColumnChooser *chooser,
+ NautilusListView *view)
+{
+ NautilusFile *file;
+ char **default_columns;
+ char **default_order;
+
+ file = nautilus_files_view_get_directory_as_file
+ (NAUTILUS_FILES_VIEW (view));
+
+ nautilus_file_set_metadata_list (file, NAUTILUS_METADATA_KEY_LIST_VIEW_COLUMN_ORDER, NULL);
+ nautilus_file_set_metadata_list (file, NAUTILUS_METADATA_KEY_LIST_VIEW_VISIBLE_COLUMNS, NULL);
+
+ /* set view values ourselves, as new metadata could not have been
+ * updated yet.
+ */
+ default_columns = get_default_visible_columns (view);
+ default_order = get_default_column_order (view);
+
+ apply_columns_settings (view, default_order, default_columns);
+ column_chooser_set_from_arrays (chooser, view,
+ default_columns, default_order);
+
+ g_strfreev (default_columns);
+ g_strfreev (default_order);
+}
+
+static GtkWidget *
+create_column_editor (NautilusListView *view)
+{
+ g_autoptr (GtkBuilder) builder = NULL;
+ GtkWidget *window;
+ AdwWindowTitle *window_title;
+ GtkWidget *box;
+ GtkWidget *column_chooser;
+ NautilusFile *file;
+ char *name;
+
+ builder = gtk_builder_new_from_resource ("/org/gnome/nautilus/ui/nautilus-list-view-column-editor.ui");
+
+ window = GTK_WIDGET (gtk_builder_get_object (builder, "window"));
+ gtk_window_set_transient_for (GTK_WINDOW (window),
+ GTK_WINDOW (gtk_widget_get_root (GTK_WIDGET (view))));
+
+ file = nautilus_files_view_get_directory_as_file (NAUTILUS_FILES_VIEW (view));
+ name = nautilus_file_get_display_name (file);
+ window_title = ADW_WINDOW_TITLE (gtk_builder_get_object (builder, "window_title"));
+ adw_window_title_set_subtitle (window_title, name);
+ g_free (name);
+
+ box = GTK_WIDGET (gtk_builder_get_object (builder, "box"));
+
+ column_chooser = nautilus_column_chooser_new (file);
+ gtk_widget_set_vexpand (column_chooser, TRUE);
+ gtk_box_append (GTK_BOX (box), column_chooser);
+
+ g_signal_connect (column_chooser, "changed",
+ G_CALLBACK (column_chooser_changed_callback),
+ view);
+ g_signal_connect (column_chooser, "use-default",
+ G_CALLBACK (column_chooser_use_default_callback),
+ view);
+
+ column_chooser_set_from_settings
+ (NAUTILUS_COLUMN_CHOOSER (column_chooser), view);
+
+ return window;
+}
+
+static void
+action_visible_columns (GSimpleAction *action,
+ GVariant *state,
+ gpointer user_data)
+{
+ NautilusListView *self = NAUTILUS_LIST_VIEW (user_data);
+
+ if (self->column_editor)
+ {
+ gtk_widget_show (self->column_editor);
+ }
+ else
+ {
+ self->column_editor = create_column_editor (self);
+ g_object_add_weak_pointer (G_OBJECT (self->column_editor),
+ (gpointer *) &self->column_editor);
+
+ gtk_widget_show (self->column_editor);
+ }
+}
+
+static void
+action_sort_order_changed (GSimpleAction *action,
+ GVariant *value,
+ gpointer user_data)
+{
+ const gchar *target_name;
+ gboolean reversed;
+ NautilusFileSortType sort_type;
+ NautilusListView *self;
+ GListModel *view_columns;
+ NautilusViewModel *model;
+ g_autoptr (GtkColumnViewColumn) sort_column = NULL;
+ GtkSorter *sorter;
+
+ /* This array makes the #NautilusFileSortType values correspond to the
+ * respective column attribute.
+ */
+ const char *attributes[] =
+ {
+ "name",
+ "size",
+ "type",
+ "date_modified",
+ "date_accessed",
+ "date_created",
+ "starred",
+ "trashed_on",
+ "search_relevance",
+ "recency",
+ NULL
+ };
+
+ /* Don't resort if the action is in the same state as before */
+ if (g_variant_equal (value, g_action_get_state (G_ACTION (action))))
+ {
+ return;
+ }
+
+ self = NAUTILUS_LIST_VIEW (user_data);
+ g_variant_get (value, "(&sb)", &target_name, &reversed);
+
+ if (g_strcmp0 (target_name, "unknown") == 0)
+ {
+ /* Sort order has been changed without using this action. */
+ g_simple_action_set_state (action, value);
+ return;
+ }
+
+ sort_type = get_sorts_type_from_metadata_text (target_name);
+
+ view_columns = gtk_column_view_get_columns (self->view_ui);
+ for (guint i = 0; i < g_list_model_get_n_items (view_columns); i++)
+ {
+ g_autoptr (GtkColumnViewColumn) view_column = NULL;
+ GtkListItemFactory *factory;
+ NautilusColumn *nautilus_column;
+ gchar *attribute;
+
+ view_column = g_list_model_get_item (view_columns, i);
+ factory = gtk_column_view_column_get_factory (view_column);
+ nautilus_column = g_hash_table_lookup (self->factory_to_column_map, factory);
+ if (nautilus_column == NULL)
+ {
+ continue;
+ }
+ g_object_get (nautilus_column, "attribute", &attribute, NULL);
+ if (g_strcmp0 (attributes[sort_type], attribute) == 0)
+ {
+ sort_column = g_steal_pointer (&view_column);
+ break;
+ }
+ }
+
+ model = nautilus_list_base_get_model (NAUTILUS_LIST_BASE (self));
+ sorter = nautilus_view_model_get_sorter (model);
+
+ /* Ask the column view to sort by column if it hasn't just done so already. */
+ if (!self->column_header_was_clicked)
+ {
+ g_signal_handlers_block_by_func (sorter, on_sorter_changed, self);
+ /* FIXME: Set NULL to stop drawing the arrow on previous sort column
+ * to workaround https://gitlab.gnome.org/GNOME/gtk/-/issues/4696 */
+ gtk_column_view_sort_by_column (self->view_ui, NULL, FALSE);
+ gtk_column_view_sort_by_column (self->view_ui, sort_column, reversed);
+ g_signal_handlers_unblock_by_func (sorter, on_sorter_changed, self);
+ }
+
+ self->column_header_was_clicked = FALSE;
+
+ set_directory_sort_metadata (nautilus_files_view_get_directory_as_file (NAUTILUS_FILES_VIEW (self)),
+ target_name,
+ reversed);
+
+ g_simple_action_set_state (action, value);
+}
+
+static void
+set_zoom_level (NautilusListView *self,
+ guint new_level)
+{
+ self->zoom_level = new_level;
+
+ nautilus_list_base_set_icon_size (NAUTILUS_LIST_BASE (self),
+ get_icon_size_for_zoom_level (new_level));
+
+ if (self->zoom_level == NAUTILUS_LIST_ZOOM_LEVEL_SMALL)
+ {
+ gtk_widget_add_css_class (GTK_WIDGET (self), "compact");
+ }
+ else
+ {
+ gtk_widget_remove_css_class (GTK_WIDGET (self), "compact");
+ }
+
+ nautilus_files_view_update_toolbar_menus (NAUTILUS_FILES_VIEW (self));
+}
+
+static void
+action_zoom_to_level (GSimpleAction *action,
+ GVariant *state,
+ gpointer user_data)
+{
+ NautilusListView *self = NAUTILUS_LIST_VIEW (user_data);
+ int zoom_level;
+
+ zoom_level = g_variant_get_int32 (state);
+ set_zoom_level (self, zoom_level);
+ g_simple_action_set_state (G_SIMPLE_ACTION (action), state);
+
+ if (g_settings_get_enum (nautilus_list_view_preferences,
+ NAUTILUS_PREFERENCES_LIST_VIEW_DEFAULT_ZOOM_LEVEL) != zoom_level)
+ {
+ g_settings_set_enum (nautilus_list_view_preferences,
+ NAUTILUS_PREFERENCES_LIST_VIEW_DEFAULT_ZOOM_LEVEL,
+ zoom_level);
+ }
+}
+
+const GActionEntry list_view_entries[] =
+{
+ { "visible-columns", action_visible_columns },
+ { "sort", NULL, "(sb)", "('invalid',false)", action_sort_order_changed },
+ { "zoom-to-level", NULL, NULL, "1", action_zoom_to_level }
+};
+
+static void
+real_begin_loading (NautilusFilesView *files_view)
+{
+ NautilusListView *self = NAUTILUS_LIST_VIEW (files_view);
+ NautilusFile *file;
+
+ /* We need to setup the columns before chaining up */
+ update_columns_settings_from_metadata_and_preferences (self);
+
+ NAUTILUS_FILES_VIEW_CLASS (nautilus_list_view_parent_class)->begin_loading (files_view);
+
+ self->clicked_column_attribute_q = 0;
+
+ self->path_attribute_q = 0;
+ g_clear_object (&self->file_path_base_location);
+ file = nautilus_files_view_get_directory_as_file (files_view);
+ if (nautilus_file_is_in_trash (file))
+ {
+ self->path_attribute_q = g_quark_from_string ("trash_orig_path");
+ self->file_path_base_location = get_base_location (self);
+ }
+ else if (nautilus_file_is_in_search (file) ||
+ nautilus_file_is_in_recent (file) ||
+ nautilus_file_is_in_starred (file))
+ {
+ self->path_attribute_q = g_quark_from_string ("where");
+ self->file_path_base_location = get_base_location (self);
+ }
+}
+
+static void
+real_bump_zoom_level (NautilusFilesView *files_view,
+ int zoom_increment)
+{
+ NautilusListView *self = NAUTILUS_LIST_VIEW (files_view);
+ NautilusListZoomLevel new_level;
+
+ new_level = self->zoom_level + zoom_increment;
+
+ if (new_level >= NAUTILUS_LIST_ZOOM_LEVEL_SMALL &&
+ new_level <= NAUTILUS_LIST_ZOOM_LEVEL_LARGE)
+ {
+ g_action_group_change_action_state (self->action_group,
+ "zoom-to-level",
+ g_variant_new_int32 (new_level));
+ }
+}
+
+static gint
+get_default_zoom_level (void)
+{
+ NautilusListZoomLevel default_zoom_level;
+
+ default_zoom_level = g_settings_get_enum (nautilus_list_view_preferences,
+ NAUTILUS_PREFERENCES_LIST_VIEW_DEFAULT_ZOOM_LEVEL);
+
+ /* Sanitize preference value. */
+ return CLAMP (default_zoom_level,
+ NAUTILUS_LIST_ZOOM_LEVEL_SMALL,
+ NAUTILUS_LIST_ZOOM_LEVEL_LARGE);
+}
+
+static void
+real_restore_standard_zoom_level (NautilusFilesView *files_view)
+{
+ NautilusListView *self;
+
+ self = NAUTILUS_LIST_VIEW (files_view);
+ g_action_group_change_action_state (self->action_group,
+ "zoom-to-level",
+ g_variant_new_int32 (NAUTILUS_LIST_ZOOM_LEVEL_MEDIUM));
+}
+
+static gboolean
+real_can_zoom_in (NautilusFilesView *files_view)
+{
+ NautilusListView *self = NAUTILUS_LIST_VIEW (files_view);
+
+ return self->zoom_level < NAUTILUS_LIST_ZOOM_LEVEL_LARGE;
+}
+
+static gboolean
+real_can_zoom_out (NautilusFilesView *files_view)
+{
+ NautilusListView *self = NAUTILUS_LIST_VIEW (files_view);
+
+ return self->zoom_level > NAUTILUS_LIST_ZOOM_LEVEL_SMALL;
+}
+
+static gboolean
+real_is_zoom_level_default (NautilusFilesView *files_view)
+{
+ NautilusListView *self;
+ guint icon_size;
+
+ self = NAUTILUS_LIST_VIEW (files_view);
+ icon_size = get_icon_size_for_zoom_level (self->zoom_level);
+
+ return icon_size == NAUTILUS_LIST_ICON_SIZE_MEDIUM;
+}
+
+static void
+real_sort_directories_first_changed (NautilusFilesView *files_view)
+{
+ NautilusListView *self = NAUTILUS_LIST_VIEW (files_view);
+ NautilusViewModel *model;
+
+ self->directories_first = nautilus_files_view_should_sort_directories_first (NAUTILUS_FILES_VIEW (self));
+
+ /* Reset the sorter to trigger ressorting */
+ model = nautilus_list_base_get_model (NAUTILUS_LIST_BASE (self));
+ nautilus_view_model_set_sorter (model, nautilus_view_model_get_sorter (model));
+}
+
+static void
+on_sorter_changed (GtkSorter *sorter,
+ GtkSorterChange change,
+ gpointer user_data)
+{
+ NautilusListView *self = NAUTILUS_LIST_VIEW (user_data);
+ NautilusViewModel *model = nautilus_list_base_get_model (NAUTILUS_LIST_BASE (self));
+
+ /* Set the conditions to capture the sort attribute the first time that
+ * nautilus_list_view_sort() is called. */
+ self->column_header_was_clicked = TRUE;
+ self->clicked_column_attribute_q = 0;
+
+ /* If there is only one file, enforce a comparison against a dummy item, to
+ * ensure nautilus_list_view_sort() gets called at least once. */
+ if (g_list_model_get_n_items (G_LIST_MODEL (model)) == 1)
+ {
+ NautilusViewItem *item = g_list_model_get_item (G_LIST_MODEL (model), 0);
+ g_autoptr (NautilusViewItem) dummy_item = NULL;
+
+ dummy_item = nautilus_view_item_new (nautilus_view_item_get_file (item),
+ NAUTILUS_LIST_ICON_SIZE_SMALL);
+
+ gtk_sorter_compare (sorter, item, dummy_item);
+ }
+}
+
+static void
+on_after_sorter_changed (GtkSorter *sorter,
+ GtkSorterChange change,
+ gpointer user_data)
+{
+ NautilusListView *self = NAUTILUS_LIST_VIEW (user_data);
+ GActionGroup *action_group = nautilus_files_view_get_action_group (NAUTILUS_FILES_VIEW (self));
+ g_autoptr (GVariant) state = NULL;
+ const gchar *new_sort_text;
+ gboolean reversed;
+ const gchar *current_sort_text;
+
+ if (!self->column_header_was_clicked || self->clicked_column_attribute_q == 0)
+ {
+ return;
+ }
+
+ state = g_action_group_get_action_state (action_group, "sort");
+ g_variant_get (state, "(&sb)", &current_sort_text, &reversed);
+
+ new_sort_text = g_quark_to_string (self->clicked_column_attribute_q);
+
+ if (g_strcmp0 (new_sort_text, current_sort_text) == 0)
+ {
+ reversed = !reversed;
+ }
+ else
+ {
+ reversed = FALSE;
+ }
+
+ g_action_group_change_action_state (action_group, "sort",
+ g_variant_new ("(sb)", new_sort_text, reversed));
+}
+
+static guint
+real_get_view_id (NautilusFilesView *files_view)
+{
+ return NAUTILUS_VIEW_LIST_ID;
+}
+
+static void
+on_item_click_released_workaround (GtkGestureClick *gesture,
+ gint n_press,
+ gdouble x,
+ gdouble y,
+ gpointer user_data)
+{
+ NautilusViewCell *cell = user_data;
+ NautilusListView *self = NAUTILUS_LIST_VIEW (nautilus_view_cell_get_view (cell));
+ GdkModifierType modifiers;
+
+ modifiers = gtk_event_controller_get_current_event_state (GTK_EVENT_CONTROLLER (gesture));
+ if (n_press == 1 &&
+ modifiers & (GDK_CONTROL_MASK | GDK_SHIFT_MASK))
+ {
+ NautilusViewModel *model;
+ g_autoptr (NautilusViewItem) item = NULL;
+ guint i;
+
+ model = nautilus_list_base_get_model (NAUTILUS_LIST_BASE (self));
+ item = nautilus_view_cell_get_item (cell);
+ g_return_if_fail (item != NULL);
+ i = nautilus_view_model_get_index (model, item);
+
+ gtk_widget_activate_action (GTK_WIDGET (cell),
+ "list.select-item",
+ "(ubb)",
+ i,
+ modifiers & GDK_CONTROL_MASK,
+ modifiers & GDK_SHIFT_MASK);
+ }
+}
+
+/* This whole event handler is a workaround to a GtkColumnView bug: it
+ * activates the list|select-item action twice, which may cause the
+ * second activation to reverse the effects of the first:
+ * https://gitlab.gnome.org/GNOME/gtk/-/issues/4819
+ *
+ * As a workaround, we are going to activate the action a 3rd time.
+ * The third time is the charm, as the saying goes. */
+static void
+setup_selection_click_workaround (NautilusViewCell *cell)
+{
+ GtkEventController *controller;
+
+ controller = GTK_EVENT_CONTROLLER (gtk_gesture_click_new ());
+ gtk_widget_add_controller (GTK_WIDGET (cell), controller);
+ gtk_event_controller_set_propagation_phase (controller, GTK_PHASE_BUBBLE);
+ gtk_gesture_single_set_button (GTK_GESTURE_SINGLE (controller), GDK_BUTTON_PRIMARY);
+ g_signal_connect (controller, "released", G_CALLBACK (on_item_click_released_workaround), cell);
+}
+
+static void
+setup_name_cell (GtkSignalListItemFactory *factory,
+ GtkListItem *listitem,
+ gpointer user_data)
+{
+ NautilusListView *self = NAUTILUS_LIST_VIEW (user_data);
+ NautilusViewCell *cell;
+
+ cell = nautilus_name_cell_new (NAUTILUS_LIST_BASE (self));
+ setup_cell_common (listitem, cell);
+
+ nautilus_name_cell_set_path (NAUTILUS_NAME_CELL (cell),
+ self->path_attribute_q,
+ self->file_path_base_location);
+ if (NAUTILUS_IS_SEARCH_DIRECTORY (nautilus_files_view_get_model (NAUTILUS_FILES_VIEW (self))))
+ {
+ nautilus_name_cell_show_snippet (NAUTILUS_NAME_CELL (cell));
+ }
+
+ setup_selection_click_workaround (cell);
+}
+
+static void
+bind_name_cell (GtkSignalListItemFactory *factory,
+ GtkListItem *listitem,
+ gpointer user_data)
+{
+ GtkWidget *cell;
+ NautilusViewItem *item;
+
+ cell = gtk_list_item_get_child (listitem);
+ item = NAUTILUS_VIEW_ITEM (gtk_list_item_get_item (listitem));
+
+ nautilus_view_item_set_item_ui (item, gtk_list_item_get_child (listitem));
+
+ if (nautilus_view_cell_once (NAUTILUS_VIEW_CELL (cell)))
+ {
+ GtkWidget *row_widget;
+
+ /* At the time of ::setup emission, the item ui has got no parent yet,
+ * that's why we need to complete the widget setup process here, on the
+ * first time ::bind is emitted. */
+ row_widget = gtk_widget_get_parent (gtk_widget_get_parent (cell));
+
+ gtk_accessible_update_relation (GTK_ACCESSIBLE (row_widget),
+ GTK_ACCESSIBLE_RELATION_LABELLED_BY, cell, NULL,
+ -1);
+ }
+}
+
+static void
+unbind_name_cell (GtkSignalListItemFactory *factory,
+ GtkListItem *listitem,
+ gpointer user_data)
+{
+ NautilusViewItem *item;
+
+ item = NAUTILUS_VIEW_ITEM (gtk_list_item_get_item (listitem));
+ g_return_if_fail (NAUTILUS_IS_VIEW_ITEM (item));
+
+ nautilus_view_item_set_item_ui (item, NULL);
+}
+
+static void
+setup_star_cell (GtkSignalListItemFactory *factory,
+ GtkListItem *listitem,
+ gpointer user_data)
+{
+ NautilusViewCell *cell;
+
+ cell = nautilus_star_cell_new (NAUTILUS_LIST_BASE (user_data));
+ setup_cell_common (listitem, cell);
+ setup_selection_click_workaround (cell);
+}
+
+static void
+setup_label_cell (GtkSignalListItemFactory *factory,
+ GtkListItem *listitem,
+ gpointer user_data)
+{
+ NautilusListView *self = user_data;
+ NautilusColumn *nautilus_column;
+ NautilusViewCell *cell;
+
+ nautilus_column = g_hash_table_lookup (self->factory_to_column_map, factory);
+
+ cell = nautilus_label_cell_new (NAUTILUS_LIST_BASE (user_data), nautilus_column);
+ setup_cell_common (listitem, cell);
+ setup_selection_click_workaround (cell);
+}
+
+static void
+setup_view_columns (NautilusListView *self)
+{
+ GtkListItemFactory *factory;
+ g_autolist (NautilusColumn) nautilus_columns = NULL;
+
+ nautilus_columns = nautilus_get_all_columns ();
+
+ self->factory_to_column_map = g_hash_table_new_full (g_direct_hash,
+ g_direct_equal,
+ NULL,
+ g_object_unref);
+ self->all_view_columns_hash = g_hash_table_new_full (g_str_hash,
+ g_str_equal,
+ (GDestroyNotify) g_free,
+ g_object_unref);
+
+ for (GList *l = nautilus_columns; l != NULL; l = l->next)
+ {
+ NautilusColumn *nautilus_column = NAUTILUS_COLUMN (l->data);
+ SortData *data;
+ g_autofree gchar *name = NULL;
+ g_autofree gchar *label = NULL;
+ GQuark attribute_q = 0;
+ GtkSortType sort_order;
+ g_autoptr (GtkCustomSorter) sorter = NULL;
+ g_autoptr (GtkColumnViewColumn) view_column = NULL;
+
+ g_object_get (nautilus_column,
+ "name", &name,
+ "label", &label,
+ "attribute_q", &attribute_q,
+ "default-sort-order", &sort_order,
+ NULL);
+
+ data = g_new0 (SortData, 1);
+ data->attribute = attribute_q;
+ data->view = self;
+
+ sorter = gtk_custom_sorter_new (nautilus_list_view_sort,
+ data,
+ g_free);
+
+ factory = gtk_signal_list_item_factory_new ();
+ view_column = gtk_column_view_column_new (NULL, factory);
+ gtk_column_view_column_set_expand (view_column, FALSE);
+ gtk_column_view_column_set_resizable (view_column, TRUE);
+ gtk_column_view_column_set_title (view_column, label);
+ gtk_column_view_column_set_sorter (view_column, GTK_SORTER (sorter));
+
+ if (!strcmp (name, "name"))
+ {
+ g_signal_connect (factory, "setup", G_CALLBACK (setup_name_cell), self);
+ g_signal_connect (factory, "bind", G_CALLBACK (bind_name_cell), self);
+ g_signal_connect (factory, "unbind", G_CALLBACK (unbind_name_cell), self);
+
+ gtk_column_view_column_set_expand (view_column, TRUE);
+ }
+ else if (g_strcmp0 (name, "starred") == 0)
+ {
+ g_signal_connect (factory, "setup", G_CALLBACK (setup_star_cell), self);
+
+ gtk_column_view_column_set_title (view_column, "");
+ gtk_column_view_column_set_resizable (view_column, FALSE);
+
+ self->star_column = view_column;
+ }
+ else
+ {
+ g_signal_connect (factory, "setup", G_CALLBACK (setup_label_cell), self);
+ }
+
+ g_hash_table_insert (self->factory_to_column_map,
+ factory,
+ g_object_ref (nautilus_column));
+ g_hash_table_insert (self->all_view_columns_hash,
+ g_steal_pointer (&name),
+ g_steal_pointer (&view_column));
+ }
+}
+
+static void
+nautilus_list_view_init (NautilusListView *self)
+{
+ NautilusViewModel *model;
+ GtkWidget *content_widget;
+ g_autoptr (GtkCustomSorter) directories_sorter = NULL;
+ g_autoptr (GtkMultiSorter) sorter = NULL;
+
+ gtk_widget_add_css_class (GTK_WIDGET (self), "nautilus-list-view");
+
+ g_signal_connect_object (nautilus_list_view_preferences,
+ "changed::" NAUTILUS_PREFERENCES_LIST_VIEW_DEFAULT_VISIBLE_COLUMNS,
+ G_CALLBACK (update_columns_settings_from_metadata_and_preferences),
+ self,
+ G_CONNECT_SWAPPED);
+ g_signal_connect_object (nautilus_list_view_preferences,
+ "changed::" NAUTILUS_PREFERENCES_LIST_VIEW_DEFAULT_COLUMN_ORDER,
+ G_CALLBACK (update_columns_settings_from_metadata_and_preferences),
+ self,
+ G_CONNECT_SWAPPED);
+
+ content_widget = nautilus_files_view_get_content_widget (NAUTILUS_FILES_VIEW (self));
+
+ self->view_ui = create_view_ui (self);
+ nautilus_list_base_setup_gestures (NAUTILUS_LIST_BASE (self));
+
+ setup_view_columns (self);
+
+ self->directories_first = nautilus_files_view_should_sort_directories_first (NAUTILUS_FILES_VIEW (self));
+ directories_sorter = gtk_custom_sorter_new (sort_directories_func, &self->directories_first, NULL);
+
+ sorter = gtk_multi_sorter_new ();
+ gtk_multi_sorter_append (sorter, g_object_ref (GTK_SORTER (directories_sorter)));
+ gtk_multi_sorter_append (sorter, g_object_ref (gtk_column_view_get_sorter (self->view_ui)));
+ g_signal_connect_object (sorter, "changed", G_CALLBACK (on_sorter_changed), self, 0);
+ g_signal_connect_object (sorter, "changed", G_CALLBACK (on_after_sorter_changed), self, G_CONNECT_AFTER);
+
+ model = nautilus_list_base_get_model (NAUTILUS_LIST_BASE (self));
+ nautilus_view_model_set_sorter (model, GTK_SORTER (sorter));
+
+ gtk_scrolled_window_set_child (GTK_SCROLLED_WINDOW (content_widget),
+ GTK_WIDGET (self->view_ui));
+
+ self->action_group = nautilus_files_view_get_action_group (NAUTILUS_FILES_VIEW (self));
+ g_action_map_add_action_entries (G_ACTION_MAP (self->action_group),
+ list_view_entries,
+ G_N_ELEMENTS (list_view_entries),
+ self);
+
+ self->zoom_level = get_default_zoom_level ();
+ g_action_group_change_action_state (nautilus_files_view_get_action_group (NAUTILUS_FILES_VIEW (self)),
+ "zoom-to-level", g_variant_new_int32 (self->zoom_level));
+}
+
+static void
+nautilus_list_view_dispose (GObject *object)
+{
+ NautilusListView *self = NAUTILUS_LIST_VIEW (object);
+ NautilusViewModel *model;
+
+ model = nautilus_list_base_get_model (NAUTILUS_LIST_BASE (self));
+ nautilus_view_model_set_sorter (model, NULL);
+
+ g_clear_object (&self->file_path_base_location);
+ g_clear_pointer (&self->factory_to_column_map, g_hash_table_destroy);
+ g_clear_pointer (&self->all_view_columns_hash, g_hash_table_destroy);
+
+ G_OBJECT_CLASS (nautilus_list_view_parent_class)->dispose (object);
+}
+
+static void
+nautilus_list_view_finalize (GObject *object)
+{
+ G_OBJECT_CLASS (nautilus_list_view_parent_class)->finalize (object);
+}
+
+static void
+nautilus_list_view_class_init (NautilusListViewClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ NautilusFilesViewClass *files_view_class = NAUTILUS_FILES_VIEW_CLASS (klass);
+ NautilusListBaseClass *list_base_view_class = NAUTILUS_LIST_BASE_CLASS (klass);
+
+ object_class->dispose = nautilus_list_view_dispose;
+ object_class->finalize = nautilus_list_view_finalize;
+
+ files_view_class->begin_loading = real_begin_loading;
+ files_view_class->bump_zoom_level = real_bump_zoom_level;
+ files_view_class->can_zoom_in = real_can_zoom_in;
+ files_view_class->can_zoom_out = real_can_zoom_out;
+ files_view_class->sort_directories_first_changed = real_sort_directories_first_changed;
+ files_view_class->get_view_id = real_get_view_id;
+ files_view_class->restore_standard_zoom_level = real_restore_standard_zoom_level;
+ files_view_class->is_zoom_level_default = real_is_zoom_level_default;
+
+ list_base_view_class->get_icon_size = real_get_icon_size;
+ list_base_view_class->get_view_ui = real_get_view_ui;
+ list_base_view_class->scroll_to_item = real_scroll_to_item;
+}
+
+NautilusListView *
+nautilus_list_view_new (NautilusWindowSlot *slot)
+{
+ return g_object_new (NAUTILUS_TYPE_LIST_VIEW,
+ "window-slot", slot,
+ NULL);
+}
diff --git a/src/nautilus-list-view.h b/src/nautilus-list-view.h
new file mode 100644
index 0000000..8c21336
--- /dev/null
+++ b/src/nautilus-list-view.h
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2000 Eazel, Inc.
+ * Copyright (C) 2001, 2002 Anders Carlsson <andersca@gnu.org>
+ * Copyright (C) 2022 GNOME project contributors
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#pragma once
+
+#include "nautilus-list-base.h"
+#include "nautilus-window-slot.h"
+
+G_BEGIN_DECLS
+
+#define NAUTILUS_TYPE_LIST_VIEW (nautilus_list_view_get_type())
+
+G_DECLARE_FINAL_TYPE (NautilusListView, nautilus_list_view, NAUTILUS, LIST_VIEW, NautilusListBase)
+
+NautilusListView *nautilus_list_view_new (NautilusWindowSlot *slot);
+
+G_END_DECLS
diff --git a/src/nautilus-location-entry.c b/src/nautilus-location-entry.c
new file mode 100644
index 0000000..4ae027c
--- /dev/null
+++ b/src/nautilus-location-entry.c
@@ -0,0 +1,845 @@
+/*
+ * Nautilus
+ *
+ * Copyright (C) 2000 Eazel, Inc.
+ *
+ * Nautilus is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * Nautilus is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; see the file COPYING. If not,
+ * see <http://www.gnu.org/licenses/>.
+ *
+ * Author: Maciej Stachowiak <mjs@eazel.com>
+ * Ettore Perazzoli <ettore@gnu.org>
+ * Michael Meeks <michael@nuclecu.unam.mx>
+ * Andy Hertzfeld <andy@eazel.com>
+ *
+ */
+
+/* nautilus-location-bar.c - Location bar for Nautilus
+ */
+
+#include <config.h>
+#include "nautilus-location-entry.h"
+
+#include "nautilus-application.h"
+#include "nautilus-window.h"
+#include <gtk/gtk.h>
+#include <gdk/gdkkeysyms.h>
+#include <glib/gi18n.h>
+#include <gio/gio.h>
+#include "nautilus-file-utilities.h"
+#include "nautilus-clipboard.h"
+#include <eel/eel-stock-dialogs.h>
+#include <eel/eel-string.h>
+#include <eel/eel-vfs-extensions.h>
+#include <stdio.h>
+#include <string.h>
+
+
+typedef struct _NautilusLocationEntryPrivate
+{
+ char *current_directory;
+ GFilenameCompleter *completer;
+
+ guint idle_id;
+ gboolean idle_insert_completion;
+
+ GFile *last_location;
+
+ gboolean has_special_text;
+ NautilusLocationEntryAction secondary_action;
+
+ GtkEventController *controller;
+
+ GtkEntryCompletion *completion;
+ GtkListStore *completions_store;
+ GtkCellRenderer *completion_cell;
+} NautilusLocationEntryPrivate;
+
+enum
+{
+ CANCEL,
+ LOCATION_CHANGED,
+ LAST_SIGNAL
+};
+
+static guint signals[LAST_SIGNAL];
+
+G_DEFINE_TYPE_WITH_PRIVATE (NautilusLocationEntry, nautilus_location_entry, GTK_TYPE_ENTRY);
+
+static void on_after_insert_text (GtkEditable *editable,
+ const gchar *text,
+ gint length,
+ gint *position,
+ gpointer data);
+
+static void on_after_delete_text (GtkEditable *editable,
+ gint start_pos,
+ gint end_pos,
+ gpointer data);
+
+static GFile *
+nautilus_location_entry_get_location (NautilusLocationEntry *entry)
+{
+ char *user_location;
+ GFile *location;
+
+ user_location = gtk_editable_get_chars (GTK_EDITABLE (entry), 0, -1);
+ location = g_file_parse_name (user_location);
+ g_free (user_location);
+
+ return location;
+}
+
+static void
+nautilus_location_entry_set_text (NautilusLocationEntry *entry,
+ const char *new_text)
+{
+ GtkEditable *delegate;
+
+ delegate = gtk_editable_get_delegate (GTK_EDITABLE (entry));
+ g_signal_handlers_block_by_func (delegate, G_CALLBACK (on_after_insert_text), entry);
+ g_signal_handlers_block_by_func (delegate, G_CALLBACK (on_after_delete_text), entry);
+
+ gtk_editable_set_text (GTK_EDITABLE (entry), new_text);
+
+ g_signal_handlers_unblock_by_func (delegate, G_CALLBACK (on_after_insert_text), entry);
+ g_signal_handlers_unblock_by_func (delegate, G_CALLBACK (on_after_delete_text), entry);
+}
+
+static void
+nautilus_location_entry_insert_prefix (NautilusLocationEntry *entry,
+ GtkEntryCompletion *completion)
+{
+ GtkEditable *delegate;
+
+ delegate = gtk_editable_get_delegate (GTK_EDITABLE (entry));
+ g_signal_handlers_block_by_func (delegate, G_CALLBACK (on_after_insert_text), entry);
+
+ gtk_entry_completion_insert_prefix (completion);
+
+ g_signal_handlers_unblock_by_func (delegate, G_CALLBACK (on_after_insert_text), entry);
+}
+
+static void
+emit_location_changed (NautilusLocationEntry *entry)
+{
+ GFile *location;
+
+ location = nautilus_location_entry_get_location (entry);
+ g_signal_emit (entry, signals[LOCATION_CHANGED], 0, location);
+ g_object_unref (location);
+}
+
+static void
+nautilus_location_entry_update_action (NautilusLocationEntry *entry)
+{
+ NautilusLocationEntryPrivate *priv;
+ const char *current_text;
+ GFile *location;
+
+ priv = nautilus_location_entry_get_instance_private (entry);
+
+ if (priv->last_location == NULL)
+ {
+ nautilus_location_entry_set_secondary_action (entry,
+ NAUTILUS_LOCATION_ENTRY_ACTION_GOTO);
+ return;
+ }
+
+ current_text = gtk_editable_get_text (GTK_EDITABLE (entry));
+ location = g_file_parse_name (current_text);
+
+ if (g_file_equal (priv->last_location, location))
+ {
+ nautilus_location_entry_set_secondary_action (entry,
+ NAUTILUS_LOCATION_ENTRY_ACTION_CLEAR);
+ }
+ else
+ {
+ nautilus_location_entry_set_secondary_action (entry,
+ NAUTILUS_LOCATION_ENTRY_ACTION_GOTO);
+ }
+
+ g_object_unref (location);
+}
+
+static int
+get_editable_number_of_chars (GtkEditable *editable)
+{
+ char *text;
+ int length;
+
+ text = gtk_editable_get_chars (editable, 0, -1);
+ length = g_utf8_strlen (text, -1);
+ g_free (text);
+ return length;
+}
+
+static void
+set_position_and_selection_to_end (GtkEditable *editable)
+{
+ int end;
+
+ end = get_editable_number_of_chars (editable);
+ gtk_editable_select_region (editable, end, end);
+ gtk_editable_set_position (editable, end);
+}
+
+static void
+nautilus_location_entry_update_current_uri (NautilusLocationEntry *entry,
+ const char *uri)
+{
+ NautilusLocationEntryPrivate *priv;
+
+ priv = nautilus_location_entry_get_instance_private (entry);
+
+ g_free (priv->current_directory);
+ priv->current_directory = g_strdup (uri);
+
+ nautilus_location_entry_set_text (entry, uri);
+ set_position_and_selection_to_end (GTK_EDITABLE (entry));
+}
+
+void
+nautilus_location_entry_set_location (NautilusLocationEntry *entry,
+ GFile *location)
+{
+ NautilusLocationEntryPrivate *priv;
+ gchar *uri, *formatted_uri;
+
+ g_assert (location != NULL);
+
+ priv = nautilus_location_entry_get_instance_private (entry);
+
+ /* Note: This is called in reaction to external changes, and
+ * thus should not emit the LOCATION_CHANGED signal. */
+ uri = g_file_get_uri (location);
+ formatted_uri = g_file_get_parse_name (location);
+
+ if (eel_uri_is_search (uri))
+ {
+ nautilus_location_entry_set_special_text (entry, "");
+ }
+ else
+ {
+ nautilus_location_entry_update_current_uri (entry, formatted_uri);
+ }
+
+ /* remember the original location for later comparison */
+ if (!priv->last_location ||
+ !g_file_equal (priv->last_location, location))
+ {
+ g_clear_object (&priv->last_location);
+ priv->last_location = g_object_ref (location);
+ }
+
+ nautilus_location_entry_update_action (entry);
+
+ /* invalidate the completions list */
+ gtk_list_store_clear (priv->completions_store);
+
+ g_free (uri);
+ g_free (formatted_uri);
+}
+
+static void
+set_prefix_dimming (GtkCellRenderer *completion_cell,
+ char *user_location)
+{
+ g_autofree char *location_basename = NULL;
+ PangoAttrList *attrs;
+ PangoAttribute *attr;
+
+ /* Dim the prefixes of the completion rows, leaving the basenames
+ * highlighted. This makes it easier to find what you're looking for.
+ *
+ * Perhaps a better solution would be to *only* show the basenames, but
+ * it would take a reimplementation of GtkEntryCompletion to align the
+ * popover. */
+
+ location_basename = g_path_get_basename (user_location);
+
+ attrs = pango_attr_list_new ();
+
+ /* 55% opacity. This is the same as the dim-label style class in Adwaita. */
+ attr = pango_attr_foreground_alpha_new (36045);
+ attr->end_index = strlen (user_location) - strlen (location_basename);
+ pango_attr_list_insert (attrs, attr);
+
+ g_object_set (completion_cell, "attributes", attrs, NULL);
+ pango_attr_list_unref (attrs);
+}
+
+static gboolean
+position_and_selection_are_at_end (GtkEditable *editable)
+{
+ int end;
+ int start_sel, end_sel;
+
+ end = get_editable_number_of_chars (editable);
+ if (gtk_editable_get_selection_bounds (editable, &start_sel, &end_sel))
+ {
+ if (start_sel != end || end_sel != end)
+ {
+ return FALSE;
+ }
+ }
+ return gtk_editable_get_position (editable) == end;
+}
+
+/* Update the path completions list based on the current text of the entry. */
+static gboolean
+update_completions_store (gpointer callback_data)
+{
+ NautilusLocationEntry *entry;
+ NautilusLocationEntryPrivate *priv;
+ GtkEditable *editable;
+ g_autofree char *absolute_location = NULL;
+ g_autofree char *user_location = NULL;
+ gboolean is_relative = FALSE;
+ int start_sel;
+ g_autofree char *uri_scheme = NULL;
+ g_auto (GStrv) completions = NULL;
+ char *completion;
+ int i;
+ GtkTreeIter iter;
+ int current_dir_strlen;
+
+ entry = NAUTILUS_LOCATION_ENTRY (callback_data);
+ priv = nautilus_location_entry_get_instance_private (entry);
+ editable = GTK_EDITABLE (entry);
+
+ priv->idle_id = 0;
+
+ /* Only do completions when we are typing at the end of the
+ * text. */
+ if (!position_and_selection_are_at_end (editable))
+ {
+ return FALSE;
+ }
+
+ if (gtk_editable_get_selection_bounds (editable, &start_sel, NULL))
+ {
+ user_location = gtk_editable_get_chars (editable, 0, start_sel);
+ }
+ else
+ {
+ user_location = gtk_editable_get_chars (editable, 0, -1);
+ }
+
+ g_strstrip (user_location);
+ set_prefix_dimming (priv->completion_cell, user_location);
+
+ uri_scheme = g_uri_parse_scheme (user_location);
+
+ if (!g_path_is_absolute (user_location) && uri_scheme == NULL && user_location[0] != '~')
+ {
+ is_relative = TRUE;
+ absolute_location = g_build_filename (priv->current_directory, user_location, NULL);
+ }
+ else
+ {
+ absolute_location = g_steal_pointer (&user_location);
+ }
+
+ completions = g_filename_completer_get_completions (priv->completer, absolute_location);
+
+ /* populate the completions model */
+ gtk_list_store_clear (priv->completions_store);
+
+ current_dir_strlen = strlen (priv->current_directory);
+ for (i = 0; completions[i] != NULL; i++)
+ {
+ completion = completions[i];
+
+ if (is_relative && strlen (completion) >= current_dir_strlen)
+ {
+ /* For relative paths, we need to strip the current directory
+ * (and the trailing slash) so the completions will match what's
+ * in the text entry */
+ completion += current_dir_strlen;
+ if (G_IS_DIR_SEPARATOR (completion[0]))
+ {
+ completion++;
+ }
+ }
+
+ gtk_list_store_append (priv->completions_store, &iter);
+ gtk_list_store_set (priv->completions_store, &iter, 0, completion, -1);
+ }
+
+ /* refilter the completions dropdown */
+ gtk_entry_completion_complete (priv->completion);
+
+ if (priv->idle_insert_completion)
+ {
+ /* insert the completion */
+ nautilus_location_entry_insert_prefix (entry, priv->completion);
+ }
+
+ return FALSE;
+}
+
+static void
+got_completion_data_callback (GFilenameCompleter *completer,
+ NautilusLocationEntry *entry)
+{
+ NautilusLocationEntryPrivate *priv;
+
+ priv = nautilus_location_entry_get_instance_private (entry);
+
+ if (priv->idle_id)
+ {
+ g_source_remove (priv->idle_id);
+ priv->idle_id = 0;
+ }
+ update_completions_store (entry);
+}
+
+static void
+finalize (GObject *object)
+{
+ NautilusLocationEntry *entry;
+ NautilusLocationEntryPrivate *priv;
+
+ entry = NAUTILUS_LOCATION_ENTRY (object);
+ priv = nautilus_location_entry_get_instance_private (entry);
+
+ g_object_unref (priv->completer);
+
+ g_clear_object (&priv->last_location);
+ g_clear_object (&priv->completion);
+ g_clear_object (&priv->completions_store);
+ g_free (priv->current_directory);
+
+ G_OBJECT_CLASS (nautilus_location_entry_parent_class)->finalize (object);
+}
+
+static void
+nautilus_location_entry_dispose (GObject *object)
+{
+ NautilusLocationEntry *entry;
+ NautilusLocationEntryPrivate *priv;
+
+ entry = NAUTILUS_LOCATION_ENTRY (object);
+ priv = nautilus_location_entry_get_instance_private (entry);
+
+ /* cancel the pending idle call, if any */
+ if (priv->idle_id != 0)
+ {
+ g_source_remove (priv->idle_id);
+ priv->idle_id = 0;
+ }
+
+
+ G_OBJECT_CLASS (nautilus_location_entry_parent_class)->dispose (object);
+}
+
+static void
+on_has_focus_changed (GObject *object,
+ GParamSpec *pspec,
+ gpointer user_data)
+{
+ NautilusLocationEntry *entry;
+ NautilusLocationEntryPrivate *priv;
+
+ if (!gtk_widget_has_focus (GTK_WIDGET (object)))
+ {
+ return;
+ }
+
+ entry = NAUTILUS_LOCATION_ENTRY (object);
+ priv = nautilus_location_entry_get_instance_private (entry);
+
+ /* The entry has text which is not worth preserving on focus-in. */
+ if (priv->has_special_text)
+ {
+ nautilus_location_entry_set_text (entry, "");
+ }
+}
+
+static void
+nautilus_location_entry_text_changed (NautilusLocationEntry *entry,
+ GParamSpec *pspec)
+{
+ NautilusLocationEntryPrivate *priv;
+
+ priv = nautilus_location_entry_get_instance_private (entry);
+
+ priv->has_special_text = FALSE;
+}
+
+static void
+nautilus_location_entry_icon_release (GtkEntry *gentry,
+ GtkEntryIconPosition position,
+ gpointer unused)
+{
+ NautilusLocationEntry *entry;
+ NautilusLocationEntryPrivate *priv;
+
+ entry = NAUTILUS_LOCATION_ENTRY (gentry);
+ priv = nautilus_location_entry_get_instance_private (entry);
+
+ switch (priv->secondary_action)
+ {
+ case NAUTILUS_LOCATION_ENTRY_ACTION_GOTO:
+ {
+ g_signal_emit_by_name (gentry, "activate", gentry);
+ }
+ break;
+
+ case NAUTILUS_LOCATION_ENTRY_ACTION_CLEAR:
+ {
+ nautilus_location_entry_set_text (entry, "");
+ }
+ break;
+
+ default:
+ {
+ g_assert_not_reached ();
+ }
+ }
+}
+
+static gboolean
+nautilus_location_entry_key_pressed (GtkEventControllerKey *controller,
+ unsigned int keyval,
+ unsigned int keycode,
+ GdkModifierType state,
+ gpointer user_data)
+{
+ GtkWidget *widget;
+ GtkEditable *editable;
+ gboolean selected;
+
+
+ widget = gtk_event_controller_get_widget (GTK_EVENT_CONTROLLER (controller));
+ editable = GTK_EDITABLE (widget);
+ selected = gtk_editable_get_selection_bounds (editable, NULL, NULL);
+
+ if (!gtk_editable_get_editable (editable))
+ {
+ return GDK_EVENT_PROPAGATE;
+ }
+
+ /* The location bar entry wants TAB to work kind of
+ * like it does in the shell for command completion,
+ * so if we get a tab and there's a selection, we
+ * should position the insertion point at the end of
+ * the selection.
+ */
+ if (keyval == GDK_KEY_Tab && !(state & (GDK_CONTROL_MASK | GDK_SHIFT_MASK)))
+ {
+ if (selected)
+ {
+ int position;
+
+ position = strlen (gtk_editable_get_text (GTK_EDITABLE (editable)));
+ gtk_editable_select_region (editable, position, position);
+ }
+ else
+ {
+ gtk_widget_error_bell (widget);
+ }
+
+ return GDK_EVENT_STOP;
+ }
+
+ if ((keyval == GDK_KEY_Right || keyval == GDK_KEY_End) &&
+ !(state & (GDK_SHIFT_MASK | GDK_CONTROL_MASK)) && selected)
+ {
+ set_position_and_selection_to_end (editable);
+ }
+
+ return GDK_EVENT_PROPAGATE;
+}
+
+static void
+after_text_change (NautilusLocationEntry *self,
+ gboolean insert)
+{
+ NautilusLocationEntryPrivate *priv = nautilus_location_entry_get_instance_private (self);
+
+ /* Only insert a completion if a character was typed. Otherwise,
+ * update the completions store (i.e. in case backspace was pressed)
+ * but don't insert the completion into the entry. */
+ priv->idle_insert_completion = insert;
+
+ /* Do the expand at idle time to avoid slowing down typing when the
+ * directory is large. */
+ if (priv->idle_id == 0)
+ {
+ priv->idle_id = g_idle_add (update_completions_store, self);
+ }
+}
+
+static void
+on_after_insert_text (GtkEditable *editable,
+ const gchar *text,
+ gint length,
+ gint *position,
+ gpointer data)
+{
+ NautilusLocationEntry *self = NAUTILUS_LOCATION_ENTRY (data);
+
+ after_text_change (self, TRUE);
+}
+
+static void
+on_after_delete_text (GtkEditable *editable,
+ gint start_pos,
+ gint end_pos,
+ gpointer data)
+{
+ NautilusLocationEntry *self = NAUTILUS_LOCATION_ENTRY (data);
+
+ after_text_change (self, FALSE);
+}
+
+static void
+nautilus_location_entry_activate (GtkEntry *entry)
+{
+ NautilusLocationEntry *loc_entry;
+ NautilusLocationEntryPrivate *priv;
+ const gchar *entry_text;
+ gchar *full_path, *uri_scheme = NULL;
+ g_autofree char *path = NULL;
+
+ loc_entry = NAUTILUS_LOCATION_ENTRY (entry);
+ priv = nautilus_location_entry_get_instance_private (loc_entry);
+ entry_text = gtk_editable_get_text (GTK_EDITABLE (entry));
+ path = g_strdup (entry_text);
+ path = g_strchug (path);
+ path = g_strchomp (path);
+
+ if (path != NULL && *path != '\0')
+ {
+ uri_scheme = g_uri_parse_scheme (path);
+
+ if (!g_path_is_absolute (path) && uri_scheme == NULL && path[0] != '~')
+ {
+ /* Fix non absolute paths */
+ full_path = g_build_filename (priv->current_directory, path, NULL);
+ nautilus_location_entry_set_text (loc_entry, full_path);
+ g_free (full_path);
+ }
+
+ g_free (uri_scheme);
+ }
+}
+
+static void
+nautilus_location_entry_cancel (NautilusLocationEntry *entry)
+{
+ NautilusLocationEntryPrivate *priv;
+
+ priv = nautilus_location_entry_get_instance_private (entry);
+
+ nautilus_location_entry_set_location (entry, priv->last_location);
+}
+
+static void
+nautilus_location_entry_class_init (NautilusLocationEntryClass *class)
+{
+ GObjectClass *gobject_class;
+ GtkEntryClass *entry_class;
+ g_autoptr (GtkShortcut) shortcut = NULL;
+
+ gobject_class = G_OBJECT_CLASS (class);
+ gobject_class->dispose = nautilus_location_entry_dispose;
+ gobject_class->finalize = finalize;
+
+ entry_class = GTK_ENTRY_CLASS (class);
+ entry_class->activate = nautilus_location_entry_activate;
+
+ class->cancel = nautilus_location_entry_cancel;
+
+ signals[CANCEL] = g_signal_new
+ ("cancel",
+ G_TYPE_FROM_CLASS (class),
+ G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
+ G_STRUCT_OFFSET (NautilusLocationEntryClass,
+ cancel),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+
+ signals[LOCATION_CHANGED] = g_signal_new
+ ("location-changed",
+ G_TYPE_FROM_CLASS (class),
+ G_SIGNAL_RUN_LAST, 0,
+ NULL, NULL,
+ g_cclosure_marshal_generic,
+ G_TYPE_NONE, 1, G_TYPE_OBJECT);
+
+ shortcut = gtk_shortcut_new (gtk_keyval_trigger_new (GDK_KEY_Escape, 0),
+ gtk_signal_action_new ("cancel"));
+ gtk_widget_class_add_shortcut (GTK_WIDGET_CLASS (class), shortcut);
+}
+
+void
+nautilus_location_entry_set_secondary_action (NautilusLocationEntry *entry,
+ NautilusLocationEntryAction secondary_action)
+{
+ NautilusLocationEntryPrivate *priv;
+
+ priv = nautilus_location_entry_get_instance_private (entry);
+
+ if (priv->secondary_action == secondary_action)
+ {
+ return;
+ }
+
+ switch (secondary_action)
+ {
+ case NAUTILUS_LOCATION_ENTRY_ACTION_CLEAR:
+ {
+ gtk_entry_set_icon_from_icon_name (GTK_ENTRY (entry),
+ GTK_ENTRY_ICON_SECONDARY,
+ "edit-clear-symbolic");
+ }
+ break;
+
+ case NAUTILUS_LOCATION_ENTRY_ACTION_GOTO:
+ {
+ gtk_entry_set_icon_from_icon_name (GTK_ENTRY (entry),
+ GTK_ENTRY_ICON_SECONDARY,
+ "go-next-symbolic");
+ }
+ break;
+
+ default:
+ {
+ g_assert_not_reached ();
+ }
+ }
+ priv->secondary_action = secondary_action;
+}
+
+static void
+editable_activate_callback (GtkEntry *entry,
+ gpointer user_data)
+{
+ NautilusLocationEntry *self = user_data;
+ const char *entry_text;
+ g_autofree gchar *path = NULL;
+
+ entry_text = gtk_editable_get_text (GTK_EDITABLE (entry));
+ path = g_strdup (entry_text);
+ path = g_strchug (path);
+ path = g_strchomp (path);
+
+ if (path != NULL && *path != '\0')
+ {
+ nautilus_location_entry_set_text (self, path);
+ emit_location_changed (self);
+ }
+}
+
+static void
+editable_changed_callback (GtkEntry *entry,
+ gpointer user_data)
+{
+ nautilus_location_entry_update_action (NAUTILUS_LOCATION_ENTRY (entry));
+}
+
+static void
+nautilus_location_entry_init (NautilusLocationEntry *entry)
+{
+ NautilusLocationEntryPrivate *priv;
+ GtkEventController *controller;
+
+ priv = nautilus_location_entry_get_instance_private (entry);
+
+ priv->completer = g_filename_completer_new ();
+ g_filename_completer_set_dirs_only (priv->completer, TRUE);
+
+ nautilus_location_entry_set_secondary_action (entry,
+ NAUTILUS_LOCATION_ENTRY_ACTION_CLEAR);
+
+ g_signal_connect (entry, "notify::has-focus",
+ G_CALLBACK (on_has_focus_changed), NULL);
+
+ g_signal_connect (entry, "notify::text",
+ G_CALLBACK (nautilus_location_entry_text_changed), NULL);
+
+ g_signal_connect (entry, "icon-release",
+ G_CALLBACK (nautilus_location_entry_icon_release), NULL);
+
+ g_signal_connect (priv->completer, "got-completion-data",
+ G_CALLBACK (got_completion_data_callback), entry);
+
+ g_signal_connect_object (entry, "activate",
+ G_CALLBACK (editable_activate_callback), entry, G_CONNECT_AFTER);
+ g_signal_connect_object (entry, "changed",
+ G_CALLBACK (editable_changed_callback), entry, 0);
+
+ controller = gtk_event_controller_key_new ();
+ gtk_widget_add_controller (GTK_WIDGET (entry), controller);
+ /* In GTK3, the Tab key binding (for focus change) happens in the bubble
+ * phase, and we want to stop that from happening. After porting to GTK4
+ * we need to check whether this is still correct. */
+ gtk_event_controller_set_propagation_phase (controller, GTK_PHASE_BUBBLE);
+ g_signal_connect (controller, "key-pressed",
+ G_CALLBACK (nautilus_location_entry_key_pressed), NULL);
+
+ g_signal_connect_after (gtk_editable_get_delegate (GTK_EDITABLE (entry)),
+ "insert-text",
+ G_CALLBACK (on_after_insert_text),
+ entry);
+ g_signal_connect_after (gtk_editable_get_delegate (GTK_EDITABLE (entry)),
+ "delete-text",
+ G_CALLBACK (on_after_delete_text),
+ entry);
+
+ priv->completion = gtk_entry_completion_new ();
+ priv->completions_store = gtk_list_store_new (1, G_TYPE_STRING);
+ gtk_entry_completion_set_model (priv->completion, GTK_TREE_MODEL (priv->completions_store));
+
+ g_object_set (priv->completion,
+ "text-column", 0,
+ "inline-completion", FALSE,
+ "inline-selection", TRUE,
+ "popup-single-match", TRUE,
+ NULL);
+
+ priv->completion_cell = gtk_cell_renderer_text_new ();
+ g_object_set (priv->completion_cell, "xpad", 6, NULL);
+
+ gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (priv->completion), priv->completion_cell, FALSE);
+ gtk_cell_layout_add_attribute (GTK_CELL_LAYOUT (priv->completion), priv->completion_cell, "text", 0);
+
+ gtk_entry_set_completion (GTK_ENTRY (entry), priv->completion);
+}
+
+GtkWidget *
+nautilus_location_entry_new (void)
+{
+ GtkWidget *entry;
+
+ entry = GTK_WIDGET (g_object_new (NAUTILUS_TYPE_LOCATION_ENTRY, NULL));
+
+ return entry;
+}
+
+void
+nautilus_location_entry_set_special_text (NautilusLocationEntry *entry,
+ const char *special_text)
+{
+ NautilusLocationEntryPrivate *priv;
+
+ priv = nautilus_location_entry_get_instance_private (entry);
+
+ nautilus_location_entry_set_text (entry, special_text);
+ priv->has_special_text = TRUE;
+}
diff --git a/src/nautilus-location-entry.h b/src/nautilus-location-entry.h
new file mode 100644
index 0000000..bdff538
--- /dev/null
+++ b/src/nautilus-location-entry.h
@@ -0,0 +1,51 @@
+
+/*
+ * Nautilus
+ *
+ * Copyright (C) 2000 Eazel, Inc.
+ *
+ * Nautilus is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * Nautilus is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; see the file COPYING. If not,
+ * see <http://www.gnu.org/licenses/>.
+ *
+ * Author: Maciej Stachowiak <mjs@eazel.com>
+ * Ettore Perazzoli <ettore@gnu.org>
+ */
+
+#pragma once
+
+#include <gtk/gtk.h>
+
+#define NAUTILUS_TYPE_LOCATION_ENTRY nautilus_location_entry_get_type()
+G_DECLARE_DERIVABLE_TYPE (NautilusLocationEntry, nautilus_location_entry,
+ NAUTILUS, LOCATION_ENTRY,
+ GtkEntry)
+
+typedef struct _NautilusLocationEntryClass {
+ GtkEntryClass parent_class;
+ /* for GtkBindingSet */
+ void (* cancel) (NautilusLocationEntry *entry);
+} NautilusLocationEntryClass;
+
+typedef enum {
+ NAUTILUS_LOCATION_ENTRY_ACTION_GOTO,
+ NAUTILUS_LOCATION_ENTRY_ACTION_CLEAR
+} NautilusLocationEntryAction;
+
+GtkWidget* nautilus_location_entry_new (void);
+void nautilus_location_entry_set_special_text (NautilusLocationEntry *entry,
+ const char *special_text);
+void nautilus_location_entry_set_secondary_action (NautilusLocationEntry *entry,
+ NautilusLocationEntryAction secondary_action);
+void nautilus_location_entry_set_location (NautilusLocationEntry *entry,
+ GFile *location); \ No newline at end of file
diff --git a/src/nautilus-main.c b/src/nautilus-main.c
new file mode 100644
index 0000000..4c8bb93
--- /dev/null
+++ b/src/nautilus-main.c
@@ -0,0 +1,89 @@
+/*
+ * Nautilus
+ *
+ * Copyright (C) 1999, 2000 Red Hat, Inc.
+ * Copyright (C) 1999, 2000 Eazel, Inc.
+ *
+ * Nautilus is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * Nautilus is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Elliot Lee <sopwith@redhat.com>,
+ * Darin Adler <darin@bentspoon.com>,
+ * John Sullivan <sullivan@eazel.com>
+ *
+ */
+
+/* nautilus-main.c: Implementation of the routines that drive program lifecycle and main window creation/destruction. */
+
+#include <config.h>
+
+#include "nautilus-application.h"
+#include "nautilus-resources.h"
+
+#include "nautilus-debug.h"
+#include <eel/eel-debug.h>
+
+#include <glib/gi18n.h>
+#include <gtk/gtk.h>
+#include <gio/gdesktopappinfo.h>
+
+#include <libxml/parser.h>
+
+#ifdef HAVE_LOCALE_H
+#include <locale.h>
+#endif
+#ifdef HAVE_MALLOC_H
+#include <malloc.h>
+#endif
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+int
+main (int argc,
+ char *argv[])
+{
+ gint retval;
+ NautilusApplication *application;
+
+ if (g_getenv ("NAUTILUS_DEBUG") != NULL)
+ {
+ eel_make_warnings_and_criticals_stop_in_debugger ();
+ }
+
+ /* Initialize gettext support */
+ bindtextdomain (GETTEXT_PACKAGE, LOCALEDIR);
+ bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8");
+ textdomain (GETTEXT_PACKAGE);
+
+ g_set_prgname (APPLICATION_ID);
+
+ nautilus_register_resource ();
+ /* Run the nautilus application. */
+ application = nautilus_application_new ();
+
+ /* hold indefinitely if we're asked to persist */
+ if (g_getenv ("NAUTILUS_PERSIST") != NULL)
+ {
+ g_application_hold (G_APPLICATION (application));
+ }
+
+ retval = g_application_run (G_APPLICATION (application),
+ argc, argv);
+
+ g_object_unref (application);
+
+ eel_debug_shut_down ();
+
+ return retval;
+}
diff --git a/src/nautilus-metadata.c b/src/nautilus-metadata.c
new file mode 100644
index 0000000..e462298
--- /dev/null
+++ b/src/nautilus-metadata.c
@@ -0,0 +1,55 @@
+/* nautilus-metadata.c - metadata utils
+ *
+ * Copyright (C) 2009 Red Hatl, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+#include "nautilus-metadata.h"
+#include <glib.h>
+
+static char *used_metadata_names[] =
+{
+ NAUTILUS_METADATA_KEY_ICON_VIEW_SORT_BY,
+ NAUTILUS_METADATA_KEY_ICON_VIEW_SORT_REVERSED,
+ NAUTILUS_METADATA_KEY_LIST_VIEW_SORT_COLUMN,
+ NAUTILUS_METADATA_KEY_LIST_VIEW_SORT_REVERSED,
+ NAUTILUS_METADATA_KEY_LIST_VIEW_VISIBLE_COLUMNS,
+ NAUTILUS_METADATA_KEY_LIST_VIEW_COLUMN_ORDER,
+ NAUTILUS_METADATA_KEY_CUSTOM_ICON,
+ NAUTILUS_METADATA_KEY_CUSTOM_ICON_NAME,
+ NAUTILUS_METADATA_KEY_EMBLEMS,
+ NULL
+};
+
+guint
+nautilus_metadata_get_id (const char *metadata)
+{
+ static GHashTable *hash;
+ int i;
+
+ if (hash == NULL)
+ {
+ hash = g_hash_table_new (g_str_hash, g_str_equal);
+ for (i = 0; used_metadata_names[i] != NULL; i++)
+ {
+ g_hash_table_insert (hash,
+ used_metadata_names[i],
+ GINT_TO_POINTER (i + 1));
+ }
+ }
+
+ return GPOINTER_TO_INT (g_hash_table_lookup (hash, metadata));
+}
diff --git a/src/nautilus-metadata.h b/src/nautilus-metadata.h
new file mode 100644
index 0000000..299e3a8
--- /dev/null
+++ b/src/nautilus-metadata.h
@@ -0,0 +1,45 @@
+/*
+ nautilus-metadata.h: #defines and other metadata-related info
+
+ Copyright (C) 2000 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/>.
+
+ Author: John Sullivan <sullivan@eazel.com>
+*/
+
+#pragma once
+
+/* Keys for getting/setting Nautilus metadata. All metadata used in Nautilus
+ * should define its key here, so we can keep track of the whole set easily.
+ * Any updates here needs to be added in nautilus-metadata.c too.
+ */
+
+#include <glib.h>
+
+/* Per-file */
+
+#define NAUTILUS_METADATA_KEY_ICON_VIEW_SORT_BY "nautilus-icon-view-sort-by"
+#define NAUTILUS_METADATA_KEY_ICON_VIEW_SORT_REVERSED "nautilus-icon-view-sort-reversed"
+
+#define NAUTILUS_METADATA_KEY_LIST_VIEW_SORT_COLUMN "nautilus-list-view-sort-column"
+#define NAUTILUS_METADATA_KEY_LIST_VIEW_SORT_REVERSED "nautilus-list-view-sort-reversed"
+#define NAUTILUS_METADATA_KEY_LIST_VIEW_VISIBLE_COLUMNS "nautilus-list-view-visible-columns"
+#define NAUTILUS_METADATA_KEY_LIST_VIEW_COLUMN_ORDER "nautilus-list-view-column-order"
+
+#define NAUTILUS_METADATA_KEY_CUSTOM_ICON "custom-icon"
+#define NAUTILUS_METADATA_KEY_CUSTOM_ICON_NAME "custom-icon-name"
+#define NAUTILUS_METADATA_KEY_EMBLEMS "emblems"
+
+guint nautilus_metadata_get_id (const char *metadata);
diff --git a/src/nautilus-mime-actions.c b/src/nautilus-mime-actions.c
new file mode 100644
index 0000000..d4dfd16
--- /dev/null
+++ b/src/nautilus-mime-actions.c
@@ -0,0 +1,2325 @@
+/* nautilus-mime-actions.c - uri-specific versions of mime action functions
+ *
+ * Copyright (C) 2000, 2001 Eazel, Inc.
+ *
+ * The Gnome Library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * The Gnome Library 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
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with the Gnome Library; see the file COPYING.LIB. If not,
+ * see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Maciej Stachowiak <mjs@eazel.com>
+ */
+
+#include "nautilus-mime-actions.h"
+
+#include <eel/eel-stock-dialogs.h>
+#include <eel/eel-string.h>
+#include <glib.h>
+#include <glib/gi18n.h>
+#include <glib/gstdio.h>
+#include <string.h>
+
+#define DEBUG_FLAG NAUTILUS_DEBUG_MIME
+#include "nautilus-debug.h"
+
+#include "nautilus-application.h"
+#include "nautilus-enums.h"
+#include "nautilus-file.h"
+#include "nautilus-file-utilities.h"
+#include "nautilus-file-operations.h"
+#include "nautilus-global-preferences.h"
+#include "nautilus-metadata.h"
+#include "nautilus-program-choosing.h"
+#include "nautilus-signaller.h"
+#include "nautilus-ui-utilities.h"
+#include "nautilus-window.h"
+#include "nautilus-window-slot.h"
+
+typedef enum
+{
+ ACTIVATION_ACTION_LAUNCH,
+ ACTIVATION_ACTION_LAUNCH_IN_TERMINAL,
+ ACTIVATION_ACTION_OPEN_IN_VIEW,
+ ACTIVATION_ACTION_OPEN_IN_APPLICATION,
+ ACTIVATION_ACTION_EXTRACT,
+ ACTIVATION_ACTION_DO_NOTHING,
+} ActivationAction;
+
+typedef struct
+{
+ NautilusFile *file;
+ char *uri;
+} LaunchLocation;
+
+typedef struct
+{
+ GAppInfo *application;
+ GList *uris;
+} ApplicationLaunchParameters;
+
+typedef struct
+{
+ NautilusWindowSlot *slot;
+ gpointer window;
+ GtkWindow *parent_window;
+ GCancellable *cancellable;
+ GList *locations;
+ GList *mountables;
+ GList *start_mountables;
+ GList *not_mounted;
+ NautilusOpenFlags flags;
+ char *timed_wait_prompt;
+ gboolean timed_wait_active;
+ NautilusFileListHandle *files_handle;
+ gboolean tried_mounting;
+ char *activation_directory;
+ gboolean user_confirmation;
+ GQueue *open_in_view_files;
+ GQueue *open_in_app_uris;
+ GQueue *launch_files;
+ GQueue *launch_in_terminal_files;
+ GList *open_in_app_parameters;
+ GList *unhandled_open_in_app_uris;
+} ActivateParameters;
+
+typedef struct
+{
+ ActivateParameters *activation_params;
+ GQueue *uris;
+} ApplicationLaunchAsyncParameters;
+
+/* Microsoft mime types at https://blogs.msdn.microsoft.com/vsofficedeveloper/2008/05/08/office-2007-file-format-mime-types-for-http-content-streaming-2/ */
+struct
+{
+ char *name;
+ char *mimetypes[20];
+} mimetype_groups[] =
+{
+ {
+ N_("Anything"),
+ { NULL }
+ },
+ {
+ N_("Files"),
+ { "application/octet-stream",
+ "text/plain",
+ NULL}
+ },
+ {
+ N_("Folders"),
+ { "inode/directory",
+ NULL}
+ },
+ { N_("Documents"),
+ { "application/rtf",
+ "application/msword",
+ "application/vnd.sun.xml.writer",
+ "application/vnd.sun.xml.writer.global",
+ "application/vnd.sun.xml.writer.template",
+ "application/vnd.oasis.opendocument.text",
+ "application/vnd.oasis.opendocument.text-template",
+ "application/x-abiword",
+ "application/x-applix-word",
+ "application/x-mswrite",
+ "application/docbook+xml",
+ "application/x-kword",
+ "application/x-kword-crypt",
+ "application/x-lyx",
+ "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
+ NULL}},
+ { N_("Illustration"),
+ { "application/illustrator",
+ "application/vnd.corel-draw",
+ "application/vnd.stardivision.draw",
+ "application/vnd.oasis.opendocument.graphics",
+ "application/x-dia-diagram",
+ "application/x-karbon",
+ "application/x-killustrator",
+ "application/x-kivio",
+ "application/x-kontour",
+ "application/x-wpg",
+ NULL}},
+ { N_("Music"),
+ { "application/ogg",
+ "audio/x-vorbis+ogg",
+ "audio/ac3",
+ "audio/basic",
+ "audio/midi",
+ "audio/x-flac",
+ "audio/mp4",
+ "audio/mpeg",
+ "audio/x-mpeg",
+ "audio/x-ms-asx",
+ "audio/x-pn-realaudio",
+ NULL}},
+ { N_("PDF / PostScript"),
+ { "application/pdf",
+ "application/postscript",
+ "application/x-dvi",
+ "image/x-eps",
+ "image/vnd.djvu+multipage",
+ NULL}},
+ { N_("Picture"),
+ { "application/vnd.oasis.opendocument.image",
+ "application/x-krita",
+ "image/bmp",
+ "image/cgm",
+ "image/gif",
+ "image/jpeg",
+ "image/jpeg2000",
+ "image/png",
+ "image/svg+xml",
+ "image/tiff",
+ "image/x-compressed-xcf",
+ "image/x-pcx",
+ "image/x-photo-cd",
+ "image/x-psd",
+ "image/x-tga",
+ "image/x-xcf",
+ NULL}},
+ { N_("Presentation"),
+ { "application/vnd.ms-powerpoint",
+ "application/vnd.sun.xml.impress",
+ "application/vnd.oasis.opendocument.presentation",
+ "application/x-magicpoint",
+ "application/x-kpresenter",
+ "application/vnd.openxmlformats-officedocument.presentationml.presentation",
+ NULL}},
+ { N_("Spreadsheet"),
+ { "application/vnd.lotus-1-2-3",
+ "application/vnd.ms-excel",
+ "application/vnd.stardivision.calc",
+ "application/vnd.sun.xml.calc",
+ "application/vnd.oasis.opendocument.spreadsheet",
+ "application/x-applix-spreadsheet",
+ "application/x-gnumeric",
+ "application/x-kspread",
+ "application/x-kspread-crypt",
+ "application/x-quattropro",
+ "application/x-sc",
+ "application/x-siag",
+ "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
+ NULL}},
+ { N_("Text File"),
+ { "text/plain",
+ NULL}},
+ { N_("Video"),
+ { "video/mp4",
+ "video/3gpp",
+ "video/mpeg",
+ "video/quicktime",
+ "video/vivo",
+ "video/x-avi",
+ "video/x-mng",
+ "video/x-ms-asf",
+ "video/x-ms-wmv",
+ "video/x-msvideo",
+ "video/x-nsv",
+ "video/x-real-video",
+ NULL}}
+};
+
+/* Number of seconds until cancel dialog shows up */
+#define DELAY_UNTIL_CANCEL_MSECS 5000
+
+#define SILENT_WINDOW_OPEN_LIMIT 5
+#define SILENT_OPEN_LIMIT 5
+
+/* This number controls a maximum character count for a URL that is
+ * displayed as part of a dialog. It's fairly arbitrary -- big enough
+ * to allow most "normal" URIs to display in full, but small enough to
+ * prevent the dialog from getting insanely wide.
+ */
+#define MAX_URI_IN_DIALOG_LENGTH 60
+
+static void activate_files_internal (ActivateParameters *parameters);
+static void cancel_activate_callback (gpointer callback_data);
+static void activate_activation_uris_ready_callback (GList *files,
+ gpointer callback_data);
+static void activation_mount_mountables (ActivateParameters *parameters);
+static void activation_start_mountables (ActivateParameters *parameters);
+static void activate_callback (GList *files,
+ gpointer callback_data);
+static void activation_mount_not_mounted (ActivateParameters *parameters);
+
+static void
+launch_location_free (LaunchLocation *location)
+{
+ nautilus_file_unref (location->file);
+ g_free (location->uri);
+ g_free (location);
+}
+
+static void
+launch_location_list_free (GList *list)
+{
+ g_list_foreach (list, (GFunc) launch_location_free, NULL);
+ g_list_free (list);
+}
+
+static GList *
+get_file_list_for_launch_locations (GList *locations)
+{
+ GList *files, *l;
+ LaunchLocation *location;
+
+ files = NULL;
+ for (l = locations; l != NULL; l = l->next)
+ {
+ location = l->data;
+
+ files = g_list_prepend (files,
+ nautilus_file_ref (location->file));
+ }
+ return g_list_reverse (files);
+}
+
+
+static LaunchLocation *
+launch_location_from_file (NautilusFile *file)
+{
+ LaunchLocation *location;
+ location = g_new (LaunchLocation, 1);
+ location->file = nautilus_file_ref (file);
+ location->uri = nautilus_file_get_uri (file);
+
+ return location;
+}
+
+static void
+launch_location_update_from_file (LaunchLocation *location,
+ NautilusFile *file)
+{
+ nautilus_file_unref (location->file);
+ g_free (location->uri);
+ location->file = nautilus_file_ref (file);
+ location->uri = nautilus_file_get_uri (file);
+}
+
+static void
+launch_location_update_from_uri (LaunchLocation *location,
+ const char *uri)
+{
+ nautilus_file_unref (location->file);
+ g_free (location->uri);
+ location->file = nautilus_file_get_by_uri (uri);
+ location->uri = g_strdup (uri);
+}
+
+static LaunchLocation *
+find_launch_location_for_file (GList *list,
+ NautilusFile *file)
+{
+ LaunchLocation *location;
+ GList *l;
+
+ for (l = list; l != NULL; l = l->next)
+ {
+ location = l->data;
+
+ if (location->file == file)
+ {
+ return location;
+ }
+ }
+ return NULL;
+}
+
+static GList *
+launch_locations_from_file_list (GList *list)
+{
+ GList *new;
+
+ new = NULL;
+ while (list)
+ {
+ new = g_list_prepend (new,
+ launch_location_from_file (list->data));
+ list = list->next;
+ }
+ new = g_list_reverse (new);
+ return new;
+}
+
+static ApplicationLaunchParameters *
+application_launch_parameters_new (GAppInfo *application,
+ GList *uris)
+{
+ ApplicationLaunchParameters *result;
+
+ result = g_new0 (ApplicationLaunchParameters, 1);
+ result->application = g_object_ref (application);
+ result->uris = g_list_copy_deep (uris, (GCopyFunc) g_strdup, NULL);
+
+ return result;
+}
+
+static void
+application_launch_parameters_free (ApplicationLaunchParameters *parameters)
+{
+ g_object_unref (parameters->application);
+ g_list_free_full (parameters->uris, g_free);
+
+ g_free (parameters);
+}
+
+static gboolean
+nautilus_mime_actions_check_if_required_attributes_ready (NautilusFile *file)
+{
+ NautilusFileAttributes attributes;
+ gboolean ready;
+
+ attributes = nautilus_mime_actions_get_required_file_attributes ();
+ ready = nautilus_file_check_if_ready (file, attributes);
+
+ return ready;
+}
+
+NautilusFileAttributes
+nautilus_mime_actions_get_required_file_attributes (void)
+{
+ return NAUTILUS_FILE_ATTRIBUTE_INFO;
+}
+
+GAppInfo *
+nautilus_mime_get_default_application_for_file (NautilusFile *file)
+{
+ GAppInfo *app;
+ char *mime_type;
+ char *uri_scheme;
+
+ if (!nautilus_mime_actions_check_if_required_attributes_ready (file))
+ {
+ return NULL;
+ }
+
+ mime_type = nautilus_file_get_mime_type (file);
+ app = g_app_info_get_default_for_type (mime_type,
+ !nautilus_file_has_local_path (file));
+ g_free (mime_type);
+
+ if (app == NULL)
+ {
+ uri_scheme = nautilus_file_get_uri_scheme (file);
+ if (uri_scheme != NULL)
+ {
+ app = g_app_info_get_default_for_uri_scheme (uri_scheme);
+ g_free (uri_scheme);
+ }
+ }
+
+ return app;
+}
+
+static int
+file_compare_by_mime_type (NautilusFile *file_a,
+ NautilusFile *file_b)
+{
+ char *mime_type_a, *mime_type_b;
+ int ret;
+
+ mime_type_a = nautilus_file_get_mime_type (file_a);
+ mime_type_b = nautilus_file_get_mime_type (file_b);
+
+ ret = strcmp (mime_type_a, mime_type_b);
+
+ g_free (mime_type_a);
+ g_free (mime_type_b);
+
+ return ret;
+}
+
+static int
+file_compare_by_parent_uri (NautilusFile *file_a,
+ NautilusFile *file_b)
+{
+ char *parent_uri_a, *parent_uri_b;
+ int ret;
+
+ parent_uri_a = nautilus_file_get_parent_uri (file_a);
+ parent_uri_b = nautilus_file_get_parent_uri (file_b);
+
+ ret = strcmp (parent_uri_a, parent_uri_b);
+
+ g_free (parent_uri_a);
+ g_free (parent_uri_b);
+
+ return ret;
+}
+
+GAppInfo *
+nautilus_mime_get_default_application_for_files (GList *files)
+{
+ GList *l, *sorted_files;
+ NautilusFile *file;
+ GAppInfo *app, *one_app;
+
+ g_assert (files != NULL);
+
+ sorted_files = g_list_sort (g_list_copy (files), (GCompareFunc) file_compare_by_mime_type);
+
+ app = NULL;
+ for (l = sorted_files; l != NULL; l = l->next)
+ {
+ file = l->data;
+
+ if (l->prev &&
+ file_compare_by_mime_type (file, l->prev->data) == 0 &&
+ file_compare_by_parent_uri (file, l->prev->data) == 0)
+ {
+ continue;
+ }
+
+ one_app = nautilus_mime_get_default_application_for_file (file);
+ if (one_app == NULL || (app != NULL && !g_app_info_equal (app, one_app)))
+ {
+ if (app)
+ {
+ g_object_unref (app);
+ }
+ if (one_app)
+ {
+ g_object_unref (one_app);
+ }
+ app = NULL;
+ break;
+ }
+
+ if (app == NULL)
+ {
+ app = one_app;
+ }
+ else
+ {
+ g_object_unref (one_app);
+ }
+ }
+
+ g_list_free (sorted_files);
+
+ return app;
+}
+
+static void
+trash_or_delete_files (GtkWindow *parent_window,
+ const GList *files,
+ gboolean delete_if_all_already_in_trash)
+{
+ GList *locations;
+ const GList *node;
+
+ locations = NULL;
+ for (node = files; node != NULL; node = node->next)
+ {
+ locations = g_list_prepend (locations,
+ nautilus_file_get_location ((NautilusFile *) node->data));
+ }
+
+ locations = g_list_reverse (locations);
+
+ nautilus_file_operations_trash_or_delete_async (locations,
+ parent_window,
+ NULL,
+ NULL, NULL);
+ g_list_free_full (locations, g_object_unref);
+}
+
+typedef struct
+{
+ GtkWindow *parent_window;
+ NautilusFile *file;
+} TrashBrokenSymbolicLinkData;
+
+static void
+trash_symbolic_link_cb (GtkDialog *dialog,
+ char *response,
+ gpointer user_data)
+{
+ g_autofree TrashBrokenSymbolicLinkData *data = NULL;
+ GList file_as_list;
+
+ data = user_data;
+
+ if (g_strcmp0 (response, "move-to-trash") == 0)
+ {
+ file_as_list.data = data->file;
+ file_as_list.next = NULL;
+ file_as_list.prev = NULL;
+ trash_or_delete_files (data->parent_window, &file_as_list, TRUE);
+ }
+}
+
+static void
+report_broken_symbolic_link (GtkWindow *parent_window,
+ NautilusFile *file)
+{
+ char *target_path;
+ char *display_name;
+ char *detail;
+ GtkWidget *dialog;
+ TrashBrokenSymbolicLinkData *data;
+
+ gboolean can_trash;
+
+ g_assert (nautilus_file_is_broken_symbolic_link (file));
+
+ display_name = nautilus_file_get_display_name (file);
+ can_trash = nautilus_file_can_trash (file) && !nautilus_file_is_in_trash (file);
+
+ target_path = nautilus_file_get_symbolic_link_target_path (file);
+ if (target_path == NULL)
+ {
+ detail = g_strdup (_("This link cannot be used because it has no target."));
+ }
+ else
+ {
+ detail = g_strdup_printf (_("This link cannot be used because its target "
+ "“%s” doesn’t exist."), target_path);
+ }
+
+ if (can_trash)
+ {
+ dialog = adw_message_dialog_new (parent_window, NULL, detail);
+ adw_message_dialog_format_heading (ADW_MESSAGE_DIALOG (dialog),
+ _("The link “%s” is broken. Move it to Trash?"),
+ display_name);
+ adw_message_dialog_add_responses (ADW_MESSAGE_DIALOG (dialog),
+ "cancel", _("_Cancel"),
+ "move-to-trash", _("Mo_ve to Trash"),
+ NULL);
+ }
+ else
+ {
+ dialog = adw_message_dialog_new (parent_window, NULL, detail);
+ adw_message_dialog_format_heading (ADW_MESSAGE_DIALOG (dialog),
+ _("The link “%s” is broken."),
+ display_name);
+ adw_message_dialog_add_response (ADW_MESSAGE_DIALOG (dialog),
+ "cancel", _("Cancel"));
+ }
+ g_free (display_name);
+
+ adw_message_dialog_set_default_response (ADW_MESSAGE_DIALOG (dialog), "cancel");
+
+ /* Make this modal to avoid problems with reffing the view & file
+ * to keep them around in case the view changes, which would then
+ * cause the old view not to be destroyed, which would cause its
+ * merged Bonobo items not to be un-merged. Maybe we need to unmerge
+ * explicitly when disconnecting views instead of relying on the
+ * unmerge in Destroy. But since BonoboUIHandler is probably going
+ * to change wildly, I don't want to mess with this now.
+ */
+
+ data = g_new0 (TrashBrokenSymbolicLinkData, 1);
+ data->parent_window = parent_window;
+ data->file = file;
+
+ g_signal_connect (G_OBJECT (dialog),
+ "response",
+ G_CALLBACK (trash_symbolic_link_cb),
+ data);
+
+ g_free (target_path);
+ g_free (detail);
+}
+
+static ActivationAction
+get_default_executable_text_file_action (void)
+{
+ return ACTIVATION_ACTION_OPEN_IN_APPLICATION;
+}
+
+static ActivationAction
+get_activation_action (NautilusFile *file)
+{
+ ActivationAction action;
+ char *activation_uri;
+ gboolean handles_extract = FALSE;
+ g_autoptr (GAppInfo) app_info = NULL;
+ const gchar *app_id;
+
+ app_info = nautilus_mime_get_default_application_for_file (file);
+ if (app_info != NULL)
+ {
+ app_id = g_app_info_get_id (app_info);
+ handles_extract = g_strcmp0 (app_id, NAUTILUS_DESKTOP_ID) == 0;
+ }
+ if (handles_extract && nautilus_file_is_archive (file))
+ {
+ return ACTIVATION_ACTION_EXTRACT;
+ }
+
+ activation_uri = nautilus_file_get_activation_uri (file);
+ if (activation_uri == NULL)
+ {
+ activation_uri = nautilus_file_get_uri (file);
+ }
+
+ action = ACTIVATION_ACTION_DO_NOTHING;
+ if (nautilus_file_is_launchable (file))
+ {
+ char *executable_path;
+
+ action = ACTIVATION_ACTION_LAUNCH;
+
+ executable_path = g_filename_from_uri (activation_uri, NULL, NULL);
+ if (!executable_path)
+ {
+ action = ACTIVATION_ACTION_DO_NOTHING;
+ }
+ else if (nautilus_file_contains_text (file))
+ {
+ action = get_default_executable_text_file_action ();
+ }
+ g_free (executable_path);
+ }
+
+ if (action == ACTIVATION_ACTION_DO_NOTHING)
+ {
+ if (nautilus_file_opens_in_view (file))
+ {
+ action = ACTIVATION_ACTION_OPEN_IN_VIEW;
+ }
+ else
+ {
+ action = ACTIVATION_ACTION_OPEN_IN_APPLICATION;
+ }
+ }
+ g_free (activation_uri);
+
+ return action;
+}
+
+gboolean
+nautilus_mime_file_extracts (NautilusFile *file)
+{
+ return get_activation_action (file) == ACTIVATION_ACTION_EXTRACT;
+}
+
+gboolean
+nautilus_mime_file_launches (NautilusFile *file)
+{
+ ActivationAction activation_action;
+
+ activation_action = get_activation_action (file);
+
+ return (activation_action == ACTIVATION_ACTION_LAUNCH);
+}
+
+gboolean
+nautilus_mime_file_opens_in_external_app (NautilusFile *file)
+{
+ ActivationAction activation_action;
+
+ activation_action = get_activation_action (file);
+
+ return (activation_action == ACTIVATION_ACTION_OPEN_IN_APPLICATION);
+}
+
+
+static unsigned int
+mime_application_hash (GAppInfo *app)
+{
+ const char *id;
+
+ id = g_app_info_get_id (app);
+
+ if (id == NULL)
+ {
+ return GPOINTER_TO_UINT (app);
+ }
+
+ return g_str_hash (id);
+}
+
+static void
+list_to_parameters_foreach (GAppInfo *application,
+ GList *uris,
+ GList **ret)
+{
+ ApplicationLaunchParameters *parameters;
+
+ uris = g_list_reverse (uris);
+
+ parameters = application_launch_parameters_new
+ (application, uris);
+ *ret = g_list_prepend (*ret, parameters);
+}
+
+
+/**
+ * make_activation_parameters
+ *
+ * Construct a list of ApplicationLaunchParameters from a list of NautilusFiles,
+ * where files that have the same default application are put into the same
+ * launch parameter, and others are put into the unhandled_files list.
+ *
+ * @files: Files to use for construction.
+ * @unhandled_files: Files without any default application will be put here.
+ *
+ * Return value: Newly allocated list of ApplicationLaunchParameters.
+ **/
+static GList *
+make_activation_parameters (GList *uris,
+ GList **unhandled_uris)
+{
+ GList *ret, *l, *app_uris;
+ NautilusFile *file;
+ GAppInfo *app, *old_app;
+ GHashTable *app_table;
+ char *uri;
+
+ ret = NULL;
+ *unhandled_uris = NULL;
+
+ app_table = g_hash_table_new_full
+ ((GHashFunc) mime_application_hash,
+ (GEqualFunc) g_app_info_equal,
+ (GDestroyNotify) g_object_unref,
+ (GDestroyNotify) g_list_free);
+
+ for (l = uris; l != NULL; l = l->next)
+ {
+ uri = l->data;
+ file = nautilus_file_get_by_uri (uri);
+
+ app = nautilus_mime_get_default_application_for_file (file);
+ if (app != NULL)
+ {
+ app_uris = NULL;
+
+ if (g_hash_table_lookup_extended (app_table, app,
+ (gpointer *) &old_app,
+ (gpointer *) &app_uris))
+ {
+ g_hash_table_steal (app_table, old_app);
+
+ app_uris = g_list_prepend (app_uris, uri);
+
+ g_object_unref (app);
+ app = old_app;
+ }
+ else
+ {
+ app_uris = g_list_prepend (NULL, uri);
+ }
+
+ g_hash_table_insert (app_table, app, app_uris);
+ }
+ else
+ {
+ *unhandled_uris = g_list_prepend (*unhandled_uris, uri);
+ }
+ nautilus_file_unref (file);
+ }
+
+ g_hash_table_foreach (app_table,
+ (GHFunc) list_to_parameters_foreach,
+ &ret);
+
+ g_hash_table_destroy (app_table);
+
+ *unhandled_uris = g_list_reverse (*unhandled_uris);
+
+ return g_list_reverse (ret);
+}
+
+static gboolean
+file_was_cancelled (NautilusFile *file)
+{
+ GError *error;
+
+ error = nautilus_file_get_file_info_error (file);
+ return
+ error != NULL &&
+ error->domain == G_IO_ERROR &&
+ error->code == G_IO_ERROR_CANCELLED;
+}
+
+static gboolean
+file_was_not_mounted (NautilusFile *file)
+{
+ GError *error;
+
+ error = nautilus_file_get_file_info_error (file);
+ return
+ error != NULL &&
+ error->domain == G_IO_ERROR &&
+ error->code == G_IO_ERROR_NOT_MOUNTED;
+}
+
+static void
+activation_parameters_free (ActivateParameters *parameters)
+{
+ if (parameters->timed_wait_active)
+ {
+ eel_timed_wait_stop (cancel_activate_callback, parameters);
+ }
+
+ if (parameters->slot)
+ {
+ g_object_remove_weak_pointer (G_OBJECT (parameters->slot), (gpointer *) &parameters->slot);
+ }
+ if (parameters->parent_window)
+ {
+ g_object_remove_weak_pointer (G_OBJECT (parameters->parent_window), (gpointer *) &parameters->parent_window);
+ }
+ g_object_unref (parameters->cancellable);
+ launch_location_list_free (parameters->locations);
+ nautilus_file_list_free (parameters->mountables);
+ nautilus_file_list_free (parameters->start_mountables);
+ nautilus_file_list_free (parameters->not_mounted);
+ g_free (parameters->activation_directory);
+ g_free (parameters->timed_wait_prompt);
+ g_assert (parameters->files_handle == NULL);
+ g_clear_pointer (&parameters->open_in_view_files, g_queue_free);
+ g_clear_pointer (&parameters->open_in_app_uris, g_queue_free);
+ g_clear_pointer (&parameters->launch_files, g_queue_free);
+ g_clear_pointer (&parameters->launch_in_terminal_files, g_queue_free);
+ g_list_free (parameters->open_in_app_parameters);
+ g_list_free (parameters->unhandled_open_in_app_uris);
+ g_free (parameters);
+}
+
+static void
+application_launch_async_parameters_free (ApplicationLaunchAsyncParameters *parameters)
+{
+ g_queue_free (parameters->uris);
+ activation_parameters_free (parameters->activation_params);
+
+ g_free (parameters);
+}
+
+static void
+cancel_activate_callback (gpointer callback_data)
+{
+ ActivateParameters *parameters = callback_data;
+
+ parameters->timed_wait_active = FALSE;
+
+ g_cancellable_cancel (parameters->cancellable);
+
+ if (parameters->files_handle)
+ {
+ nautilus_file_list_cancel_call_when_ready (parameters->files_handle);
+ parameters->files_handle = NULL;
+ activation_parameters_free (parameters);
+ }
+}
+
+static void
+activation_start_timed_cancel (ActivateParameters *parameters)
+{
+ parameters->timed_wait_active = TRUE;
+ eel_timed_wait_start_with_duration
+ (DELAY_UNTIL_CANCEL_MSECS,
+ cancel_activate_callback,
+ parameters,
+ parameters->timed_wait_prompt,
+ parameters->parent_window);
+}
+
+static void
+pause_activation_timed_cancel (ActivateParameters *parameters)
+{
+ if (parameters->timed_wait_active)
+ {
+ eel_timed_wait_stop (cancel_activate_callback, parameters);
+ parameters->timed_wait_active = FALSE;
+ }
+}
+
+static void
+unpause_activation_timed_cancel (ActivateParameters *parameters)
+{
+ if (!parameters->timed_wait_active)
+ {
+ activation_start_timed_cancel (parameters);
+ }
+}
+
+
+static void
+activate_mount_op_active (GtkMountOperation *operation,
+ GParamSpec *pspec,
+ ActivateParameters *parameters)
+{
+ gboolean is_active;
+
+ g_object_get (operation, "is-showing", &is_active, NULL);
+
+ if (is_active)
+ {
+ pause_activation_timed_cancel (parameters);
+ }
+ else
+ {
+ unpause_activation_timed_cancel (parameters);
+ }
+}
+
+static void
+on_confirm_multiple_windows_response (GtkDialog *dialog,
+ gchar *response,
+ ActivateParameters *parameters)
+{
+ if (g_strcmp0 (response, "open-all") == 0)
+ {
+ unpause_activation_timed_cancel (parameters);
+ activate_files_internal (parameters);
+ }
+ else
+ {
+ activation_parameters_free (parameters);
+ }
+}
+
+static void
+show_confirm_multiple (ActivateParameters *parameters,
+ int window_count,
+ int tab_count)
+{
+ GtkWindow *parent_window = parameters->parent_window;
+ GtkWidget *dialog;
+ char *prompt;
+ char *detail;
+
+ prompt = _("Are you sure you want to open all files?");
+ if (tab_count > 0 && window_count > 0)
+ {
+ int count = tab_count + window_count;
+ detail = g_strdup_printf (ngettext ("This will open %d separate tab and window.",
+ "This will open %d separate tabs and windows.", count), count);
+ }
+ else if (tab_count > 0)
+ {
+ detail = g_strdup_printf (ngettext ("This will open %d separate tab.",
+ "This will open %d separate tabs.", tab_count), tab_count);
+ }
+ else
+ {
+ detail = g_strdup_printf (ngettext ("This will open %d separate window.",
+ "This will open %d separate windows.", window_count), window_count);
+ }
+
+ dialog = adw_message_dialog_new (parent_window, prompt, detail);
+ adw_message_dialog_add_responses (ADW_MESSAGE_DIALOG (dialog),
+ "cancel", _("_Cancel"),
+ "open-all", _("_Open All"),
+ NULL);
+
+ adw_message_dialog_set_default_response (ADW_MESSAGE_DIALOG (dialog), "open-all");
+
+ g_signal_connect (dialog, "response",
+ G_CALLBACK (on_confirm_multiple_windows_response), parameters);
+ gtk_window_present (GTK_WINDOW (dialog));
+
+ g_free (detail);
+}
+
+typedef struct
+{
+ NautilusWindowSlot *slot;
+ GtkWindow *parent_window;
+ NautilusFile *file;
+ GList *files;
+ NautilusOpenFlags flags;
+ char *activation_directory;
+ gboolean user_confirmation;
+ char *uri;
+ GDBusProxy *proxy;
+ GtkWidget *dialog;
+} ActivateParametersInstall;
+
+static void
+activate_parameters_install_free (ActivateParametersInstall *parameters_install)
+{
+ if (parameters_install->slot)
+ {
+ g_object_remove_weak_pointer (G_OBJECT (parameters_install->slot), (gpointer *) &parameters_install->slot);
+ }
+ if (parameters_install->parent_window)
+ {
+ g_object_remove_weak_pointer (G_OBJECT (parameters_install->parent_window), (gpointer *) &parameters_install->parent_window);
+ }
+
+ if (parameters_install->proxy != NULL)
+ {
+ g_object_unref (parameters_install->proxy);
+ }
+
+ nautilus_file_unref (parameters_install->file);
+ nautilus_file_list_free (parameters_install->files);
+ g_free (parameters_install->activation_directory);
+ g_free (parameters_install->uri);
+ g_free (parameters_install);
+}
+
+static char *
+get_application_no_mime_type_handler_message (NautilusFile *file,
+ char *uri)
+{
+ char *uri_for_display;
+ char *name;
+ char *error_message;
+
+ name = nautilus_file_get_display_name (file);
+
+ /* Truncate the URI so it doesn't get insanely wide. Note that even
+ * though the dialog uses wrapped text, if the URI doesn't contain
+ * white space then the text-wrapping code is too stupid to wrap it.
+ */
+ uri_for_display = eel_str_middle_truncate (name, MAX_URI_IN_DIALOG_LENGTH);
+ error_message = g_strdup_printf (_("Could Not Display “%s”"), uri_for_display);
+ g_free (uri_for_display);
+ g_free (name);
+
+ return error_message;
+}
+
+static void
+open_with_response_cb (GtkDialog *dialog,
+ gint response_id,
+ gpointer user_data)
+{
+ GtkWindow *parent_window;
+ NautilusFile *file;
+ GList files;
+ GAppInfo *info;
+ ActivateParametersInstall *parameters = user_data;
+
+ if (response_id != GTK_RESPONSE_OK)
+ {
+ gtk_window_destroy (GTK_WINDOW (dialog));
+ return;
+ }
+
+ parent_window = parameters->parent_window;
+ file = g_object_get_data (G_OBJECT (dialog), "mime-action:file");
+ info = gtk_app_chooser_get_app_info (GTK_APP_CHOOSER (dialog));
+
+ gtk_window_destroy (GTK_WINDOW (dialog));
+
+ g_signal_emit_by_name (nautilus_signaller_get_current (), "mime-data-changed");
+
+ files.next = NULL;
+ files.prev = NULL;
+ files.data = file;
+ nautilus_launch_application (info, &files, parent_window);
+
+ g_object_unref (info);
+
+ activate_parameters_install_free (parameters);
+}
+
+static void
+choose_program (GtkDialog *message_dialog,
+ gchar *response,
+ gpointer callback_data)
+{
+ GtkWidget *dialog;
+ NautilusFile *file;
+ GFile *location;
+ ActivateParametersInstall *parameters = callback_data;
+
+ if (g_strcmp0 (response, "select-application") != 0)
+ {
+ activate_parameters_install_free (parameters);
+ return;
+ }
+
+ file = g_object_get_data (G_OBJECT (message_dialog), "mime-action:file");
+
+ g_assert (NAUTILUS_IS_FILE (file));
+
+ location = nautilus_file_get_location (file);
+ nautilus_file_ref (file);
+
+ /* Destroy the message dialog after ref:ing the file */
+ gtk_window_destroy (GTK_WINDOW (message_dialog));
+
+ dialog = gtk_app_chooser_dialog_new (parameters->parent_window,
+ GTK_DIALOG_MODAL,
+ location);
+ g_object_set_data_full (G_OBJECT (dialog),
+ "mime-action:file",
+ nautilus_file_ref (file),
+ (GDestroyNotify) nautilus_file_unref);
+
+ gtk_widget_show (dialog);
+
+ g_signal_connect (dialog,
+ "response",
+ G_CALLBACK (open_with_response_cb),
+ parameters);
+
+ g_object_unref (location);
+ nautilus_file_unref (file);
+}
+
+static void
+show_unhandled_type_error (ActivateParametersInstall *parameters)
+{
+ GtkWidget *dialog;
+ g_autofree char *body = NULL;
+ g_autofree char *content_type_description = NULL;
+
+ char *mime_type = nautilus_file_get_mime_type (parameters->file);
+ char *error_message = get_application_no_mime_type_handler_message (parameters->file, parameters->uri);
+
+ if (g_content_type_is_unknown (mime_type))
+ {
+ body = g_strdup (_("The file is of an unknown type"));
+ }
+ else
+ {
+ content_type_description = g_content_type_get_description (mime_type);
+ body = g_strdup_printf (_("There is no application installed for “%s” files"), content_type_description);
+ }
+
+ dialog = adw_message_dialog_new (parameters->parent_window, error_message, body);
+ adw_message_dialog_add_responses (ADW_MESSAGE_DIALOG (dialog),
+ "select-application", _("_Select Application"),
+ "ok", _("_OK"),
+ NULL);
+ adw_message_dialog_set_default_response (ADW_MESSAGE_DIALOG (dialog), "ok");
+
+ g_object_set_data_full (G_OBJECT (dialog),
+ "mime-action:file",
+ nautilus_file_ref (parameters->file),
+ (GDestroyNotify) nautilus_file_unref);
+
+ gtk_window_present (GTK_WINDOW (dialog));
+
+ g_signal_connect (dialog, "response",
+ G_CALLBACK (choose_program), parameters);
+
+ g_free (error_message);
+ g_free (mime_type);
+}
+
+static void
+search_for_application_dbus_call_notify_cb (GDBusProxy *proxy,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ ActivateParametersInstall *parameters_install = user_data;
+ GVariant *variant;
+ GError *error = NULL;
+
+ variant = g_dbus_proxy_call_finish (proxy, result, &error);
+ if (variant == NULL)
+ {
+ if (!g_dbus_error_is_remote_error (error) ||
+ g_strcmp0 (g_dbus_error_get_remote_error (error), "org.freedesktop.PackageKit.Modify.Failed") == 0)
+ {
+ char *message;
+
+ message = g_strdup_printf ("%s\n%s",
+ _("There was an internal error trying to search for applications:"),
+ error->message);
+ show_dialog (_("Unable to search for application"),
+ message,
+ parameters_install->parent_window,
+ GTK_MESSAGE_ERROR);
+ g_free (message);
+ }
+ else
+ {
+ g_warning ("Error while trying to search for applications: %s",
+ error->message);
+ }
+
+ g_error_free (error);
+ activate_parameters_install_free (parameters_install);
+ return;
+ }
+
+ g_variant_unref (variant);
+
+ activate_parameters_install_free (parameters_install);
+}
+
+static void
+search_for_application_mime_type (ActivateParametersInstall *parameters_install,
+ const gchar *mime_type)
+{
+ gchar *desktop_startup_id;
+
+ g_assert (parameters_install->proxy != NULL);
+
+ desktop_startup_id = g_strdup_printf ("_TIME%i", (guint32) GDK_CURRENT_TIME);
+
+ g_dbus_proxy_call (parameters_install->proxy,
+ "InstallMimeTypes",
+ g_variant_new_parsed ("([%s], %s, %s, [{%s, %v}])",
+ mime_type,
+ "hide-confirm-search",
+ APPLICATION_ID,
+ "desktop-startup-id",
+ g_variant_new_take_string (desktop_startup_id)),
+ G_DBUS_CALL_FLAGS_NONE,
+ G_MAXINT /* no timeout */,
+ NULL /* cancellable */,
+ (GAsyncReadyCallback) search_for_application_dbus_call_notify_cb,
+ parameters_install);
+
+ DEBUG ("InstallMimeType method invoked for %s", mime_type);
+}
+
+static void
+application_unhandled_file_install (GtkDialog *dialog,
+ gchar *response,
+ ActivateParametersInstall *parameters_install)
+{
+ char *mime_type;
+
+ parameters_install->dialog = NULL;
+
+ if (g_strcmp0 (response, "search-in-software") == 0)
+ {
+ mime_type = nautilus_file_get_mime_type (parameters_install->file);
+ search_for_application_mime_type (parameters_install, mime_type);
+ g_free (mime_type);
+ }
+ else
+ {
+ /* free as we're not going to get the async dbus callback */
+ activate_parameters_install_free (parameters_install);
+ }
+}
+
+static void
+pk_proxy_appeared_cb (GObject *source,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ ActivateParametersInstall *parameters_install = user_data;
+ char *mime_type, *name_owner;
+ char *error_message;
+ GtkWidget *dialog;
+ GDBusProxy *proxy;
+ GError *error = NULL;
+ g_autofree char *content_type_description = NULL;
+
+ proxy = g_dbus_proxy_new_for_bus_finish (res, &error);
+ name_owner = g_dbus_proxy_get_name_owner (proxy);
+
+ if (error != NULL || name_owner == NULL)
+ {
+ g_warning ("Couldn't call Modify on the PackageKit interface: %s",
+ error != NULL ? error->message : "no owner for PackageKit");
+ g_clear_error (&error);
+
+ /* show an unhelpful dialog */
+ show_unhandled_type_error (parameters_install);
+
+ return;
+ }
+
+ g_free (name_owner);
+
+ mime_type = nautilus_file_get_mime_type (parameters_install->file);
+ content_type_description = g_content_type_get_description (mime_type);
+ error_message = get_application_no_mime_type_handler_message (parameters_install->file,
+ parameters_install->uri);
+ /* use a custom dialog to prompt the user to install new software */
+ dialog = adw_message_dialog_new (parameters_install->parent_window, error_message, NULL);
+ adw_message_dialog_add_responses (ADW_MESSAGE_DIALOG (dialog),
+ "cancel", _("_Cancel"),
+ "search-in-software", _("_Search in Software"),
+ NULL);
+ adw_message_dialog_format_body (ADW_MESSAGE_DIALOG (dialog),
+ _("There is no application installed for “%s” files. "
+ "Do you want to search for an application to open this file?"),
+ content_type_description);
+
+ adw_message_dialog_set_default_response (ADW_MESSAGE_DIALOG (dialog), "search-in-software");
+
+ parameters_install->dialog = dialog;
+ parameters_install->proxy = proxy;
+
+ g_signal_connect (dialog, "response",
+ G_CALLBACK (application_unhandled_file_install),
+ parameters_install);
+ gtk_window_present (GTK_WINDOW (dialog));
+ g_free (mime_type);
+}
+
+static void
+application_unhandled_uri (ActivateParameters *parameters,
+ char *uri)
+{
+ gboolean show_install_mime;
+ char *mime_type;
+ NautilusFile *file;
+ ActivateParametersInstall *parameters_install;
+
+ file = nautilus_file_get_by_uri (uri);
+
+ mime_type = nautilus_file_get_mime_type (file);
+
+ /* copy the parts of parameters we are interested in as the orignal will be unref'd */
+ parameters_install = g_new0 (ActivateParametersInstall, 1);
+ parameters_install->slot = parameters->slot;
+ g_object_add_weak_pointer (G_OBJECT (parameters_install->slot), (gpointer *) &parameters_install->slot);
+ if (parameters->parent_window)
+ {
+ parameters_install->parent_window = parameters->parent_window;
+ g_object_add_weak_pointer (G_OBJECT (parameters_install->parent_window), (gpointer *) &parameters_install->parent_window);
+ }
+ parameters_install->activation_directory = g_strdup (parameters->activation_directory);
+ parameters_install->file = file;
+ parameters_install->files = get_file_list_for_launch_locations (parameters->locations);
+ parameters_install->flags = parameters->flags;
+ parameters_install->user_confirmation = parameters->user_confirmation;
+ parameters_install->uri = g_strdup (uri);
+
+#ifdef ENABLE_PACKAGEKIT
+ /* allow an admin to disable the PackageKit search functionality */
+ show_install_mime = g_settings_get_boolean (nautilus_preferences, NAUTILUS_PREFERENCES_INSTALL_MIME_ACTIVATION);
+#else
+ /* we have no install functionality */
+ show_install_mime = FALSE;
+#endif
+ /* There is no use trying to look for handlers of application/octet-stream */
+ if (g_content_type_is_unknown (mime_type))
+ {
+ show_install_mime = FALSE;
+ }
+
+ g_free (mime_type);
+
+ if (!show_install_mime)
+ {
+ goto out;
+ }
+
+ g_dbus_proxy_new_for_bus (G_BUS_TYPE_SESSION,
+ G_DBUS_PROXY_FLAGS_NONE,
+ NULL,
+ "org.freedesktop.PackageKit",
+ "/org/freedesktop/PackageKit",
+ "org.freedesktop.PackageKit.Modify2",
+ NULL,
+ pk_proxy_appeared_cb,
+ parameters_install);
+
+ return;
+
+out:
+ /* show an unhelpful dialog */
+ show_unhandled_type_error (parameters_install);
+}
+
+static void
+launch_default_for_uris_callback (GObject *source_object,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ ApplicationLaunchAsyncParameters *params;
+ ActivateParameters *activation_params;
+ char *uri;
+ g_autoptr (GError) error = NULL;
+
+ params = user_data;
+ activation_params = params->activation_params;
+ uri = g_queue_pop_head (params->uris);
+
+ nautilus_launch_default_for_uri_finish (res, &error);
+ if (error == NULL)
+ {
+ gtk_recent_manager_add_item (gtk_recent_manager_get_default (), uri);
+ }
+
+ if (!g_queue_is_empty (params->uris))
+ {
+ nautilus_launch_default_for_uri_async (g_queue_peek_head (params->uris),
+ activation_params->parent_window,
+ activation_params->cancellable,
+ launch_default_for_uris_callback,
+ params);
+ }
+ else
+ {
+ application_launch_async_parameters_free (params);
+ }
+}
+
+static void
+activate_files (ActivateParameters *parameters)
+{
+ NautilusFile *file;
+ int count;
+ gint num_windows = 0;
+ gint num_tabs = 0;
+ GList *l;
+ ActivationAction action;
+
+ parameters->launch_files = g_queue_new ();
+ parameters->launch_in_terminal_files = g_queue_new ();
+ parameters->open_in_view_files = g_queue_new ();
+ parameters->open_in_app_uris = g_queue_new ();
+
+ for (l = parameters->locations; l != NULL; l = l->next)
+ {
+ LaunchLocation *location;
+
+ location = l->data;
+ file = location->file;
+
+ if (file_was_cancelled (file))
+ {
+ continue;
+ }
+
+ action = get_activation_action (file);
+
+ switch (action)
+ {
+ case ACTIVATION_ACTION_LAUNCH:
+ {
+ g_queue_push_tail (parameters->launch_files, file);
+ }
+ break;
+
+ case ACTIVATION_ACTION_LAUNCH_IN_TERMINAL:
+ {
+ g_queue_push_tail (parameters->launch_in_terminal_files, file);
+ }
+ break;
+
+ case ACTIVATION_ACTION_OPEN_IN_VIEW:
+ {
+ g_queue_push_tail (parameters->open_in_view_files, file);
+ }
+ break;
+
+ case ACTIVATION_ACTION_OPEN_IN_APPLICATION:
+ {
+ g_queue_push_tail (parameters->open_in_app_uris, location->uri);
+ }
+ break;
+
+ case ACTIVATION_ACTION_DO_NOTHING:
+ {
+ }
+ break;
+
+ case ACTIVATION_ACTION_EXTRACT:
+ {
+ /* Extraction of files should be handled in the view */
+ g_assert_not_reached ();
+ }
+ break;
+ }
+ }
+
+ count = g_queue_get_length (parameters->open_in_view_files);
+ if (count > 1)
+ {
+ if ((parameters->flags & NAUTILUS_OPEN_FLAG_NEW_WINDOW) == 0)
+ {
+ parameters->flags |= NAUTILUS_OPEN_FLAG_NEW_TAB;
+ num_tabs += count;
+ }
+ else
+ {
+ parameters->flags |= NAUTILUS_OPEN_FLAG_NEW_WINDOW;
+ num_windows += count;
+ }
+ }
+
+ if (parameters->open_in_app_uris != NULL)
+ {
+ if (nautilus_application_is_sandboxed ())
+ {
+ num_windows += g_queue_get_length (parameters->open_in_app_uris);
+ }
+ else
+ {
+ parameters->open_in_app_parameters = make_activation_parameters (g_queue_peek_head_link (parameters->open_in_app_uris),
+ &parameters->unhandled_open_in_app_uris);
+ num_windows += g_list_length (parameters->open_in_app_parameters);
+ num_windows += g_list_length (parameters->unhandled_open_in_app_uris);
+ }
+ }
+
+ num_windows += g_queue_get_length (parameters->launch_files);
+ num_windows += g_queue_get_length (parameters->launch_in_terminal_files);
+
+ if (parameters->user_confirmation &&
+ num_tabs + num_windows > SILENT_OPEN_LIMIT)
+ {
+ pause_activation_timed_cancel (parameters);
+ show_confirm_multiple (parameters, num_windows, num_tabs);
+ }
+ else
+ {
+ activate_files_internal (parameters);
+ }
+}
+
+static void
+activate_files_internal (ActivateParameters *parameters)
+{
+ NautilusFile *file;
+ ApplicationLaunchParameters *one_parameters;
+ g_autofree char *old_working_dir = NULL;
+ GdkDisplay *display;
+ GList *l;
+
+ if (parameters->activation_directory &&
+ (!g_queue_is_empty (parameters->launch_files) ||
+ !g_queue_is_empty (parameters->launch_in_terminal_files)))
+ {
+ old_working_dir = g_get_current_dir ();
+ g_chdir (parameters->activation_directory);
+ }
+
+ display = gtk_widget_get_display (GTK_WIDGET (parameters->parent_window));
+ for (l = g_queue_peek_head_link (parameters->launch_files); l != NULL; l = l->next)
+ {
+ g_autofree char *uri = NULL;
+ g_autofree char *executable_path = NULL;
+ g_autofree char *quoted_path = NULL;
+
+ file = NAUTILUS_FILE (l->data);
+
+ uri = nautilus_file_get_activation_uri (file);
+ executable_path = g_filename_from_uri (uri, NULL, NULL);
+ quoted_path = g_shell_quote (executable_path);
+
+ DEBUG ("Launching file path %s", quoted_path);
+
+ nautilus_launch_application_from_command (display, quoted_path, FALSE, NULL);
+ }
+
+ for (l = g_queue_peek_head_link (parameters->launch_in_terminal_files); l != NULL; l = l->next)
+ {
+ g_autofree char *uri = NULL;
+ g_autofree char *executable_path = NULL;
+ g_autofree char *quoted_path = NULL;
+
+ file = NAUTILUS_FILE (l->data);
+
+ uri = nautilus_file_get_activation_uri (file);
+ executable_path = g_filename_from_uri (uri, NULL, NULL);
+ quoted_path = g_shell_quote (executable_path);
+
+ DEBUG ("Launching in terminal file quoted path %s", quoted_path);
+
+ nautilus_launch_application_from_command (display, quoted_path, TRUE, NULL);
+ }
+
+ if (old_working_dir != NULL)
+ {
+ g_chdir (old_working_dir);
+ }
+
+ if (parameters->slot != NULL)
+ {
+ if ((parameters->flags & NAUTILUS_OPEN_FLAG_NEW_TAB) != 0)
+ {
+ /* When inserting N tabs after the current one,
+ * we first open tab N, then tab N-1, ..., then tab 0.
+ * Each of them is appended to the current tab, i.e.
+ * prepended to the list of tabs to open.
+ */
+ g_queue_reverse (parameters->open_in_view_files);
+ }
+
+ for (l = g_queue_peek_head_link (parameters->open_in_view_files); l != NULL; l = l->next)
+ {
+ g_autofree char *uri = NULL;
+ g_autoptr (GFile) location = NULL;
+ g_autoptr (GFile) location_with_permissions = NULL;
+ /* The ui should ask for navigation or object windows
+ * depending on what the current one is */
+ file = NAUTILUS_FILE (l->data);
+ uri = nautilus_file_get_activation_uri (file);
+ location = g_file_new_for_uri (uri);
+ if (g_file_is_native (location) &&
+ (nautilus_file_is_in_admin (file) ||
+ !nautilus_file_can_read (file) ||
+ !nautilus_file_can_execute (file)))
+ {
+ g_autofree gchar *file_path = NULL;
+
+ g_free (uri);
+
+ file_path = g_file_get_path (location);
+ uri = g_strconcat ("admin://", file_path, NULL);
+ }
+
+ location_with_permissions = g_file_new_for_uri (uri);
+ /* FIXME: we need to pass the parent_window, but we only use it for the current active window,
+ * which nautilus-application should take care of. However is not working and creating regressions
+ * in some cases. Until we figure out what's going on, continue to use the parameters->slot
+ * to make splicit the window we want to use for activating the files */
+ nautilus_application_open_location_full (NAUTILUS_APPLICATION (g_application_get_default ()),
+ location_with_permissions, parameters->flags, NULL, NULL, parameters->slot);
+ }
+ }
+
+ if (!g_queue_is_empty (parameters->open_in_app_uris) && nautilus_application_is_sandboxed ())
+ {
+ const char *uri;
+ ApplicationLaunchAsyncParameters *async_params;
+
+ uri = g_queue_peek_head (parameters->open_in_app_uris);
+
+ async_params = g_new0 (ApplicationLaunchAsyncParameters, 1);
+ async_params->activation_params = parameters;
+ async_params->uris = g_steal_pointer (&parameters->open_in_app_uris);
+
+ nautilus_launch_default_for_uri_async (uri,
+ parameters->parent_window,
+ parameters->cancellable,
+ launch_default_for_uris_callback,
+ async_params);
+ return;
+ }
+
+ if (!g_queue_is_empty (parameters->open_in_app_uris))
+ {
+ for (l = parameters->open_in_app_parameters; l != NULL; l = l->next)
+ {
+ one_parameters = l->data;
+
+ nautilus_launch_application_by_uri (one_parameters->application,
+ one_parameters->uris,
+ parameters->parent_window);
+ application_launch_parameters_free (one_parameters);
+ }
+
+ for (l = parameters->unhandled_open_in_app_uris; l != NULL; l = l->next)
+ {
+ char *uri = l->data;
+
+ /* this does not block */
+ application_unhandled_uri (parameters, uri);
+ }
+ }
+
+ activation_parameters_free (parameters);
+}
+
+static void
+activation_mount_not_mounted_callback (GObject *source_object,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ ActivateParameters *parameters = user_data;
+ GError *error;
+ NautilusFile *file;
+ LaunchLocation *loc;
+
+ file = parameters->not_mounted->data;
+
+ error = NULL;
+ if (!g_file_mount_enclosing_volume_finish (G_FILE (source_object), res, &error))
+ {
+ if (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))
+ {
+ show_dialog (_("Unable to access location"),
+ error->message,
+ parameters->parent_window,
+ GTK_MESSAGE_ERROR);
+ }
+
+ if (error->domain != G_IO_ERROR ||
+ error->code != G_IO_ERROR_ALREADY_MOUNTED)
+ {
+ loc = find_launch_location_for_file (parameters->locations,
+ file);
+ if (loc)
+ {
+ parameters->locations =
+ g_list_remove (parameters->locations, loc);
+ launch_location_free (loc);
+ }
+ }
+
+ g_error_free (error);
+ }
+
+ parameters->not_mounted = g_list_delete_link (parameters->not_mounted,
+ parameters->not_mounted);
+ nautilus_file_unref (file);
+
+ activation_mount_not_mounted (parameters);
+}
+
+static void
+activation_mount_not_mounted (ActivateParameters *parameters)
+{
+ NautilusFile *file;
+ GFile *location;
+ LaunchLocation *loc;
+ GMountOperation *mount_op;
+ GList *l, *next, *files;
+
+ if (parameters->not_mounted != NULL)
+ {
+ file = parameters->not_mounted->data;
+ mount_op = gtk_mount_operation_new (parameters->parent_window);
+ g_mount_operation_set_password_save (mount_op, G_PASSWORD_SAVE_FOR_SESSION);
+ g_signal_connect (mount_op, "notify::is-showing",
+ G_CALLBACK (activate_mount_op_active), parameters);
+ location = nautilus_file_get_location (file);
+ g_file_mount_enclosing_volume (location, 0, mount_op, parameters->cancellable,
+ activation_mount_not_mounted_callback, parameters);
+ g_object_unref (location);
+ /* unref mount_op here - g_file_mount_enclosing_volume() does ref for itself */
+ g_object_unref (mount_op);
+ return;
+ }
+
+ parameters->tried_mounting = TRUE;
+
+ if (parameters->locations == NULL)
+ {
+ activation_parameters_free (parameters);
+ return;
+ }
+
+ /* once the mount is finished, refresh all attributes
+ * - fixes new windows not appearing after successful mount
+ */
+ for (l = parameters->locations; l != NULL; l = next)
+ {
+ loc = l->data;
+ next = l->next;
+ nautilus_file_invalidate_all_attributes (loc->file);
+ }
+
+ files = get_file_list_for_launch_locations (parameters->locations);
+ nautilus_file_list_call_when_ready
+ (files,
+ nautilus_mime_actions_get_required_file_attributes (),
+ &parameters->files_handle,
+ activate_callback, parameters);
+ nautilus_file_list_free (files);
+}
+
+
+static void
+activate_callback (GList *files,
+ gpointer callback_data)
+{
+ ActivateParameters *parameters = callback_data;
+ GList *l, *next;
+ NautilusFile *file;
+ LaunchLocation *location;
+
+ parameters->files_handle = NULL;
+
+ for (l = parameters->locations; l != NULL; l = next)
+ {
+ location = l->data;
+ file = location->file;
+ next = l->next;
+
+ if (file_was_cancelled (file))
+ {
+ launch_location_free (location);
+ parameters->locations = g_list_delete_link (parameters->locations, l);
+ continue;
+ }
+
+ if (file_was_not_mounted (file))
+ {
+ if (parameters->tried_mounting)
+ {
+ launch_location_free (location);
+ parameters->locations = g_list_delete_link (parameters->locations, l);
+ }
+ else
+ {
+ parameters->not_mounted = g_list_prepend (parameters->not_mounted,
+ nautilus_file_ref (file));
+ }
+ continue;
+ }
+ }
+
+
+ if (parameters->not_mounted != NULL)
+ {
+ activation_mount_not_mounted (parameters);
+ }
+ else
+ {
+ activate_files (parameters);
+ }
+}
+
+static void
+activate_activation_uris_ready_callback (GList *files_ignore,
+ gpointer callback_data)
+{
+ ActivateParameters *parameters = callback_data;
+ GList *l, *next, *files;
+ NautilusFile *file;
+ LaunchLocation *location;
+
+ parameters->files_handle = NULL;
+
+ for (l = parameters->locations; l != NULL; l = next)
+ {
+ location = l->data;
+ file = location->file;
+ next = l->next;
+
+ if (file_was_cancelled (file))
+ {
+ launch_location_free (location);
+ parameters->locations = g_list_delete_link (parameters->locations, l);
+ continue;
+ }
+
+ if (nautilus_file_is_broken_symbolic_link (file))
+ {
+ launch_location_free (location);
+ parameters->locations = g_list_delete_link (parameters->locations, l);
+ pause_activation_timed_cancel (parameters);
+ report_broken_symbolic_link (parameters->parent_window, file);
+ unpause_activation_timed_cancel (parameters);
+ continue;
+ }
+
+ if (nautilus_file_get_file_type (file) == G_FILE_TYPE_MOUNTABLE &&
+ !nautilus_file_has_activation_uri (file))
+ {
+ /* Don't launch these... There is nothing we
+ * can do */
+ launch_location_free (location);
+ parameters->locations = g_list_delete_link (parameters->locations, l);
+ continue;
+ }
+ }
+
+ if (parameters->locations == NULL)
+ {
+ activation_parameters_free (parameters);
+ return;
+ }
+
+ /* Convert the files to the actual activation uri files */
+ for (l = parameters->locations; l != NULL; l = l->next)
+ {
+ char *uri;
+ location = l->data;
+
+ /* We want the file for the activation URI since we care
+ * about the attributes for that, not for the original file.
+ */
+ uri = nautilus_file_get_activation_uri (location->file);
+ if (uri != NULL)
+ {
+ launch_location_update_from_uri (location, uri);
+ }
+ g_free (uri);
+ }
+
+
+ /* get the parameters for the actual files */
+ files = get_file_list_for_launch_locations (parameters->locations);
+ nautilus_file_list_call_when_ready
+ (files,
+ nautilus_mime_actions_get_required_file_attributes (),
+ &parameters->files_handle,
+ activate_callback, parameters);
+ nautilus_file_list_free (files);
+}
+
+static void
+activate_regular_files (ActivateParameters *parameters)
+{
+ GList *l, *files;
+ NautilusFile *file;
+ LaunchLocation *location;
+
+ /* link target info might be stale, re-read it */
+ for (l = parameters->locations; l != NULL; l = l->next)
+ {
+ location = l->data;
+ file = location->file;
+
+ if (file_was_cancelled (file))
+ {
+ launch_location_free (location);
+ parameters->locations = g_list_delete_link (parameters->locations, l);
+ continue;
+ }
+ }
+
+ if (parameters->locations == NULL)
+ {
+ activation_parameters_free (parameters);
+ return;
+ }
+
+ files = get_file_list_for_launch_locations (parameters->locations);
+ nautilus_file_list_call_when_ready
+ (files, nautilus_mime_actions_get_required_file_attributes (),
+ &parameters->files_handle,
+ activate_activation_uris_ready_callback, parameters);
+ nautilus_file_list_free (files);
+}
+
+static void
+activation_mountable_mounted (NautilusFile *file,
+ GFile *result_location,
+ GError *error,
+ gpointer callback_data)
+{
+ ActivateParameters *parameters = callback_data;
+ NautilusFile *target_file;
+ LaunchLocation *location;
+
+ /* Remove from list of files that have to be mounted */
+ parameters->mountables = g_list_remove (parameters->mountables, file);
+ nautilus_file_unref (file);
+
+
+ if (error == NULL)
+ {
+ /* Replace file with the result of the mount */
+ target_file = nautilus_file_get (result_location);
+
+ location = find_launch_location_for_file (parameters->locations,
+ file);
+ if (location)
+ {
+ launch_location_update_from_file (location, target_file);
+ }
+ nautilus_file_unref (target_file);
+ }
+ else
+ {
+ /* Remove failed file */
+
+ if (error->domain != G_IO_ERROR ||
+ (error->code != G_IO_ERROR_FAILED_HANDLED &&
+ error->code != G_IO_ERROR_ALREADY_MOUNTED))
+ {
+ location = find_launch_location_for_file (parameters->locations,
+ file);
+ if (location)
+ {
+ parameters->locations =
+ g_list_remove (parameters->locations,
+ location);
+ launch_location_free (location);
+ }
+ }
+
+ if (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))
+ {
+ show_dialog (_("Unable to access location"),
+ error->message,
+ parameters->parent_window,
+ GTK_MESSAGE_ERROR);
+ }
+
+ if (error->code == G_IO_ERROR_CANCELLED)
+ {
+ activation_parameters_free (parameters);
+ return;
+ }
+ }
+
+ /* Mount more mountables */
+ activation_mount_mountables (parameters);
+}
+
+
+static void
+activation_mount_mountables (ActivateParameters *parameters)
+{
+ NautilusFile *file;
+ GMountOperation *mount_op;
+
+ if (parameters->mountables != NULL)
+ {
+ file = parameters->mountables->data;
+ mount_op = gtk_mount_operation_new (parameters->parent_window);
+ g_mount_operation_set_password_save (mount_op, G_PASSWORD_SAVE_FOR_SESSION);
+ g_signal_connect (mount_op, "notify::is-showing",
+ G_CALLBACK (activate_mount_op_active), parameters);
+ nautilus_file_mount (file,
+ mount_op,
+ parameters->cancellable,
+ activation_mountable_mounted,
+ parameters);
+ g_object_unref (mount_op);
+ return;
+ }
+
+ if (parameters->mountables == NULL && parameters->start_mountables == NULL)
+ {
+ activate_regular_files (parameters);
+ }
+}
+
+
+static void
+activation_mountable_started (NautilusFile *file,
+ GFile *gfile_of_file,
+ GError *error,
+ gpointer callback_data)
+{
+ ActivateParameters *parameters = callback_data;
+ LaunchLocation *location;
+
+ /* Remove from list of files that have to be mounted */
+ parameters->start_mountables = g_list_remove (parameters->start_mountables, file);
+ nautilus_file_unref (file);
+
+ if (error == NULL)
+ {
+ /* Remove file */
+ location = find_launch_location_for_file (parameters->locations, file);
+ if (location != NULL)
+ {
+ parameters->locations = g_list_remove (parameters->locations, location);
+ launch_location_free (location);
+ }
+ }
+ else
+ {
+ /* Remove failed file */
+ if (error->domain != G_IO_ERROR ||
+ (error->code != G_IO_ERROR_FAILED_HANDLED))
+ {
+ location = find_launch_location_for_file (parameters->locations,
+ file);
+ if (location)
+ {
+ parameters->locations =
+ g_list_remove (parameters->locations,
+ location);
+ launch_location_free (location);
+ }
+ }
+
+ if (error->domain != G_IO_ERROR ||
+ (error->code != G_IO_ERROR_CANCELLED &&
+ error->code != G_IO_ERROR_FAILED_HANDLED))
+ {
+ show_dialog (_("Unable to start location"),
+ error->message,
+ parameters->parent_window,
+ GTK_MESSAGE_ERROR);
+ }
+
+ if (error->code == G_IO_ERROR_CANCELLED)
+ {
+ activation_parameters_free (parameters);
+ return;
+ }
+ }
+
+ /* Start more mountables */
+ activation_start_mountables (parameters);
+}
+
+static void
+activation_start_mountables (ActivateParameters *parameters)
+{
+ NautilusFile *file;
+ GMountOperation *start_op;
+
+ if (parameters->start_mountables != NULL)
+ {
+ file = parameters->start_mountables->data;
+ start_op = gtk_mount_operation_new (parameters->parent_window);
+ g_signal_connect (start_op, "notify::is-showing",
+ G_CALLBACK (activate_mount_op_active), parameters);
+ nautilus_file_start (file,
+ start_op,
+ parameters->cancellable,
+ activation_mountable_started,
+ parameters);
+ g_object_unref (start_op);
+ return;
+ }
+
+ if (parameters->mountables == NULL && parameters->start_mountables == NULL)
+ {
+ activate_regular_files (parameters);
+ }
+}
+
+/**
+ * nautilus_mime_activate_files:
+ *
+ * Activate a list of files. Each one might launch with an application or
+ * with a component. This is normally called only by subclasses.
+ * @view: FMDirectoryView in question.
+ * @files: A GList of NautilusFiles to activate.
+ *
+ **/
+void
+nautilus_mime_activate_files (GtkWindow *parent_window,
+ NautilusWindowSlot *slot,
+ GList *files,
+ const char *launch_directory,
+ NautilusOpenFlags flags,
+ gboolean user_confirmation)
+{
+ ActivateParameters *parameters;
+ char *file_name;
+ int file_count;
+ GList *l, *next;
+ NautilusFile *file;
+ LaunchLocation *location;
+
+ if (files == NULL)
+ {
+ return;
+ }
+
+ DEBUG_FILES (files, "Calling activate_files() with files:");
+
+ parameters = g_new0 (ActivateParameters, 1);
+ parameters->slot = slot;
+ g_object_add_weak_pointer (G_OBJECT (parameters->slot), (gpointer *) &parameters->slot);
+ if (parent_window)
+ {
+ parameters->parent_window = parent_window;
+ g_object_add_weak_pointer (G_OBJECT (parameters->parent_window), (gpointer *) &parameters->parent_window);
+ }
+ parameters->cancellable = g_cancellable_new ();
+ parameters->activation_directory = g_strdup (launch_directory);
+ parameters->locations = launch_locations_from_file_list (files);
+ parameters->flags = flags;
+ parameters->user_confirmation = user_confirmation;
+
+ file_count = g_list_length (files);
+ if (file_count == 1)
+ {
+ file_name = nautilus_file_get_display_name (files->data);
+ parameters->timed_wait_prompt = g_strdup_printf (_("Opening “%s”."), file_name);
+ g_free (file_name);
+ }
+ else
+ {
+ parameters->timed_wait_prompt = g_strdup_printf (ngettext ("Opening %d item.",
+ "Opening %d items.",
+ file_count),
+ file_count);
+ }
+
+
+ for (l = parameters->locations; l != NULL; l = next)
+ {
+ location = l->data;
+ file = location->file;
+ next = l->next;
+
+ if (nautilus_file_can_mount (file))
+ {
+ parameters->mountables = g_list_prepend (parameters->mountables,
+ nautilus_file_ref (file));
+ }
+
+ if (nautilus_file_can_start (file))
+ {
+ parameters->start_mountables = g_list_prepend (parameters->start_mountables,
+ nautilus_file_ref (file));
+ }
+ }
+
+ activation_start_timed_cancel (parameters);
+ if (parameters->mountables != NULL)
+ {
+ activation_mount_mountables (parameters);
+ }
+ if (parameters->start_mountables != NULL)
+ {
+ activation_start_mountables (parameters);
+ }
+ if (parameters->mountables == NULL && parameters->start_mountables == NULL)
+ {
+ activate_regular_files (parameters);
+ }
+}
+
+/**
+ * nautilus_mime_activate_file:
+ *
+ * Activate a file in this view. This might involve switching the displayed
+ * location for the current window, or launching an application.
+ * @view: FMDirectoryView in question.
+ * @file: A NautilusFile representing the file in this view to activate.
+ * @use_new_window: Should this item be opened in a new window?
+ *
+ **/
+
+void
+nautilus_mime_activate_file (GtkWindow *parent_window,
+ NautilusWindowSlot *slot,
+ NautilusFile *file,
+ const char *launch_directory,
+ NautilusOpenFlags flags)
+{
+ GList *files;
+
+ g_return_if_fail (NAUTILUS_IS_FILE (file));
+
+ files = g_list_prepend (NULL, file);
+ nautilus_mime_activate_files (parent_window, slot, files, launch_directory, flags, FALSE);
+ g_list_free (files);
+}
+
+gint
+nautilus_mime_types_get_number_of_groups (void)
+{
+ return G_N_ELEMENTS (mimetype_groups);
+}
+
+const gchar *
+nautilus_mime_types_group_get_name (gint group_index)
+{
+ g_return_val_if_fail (group_index < G_N_ELEMENTS (mimetype_groups), NULL);
+
+ return gettext (mimetype_groups[group_index].name);
+}
+
+GPtrArray *
+nautilus_mime_types_group_get_mimetypes (gint group_index)
+{
+ GStrv group;
+ GPtrArray *mimetypes;
+
+ g_return_val_if_fail (group_index < G_N_ELEMENTS (mimetype_groups), NULL);
+
+ group = mimetype_groups[group_index].mimetypes;
+ mimetypes = g_ptr_array_new_full (g_strv_length (group), g_free);
+
+ /* Setup the new mimetypes set */
+ for (gint i = 0; group[i] != NULL; i++)
+ {
+ g_ptr_array_add (mimetypes, g_strdup (group[i]));
+ }
+
+ return mimetypes;
+}
diff --git a/src/nautilus-mime-actions.h b/src/nautilus-mime-actions.h
new file mode 100644
index 0000000..24b891b
--- /dev/null
+++ b/src/nautilus-mime-actions.h
@@ -0,0 +1,54 @@
+
+/* nautilus-mime-actions.h - uri-specific versions of mime action functions
+
+ Copyright (C) 2000 Eazel, Inc.
+
+ The Gnome Library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Library General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version.
+
+ The Gnome Library 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
+ Library General Public License for more details.
+
+ You should have received a copy of the GNU Library General Public
+ License along with the Gnome Library; see the file COPYING.LIB. If not,
+ see <http://www.gnu.org/licenses/>.
+
+ Authors: Maciej Stachowiak <mjs@eazel.com>
+*/
+
+#pragma once
+
+#include <gio/gio.h>
+#include <glib/gi18n.h>
+#include <gtk/gtk.h>
+
+#include "nautilus-types.h"
+
+NautilusFileAttributes nautilus_mime_actions_get_required_file_attributes (void);
+
+GAppInfo * nautilus_mime_get_default_application_for_file (NautilusFile *file);
+GList * nautilus_mime_get_applications_for_file (NautilusFile *file);
+
+GAppInfo * nautilus_mime_get_default_application_for_files (GList *files);
+
+gboolean nautilus_mime_file_extracts (NautilusFile *file);
+gboolean nautilus_mime_file_opens_in_external_app (NautilusFile *file);
+gboolean nautilus_mime_file_launches (NautilusFile *file);
+void nautilus_mime_activate_files (GtkWindow *parent_window,
+ NautilusWindowSlot *slot,
+ GList *files,
+ const char *launch_directory,
+ NautilusOpenFlags flags,
+ gboolean user_confirmation);
+void nautilus_mime_activate_file (GtkWindow *parent_window,
+ NautilusWindowSlot *slot_info,
+ NautilusFile *file,
+ const char *launch_directory,
+ NautilusOpenFlags flags);
+gint nautilus_mime_types_get_number_of_groups (void);
+const gchar* nautilus_mime_types_group_get_name (gint group_index);
+GPtrArray* nautilus_mime_types_group_get_mimetypes (gint group_index);
diff --git a/src/nautilus-module.c b/src/nautilus-module.c
new file mode 100644
index 0000000..ced810e
--- /dev/null
+++ b/src/nautilus-module.c
@@ -0,0 +1,332 @@
+/*
+ * nautilus-module.h - Interface to nautilus extensions
+ *
+ * Copyright (C) 2003 Novell, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ *
+ * Author: Dave Camp <dave@ximian.com>
+ *
+ */
+
+#include <config.h>
+#include "nautilus-module.h"
+
+#include <eel/eel-debug.h>
+#include <gmodule.h>
+
+#define NAUTILUS_TYPE_MODULE (nautilus_module_get_type ())
+#define NAUTILUS_MODULE(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), NAUTILUS_TYPE_MODULE, NautilusModule))
+#define NAUTILUS_MODULE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), NAUTILUS_TYPE_MODULE, NautilusModule))
+#define NAUTILUS_IS_MODULE(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), NAUTILUS_TYPE_MODULE))
+#define NAUTILUS_IS_MODULE_CLASS(klass) (G_TYPE_CLASS_CHECK_CLASS_TYPE ((klass), NAUTILUS_TYPE_MODULE))
+
+typedef struct _NautilusModule NautilusModule;
+typedef struct _NautilusModuleClass NautilusModuleClass;
+
+struct _NautilusModule
+{
+ GTypeModule parent;
+
+ GModule *library;
+
+ char *path;
+
+ void (*initialize) (GTypeModule *module);
+ void (*shutdown) (void);
+
+ void (*list_types) (const GType **types,
+ int *num_types);
+};
+
+struct _NautilusModuleClass
+{
+ GTypeModuleClass parent;
+};
+
+static GList *module_objects = NULL;
+static GStrv installed_module_names = NULL;
+
+static GType nautilus_module_get_type (void);
+
+G_DEFINE_TYPE (NautilusModule, nautilus_module, G_TYPE_TYPE_MODULE);
+
+static gboolean
+module_pulls_in_orbit (GModule *module)
+{
+ gpointer symbol;
+ gboolean res;
+
+ res = g_module_symbol (module, "ORBit_realloc_tcval", &symbol);
+
+ return res;
+}
+
+static gboolean
+nautilus_module_load (GTypeModule *gmodule)
+{
+ NautilusModule *module;
+
+ module = NAUTILUS_MODULE (gmodule);
+
+ module->library = g_module_open (module->path, G_MODULE_BIND_LAZY | G_MODULE_BIND_LOCAL);
+
+ if (!module->library)
+ {
+ g_warning ("%s", g_module_error ());
+ return FALSE;
+ }
+
+ /* ORBit installs atexit() handlers, which would get unloaded together
+ * with the module now that the main process doesn't depend on GConf anymore,
+ * causing nautilus to sefgault at exit.
+ * If we detect that an extension would pull in ORBit, we make the
+ * module resident to prevent that.
+ */
+ if (module_pulls_in_orbit (module->library))
+ {
+ g_module_make_resident (module->library);
+ }
+
+ if (!g_module_symbol (module->library,
+ "nautilus_module_initialize",
+ (gpointer *) &module->initialize) ||
+ !g_module_symbol (module->library,
+ "nautilus_module_shutdown",
+ (gpointer *) &module->shutdown) ||
+ !g_module_symbol (module->library,
+ "nautilus_module_list_types",
+ (gpointer *) &module->list_types))
+ {
+ g_warning ("%s", g_module_error ());
+ g_module_close (module->library);
+
+ return FALSE;
+ }
+
+ module->initialize (gmodule);
+
+ return TRUE;
+}
+
+static void
+nautilus_module_unload (GTypeModule *gmodule)
+{
+ NautilusModule *module;
+
+ module = NAUTILUS_MODULE (gmodule);
+
+ module->shutdown ();
+
+ g_module_close (module->library);
+
+ module->initialize = NULL;
+ module->shutdown = NULL;
+ module->list_types = NULL;
+}
+
+static void
+nautilus_module_finalize (GObject *object)
+{
+ NautilusModule *module;
+
+ module = NAUTILUS_MODULE (object);
+
+ g_free (module->path);
+
+ G_OBJECT_CLASS (nautilus_module_parent_class)->finalize (object);
+}
+
+static void
+nautilus_module_init (NautilusModule *module)
+{
+}
+
+static void
+nautilus_module_class_init (NautilusModuleClass *class)
+{
+ G_OBJECT_CLASS (class)->finalize = nautilus_module_finalize;
+ G_TYPE_MODULE_CLASS (class)->load = nautilus_module_load;
+ G_TYPE_MODULE_CLASS (class)->unload = nautilus_module_unload;
+}
+
+static void
+module_object_weak_notify (gpointer user_data,
+ GObject *object)
+{
+ module_objects = g_list_remove (module_objects, object);
+}
+
+static void
+add_module_objects (NautilusModule *module)
+{
+ const GType *types;
+ int num_types;
+ int i;
+
+ module->list_types (&types, &num_types);
+
+ for (i = 0; i < num_types; i++)
+ {
+ if (types[i] == 0) /* Work around broken extensions */
+ {
+ break;
+ }
+ nautilus_module_add_type (types[i]);
+ }
+}
+
+static NautilusModule *
+nautilus_module_load_file (const char *filename,
+ GStrvBuilder *installed_module_name_builder)
+{
+ NautilusModule *module;
+
+ module = g_object_new (NAUTILUS_TYPE_MODULE, NULL);
+ module->path = g_strdup (filename);
+
+ if (g_type_module_use (G_TYPE_MODULE (module)))
+ {
+ add_module_objects (module);
+ g_type_module_unuse (G_TYPE_MODULE (module));
+ g_strv_builder_add (installed_module_name_builder, filename);
+ return module;
+ }
+ else
+ {
+ g_object_unref (module);
+ return NULL;
+ }
+}
+
+char *
+nautilus_module_get_installed_module_names (void)
+{
+ return g_strjoinv ("\n", installed_module_names);
+}
+
+static void
+load_module_dir (const char *dirname)
+{
+ GDir *dir;
+
+ g_autoptr (GStrvBuilder) installed_module_name_builder = g_strv_builder_new ();
+ dir = g_dir_open (dirname, 0, NULL);
+
+ if (dir)
+ {
+ const char *name;
+
+ while ((name = g_dir_read_name (dir)))
+ {
+ if (g_str_has_suffix (name, "." G_MODULE_SUFFIX))
+ {
+ char *filename;
+
+ filename = g_build_filename (dirname,
+ name,
+ NULL);
+ nautilus_module_load_file (filename, installed_module_name_builder);
+ g_free (filename);
+ }
+ }
+
+ g_dir_close (dir);
+ }
+
+ installed_module_names = g_strv_builder_end (installed_module_name_builder);
+}
+
+static void
+free_module_objects (void)
+{
+ GList *l, *next;
+
+ for (l = module_objects; l != NULL; l = next)
+ {
+ next = l->next;
+ g_object_unref (l->data);
+ }
+
+ g_list_free (module_objects);
+ g_strfreev (installed_module_names);
+}
+
+void
+nautilus_module_setup (void)
+{
+ static gboolean initialized = FALSE;
+ const gchar *disable_plugins;
+
+ disable_plugins = g_getenv ("NAUTILUS_DISABLE_PLUGINS");
+ if (g_strcmp0 (disable_plugins, "TRUE") == 0)
+ {
+ /* Troublingshooting envvar is set to disable extensions */
+ return;
+ }
+
+ if (!initialized)
+ {
+ initialized = TRUE;
+
+ load_module_dir (NAUTILUS_EXTENSIONDIR);
+
+ eel_debug_call_at_shutdown (free_module_objects);
+ }
+}
+
+GList *
+nautilus_module_get_extensions_for_type (GType type)
+{
+ GList *l;
+ GList *ret = NULL;
+
+ for (l = module_objects; l != NULL; l = l->next)
+ {
+ if (G_TYPE_CHECK_INSTANCE_TYPE (G_OBJECT (l->data),
+ type))
+ {
+ g_object_ref (l->data);
+ ret = g_list_prepend (ret, l->data);
+ }
+ }
+
+ return ret;
+}
+
+void
+nautilus_module_extension_list_free (GList *extensions)
+{
+ GList *l, *next;
+
+ for (l = extensions; l != NULL; l = next)
+ {
+ next = l->next;
+ g_object_unref (l->data);
+ }
+ g_list_free (extensions);
+}
+
+void
+nautilus_module_add_type (GType type)
+{
+ GObject *object;
+
+ object = g_object_new (type, NULL);
+ g_object_weak_ref (object,
+ (GWeakNotify) module_object_weak_notify,
+ NULL);
+
+ module_objects = g_list_prepend (module_objects, object);
+}
diff --git a/src/nautilus-module.h b/src/nautilus-module.h
new file mode 100644
index 0000000..dc91918
--- /dev/null
+++ b/src/nautilus-module.h
@@ -0,0 +1,39 @@
+/*
+ * nautilus-module.h - Interface to nautilus extensions
+ *
+ * Copyright (C) 2003 Novell, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ *
+ * Author: Dave Camp <dave@ximian.com>
+ *
+ */
+
+#pragma once
+
+#include <glib-object.h>
+
+G_BEGIN_DECLS
+
+void nautilus_module_setup (void);
+GList *nautilus_module_get_extensions_for_type (GType type);
+void nautilus_module_extension_list_free (GList *list);
+gchar *nautilus_module_get_installed_module_names (void);
+
+
+/* Add a type to the module interface - allows nautilus to add its own modules
+ * without putting them in separate shared libraries */
+void nautilus_module_add_type (GType type);
+
+G_END_DECLS
diff --git a/src/nautilus-monitor.c b/src/nautilus-monitor.c
new file mode 100644
index 0000000..cf3ef4e
--- /dev/null
+++ b/src/nautilus-monitor.c
@@ -0,0 +1,182 @@
+/*
+ * nautilus-monitor.c: file and directory change monitoring for nautilus
+ *
+ * Copyright (C) 2000, 2001 Eazel, Inc.
+ * Copyright (C) 2016 Red Hat
+ *
+ * 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: Seth Nickell <seth@eazel.com>
+ * Darin Adler <darin@bentspoon.com>
+ * Alex Graveley <alex@ximian.com>
+ * Carlos Soriano <csoriano@gnome.org>
+ */
+
+#include <config.h>
+#include "nautilus-monitor.h"
+#include "nautilus-file-changes-queue.h"
+#include "nautilus-file-utilities.h"
+
+#include <gio/gio.h>
+
+struct NautilusMonitor
+{
+ GFileMonitor *monitor;
+ GVolumeMonitor *volume_monitor;
+ GFile *location;
+};
+
+static gboolean call_consume_changes_idle_id = 0;
+
+static gboolean
+call_consume_changes_idle_cb (gpointer not_used)
+{
+ nautilus_file_changes_consume_changes (TRUE);
+ call_consume_changes_idle_id = 0;
+ return FALSE;
+}
+
+static void
+schedule_call_consume_changes (void)
+{
+ if (call_consume_changes_idle_id == 0)
+ {
+ call_consume_changes_idle_id =
+ g_idle_add (call_consume_changes_idle_cb, NULL);
+ }
+}
+
+static void
+mount_removed (GVolumeMonitor *volume_monitor,
+ GMount *mount,
+ gpointer user_data)
+{
+ NautilusMonitor *monitor = user_data;
+ GFile *mount_location;
+
+ mount_location = g_mount_get_root (mount);
+
+ if (g_file_has_prefix (monitor->location, mount_location))
+ {
+ nautilus_file_changes_queue_file_removed (monitor->location);
+ schedule_call_consume_changes ();
+ }
+
+ g_object_unref (mount_location);
+}
+
+static void
+dir_changed (GFileMonitor *monitor,
+ GFile *child,
+ GFile *other_file,
+ GFileMonitorEvent event_type,
+ gpointer user_data)
+{
+ char *to_uri;
+
+ to_uri = NULL;
+ if (other_file)
+ {
+ to_uri = g_file_get_uri (other_file);
+ }
+
+ switch (event_type)
+ {
+ default:
+ case G_FILE_MONITOR_EVENT_CHANGED:
+ {
+ /* ignore */
+ }
+ break;
+
+ case G_FILE_MONITOR_EVENT_ATTRIBUTE_CHANGED:
+ case G_FILE_MONITOR_EVENT_CHANGES_DONE_HINT:
+ {
+ nautilus_file_changes_queue_file_changed (child);
+ }
+ break;
+
+ case G_FILE_MONITOR_EVENT_UNMOUNTED:
+ case G_FILE_MONITOR_EVENT_DELETED:
+ {
+ nautilus_file_changes_queue_file_removed (child);
+ }
+ break;
+
+ case G_FILE_MONITOR_EVENT_CREATED:
+ {
+ nautilus_file_changes_queue_file_added (child);
+ }
+ break;
+ }
+
+ g_free (to_uri);
+
+ schedule_call_consume_changes ();
+}
+
+NautilusMonitor *
+nautilus_monitor_directory (GFile *location)
+{
+ GFileMonitor *dir_monitor;
+ NautilusMonitor *ret;
+
+ ret = g_slice_new0 (NautilusMonitor);
+ dir_monitor = g_file_monitor_directory (location, G_FILE_MONITOR_WATCH_MOUNTS, NULL, NULL);
+
+ if (dir_monitor != NULL)
+ {
+ ret->monitor = dir_monitor;
+ }
+ else if (!g_file_is_native (location))
+ {
+ ret->location = g_object_ref (location);
+ ret->volume_monitor = g_volume_monitor_get ();
+ }
+
+ if (ret->monitor != NULL)
+ {
+ g_signal_connect (ret->monitor, "changed",
+ G_CALLBACK (dir_changed), ret);
+ }
+
+ if (ret->volume_monitor != NULL)
+ {
+ g_signal_connect (ret->volume_monitor, "mount-removed",
+ G_CALLBACK (mount_removed), ret);
+ }
+
+ /* We return a monitor even on failure, so we can avoid later trying again */
+ return ret;
+}
+
+void
+nautilus_monitor_cancel (NautilusMonitor *monitor)
+{
+ if (monitor->monitor != NULL)
+ {
+ g_signal_handlers_disconnect_by_func (monitor->monitor, dir_changed, monitor);
+ g_file_monitor_cancel (monitor->monitor);
+ g_object_unref (monitor->monitor);
+ }
+
+ if (monitor->volume_monitor != NULL)
+ {
+ g_signal_handlers_disconnect_by_func (monitor->volume_monitor, mount_removed, monitor);
+ g_object_unref (monitor->volume_monitor);
+ }
+
+ g_clear_object (&monitor->location);
+ g_slice_free (NautilusMonitor, monitor);
+}
diff --git a/src/nautilus-monitor.h b/src/nautilus-monitor.h
new file mode 100644
index 0000000..ad0386d
--- /dev/null
+++ b/src/nautilus-monitor.h
@@ -0,0 +1,33 @@
+/*
+ nautilus-monitor.h: file and directory change monitoring for nautilus
+
+ Copyright (C) 2000, 2001 Eazel, Inc.
+ Copyright (C) 2016 Red Hat, 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: Seth Nickell <seth@eazel.com>
+ Darin Adler <darin@bentspoon.com>
+ Carlos Soriano <csoriano@gnome.org>
+*/
+
+#pragma once
+
+#include <glib.h>
+#include <gio/gio.h>
+
+typedef struct NautilusMonitor NautilusMonitor;
+
+NautilusMonitor *nautilus_monitor_directory (GFile *location);
+void nautilus_monitor_cancel (NautilusMonitor *monitor); \ No newline at end of file
diff --git a/src/nautilus-name-cell.c b/src/nautilus-name-cell.c
new file mode 100644
index 0000000..f7e0a27
--- /dev/null
+++ b/src/nautilus-name-cell.c
@@ -0,0 +1,340 @@
+/*
+ * Copyright (C) 2022 The GNOME project contributors
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#include "nautilus-name-cell.h"
+#include "nautilus-file-utilities.h"
+
+struct _NautilusNameCell
+{
+ NautilusViewCell parent_instance;
+
+ GSignalGroup *item_signal_group;
+
+ GQuark path_attribute_q;
+ GFile *file_path_base_location;
+
+ GtkWidget *fixed_height_box;
+ GtkWidget *icon;
+ GtkWidget *label;
+ GtkWidget *emblems_box;
+ GtkWidget *snippet_button;
+ GtkWidget *snippet;
+ GtkWidget *path;
+
+ gboolean show_snippet;
+};
+
+G_DEFINE_TYPE (NautilusNameCell, nautilus_name_cell, NAUTILUS_TYPE_VIEW_CELL)
+
+static gchar *
+get_path_text (NautilusFile *file,
+ GQuark path_attribute_q,
+ GFile *base_location)
+{
+ g_autofree gchar *path = NULL;
+ g_autoptr (GFile) dir_location = NULL;
+ g_autoptr (GFile) home_location = g_file_new_for_path (g_get_home_dir ());
+ g_autoptr (GFile) root_location = g_file_new_for_path ("/");
+ GFile *relative_location_base;
+
+ if (path_attribute_q == 0)
+ {
+ return NULL;
+ }
+
+ path = nautilus_file_get_string_attribute_q (file, path_attribute_q);
+ dir_location = g_file_new_for_commandline_arg (path);
+
+ if (base_location != NULL && g_file_equal (base_location, dir_location))
+ {
+ /* Only occurs when search result is
+ * a direct child of the base location
+ */
+ return NULL;
+ }
+
+ if (g_file_equal (dir_location, home_location))
+ {
+ return nautilus_compute_title_for_location (home_location);
+ }
+
+ relative_location_base = base_location;
+ if (relative_location_base == NULL)
+ {
+ /* Only occurs in Recent, Starred and Trash. */
+ relative_location_base = home_location;
+ }
+
+ if (!g_file_equal (relative_location_base, root_location) &&
+ g_file_has_prefix (dir_location, relative_location_base))
+ {
+ g_autofree gchar *relative_path = NULL;
+ g_autofree gchar *display_name = NULL;
+
+ relative_path = g_file_get_relative_path (relative_location_base, dir_location);
+ display_name = g_filename_display_name (relative_path);
+
+ /* Ensure a trailing slash to emphasize it is a directory */
+ if (g_str_has_suffix (display_name, G_DIR_SEPARATOR_S))
+ {
+ return g_steal_pointer (&display_name);
+ }
+
+ return g_strconcat (display_name, G_DIR_SEPARATOR_S, NULL);
+ }
+
+ return g_steal_pointer (&path);
+}
+
+static void
+update_labels (NautilusNameCell *self)
+{
+ g_autoptr (NautilusViewItem) item = NULL;
+ NautilusFile *file;
+ g_autofree gchar *display_name = NULL;
+ g_autofree gchar *path_text = NULL;
+ const gchar *fts_snippet = NULL;
+
+ item = nautilus_view_cell_get_item (NAUTILUS_VIEW_CELL (self));
+ g_return_if_fail (item != NULL);
+ file = nautilus_view_item_get_file (item);
+
+ display_name = nautilus_file_get_display_name (file);
+ path_text = get_path_text (file,
+ self->path_attribute_q,
+ self->file_path_base_location);
+ if (self->show_snippet)
+ {
+ fts_snippet = nautilus_file_get_search_fts_snippet (file);
+ }
+
+ gtk_label_set_text (GTK_LABEL (self->label), display_name);
+ gtk_label_set_text (GTK_LABEL (self->path), path_text);
+ gtk_label_set_markup (GTK_LABEL (self->snippet), fts_snippet);
+
+ gtk_widget_set_visible (self->path, (path_text != NULL));
+ gtk_widget_set_visible (self->snippet_button, (fts_snippet != NULL));
+}
+
+static void
+update_icon (NautilusNameCell *self)
+{
+ NautilusFileIconFlags flags;
+ g_autoptr (GdkPaintable) icon_paintable = NULL;
+ GtkStyleContext *style_context;
+ g_autoptr (NautilusViewItem) item = NULL;
+ NautilusFile *file;
+ guint icon_size;
+ gint scale_factor;
+ int icon_height;
+ int extra_margin;
+ g_autofree gchar *thumbnail_path = NULL;
+
+ item = nautilus_view_cell_get_item (NAUTILUS_VIEW_CELL (self));
+ g_return_if_fail (item != NULL);
+
+ file = nautilus_view_item_get_file (item);
+ icon_size = nautilus_view_item_get_icon_size (item);
+ scale_factor = gtk_widget_get_scale_factor (GTK_WIDGET (self));
+ flags = NAUTILUS_FILE_ICON_FLAGS_USE_THUMBNAILS;
+
+ icon_paintable = nautilus_file_get_icon_paintable (file, icon_size, scale_factor, flags);
+ gtk_picture_set_paintable (GTK_PICTURE (self->icon), icon_paintable);
+
+ /* Set the same width for all icons regardless of aspect ratio.
+ * Don't set the width here because it would get GtkPicture w4h confused.
+ */
+ gtk_widget_set_size_request (self->fixed_height_box, icon_size, -1);
+
+ /* Give all items the same minimum width. This cannot be done by setting the
+ * width request directly, as above, because it would get mess up with
+ * height for width calculations.
+ *
+ * Instead we must add margins on both sides of the icon which, summed up
+ * with the icon's actual width, equal the desired item width. */
+ icon_height = gdk_paintable_get_intrinsic_height (icon_paintable);
+ extra_margin = (icon_size - icon_height) / 2;
+ gtk_widget_set_margin_top (self->fixed_height_box, extra_margin);
+ gtk_widget_set_margin_bottom (self->fixed_height_box, extra_margin);
+
+ style_context = gtk_widget_get_style_context (self->icon);
+ thumbnail_path = nautilus_file_get_thumbnail_path (file);
+ if (icon_size >= NAUTILUS_THUMBNAIL_MINIMUM_ICON_SIZE &&
+ thumbnail_path != NULL &&
+ nautilus_file_should_show_thumbnail (file))
+ {
+ gtk_style_context_add_class (style_context, "thumbnail");
+ }
+ else
+ {
+ gtk_style_context_remove_class (style_context, "thumbnail");
+ }
+}
+
+static void
+update_emblems (NautilusNameCell *self)
+{
+ g_autoptr (NautilusViewItem) item = NULL;
+ NautilusFile *file;
+ GtkWidget *child;
+ GtkIconTheme *theme;
+ g_autolist (GIcon) emblems = NULL;
+
+ item = nautilus_view_cell_get_item (NAUTILUS_VIEW_CELL (self));
+ g_return_if_fail (item != NULL);
+ file = nautilus_view_item_get_file (item);
+
+ /* Remove old emblems. */
+ while ((child = gtk_widget_get_first_child (self->emblems_box)) != NULL)
+ {
+ gtk_box_remove (GTK_BOX (self->emblems_box), child);
+ }
+
+ theme = gtk_icon_theme_get_for_display (gdk_display_get_default ());
+ emblems = nautilus_file_get_emblem_icons (file);
+ for (GList *l = emblems; l != NULL; l = l->next)
+ {
+ if (!gtk_icon_theme_has_gicon (theme, l->data))
+ {
+ g_autofree gchar *icon_string = g_icon_to_string (l->data);
+ g_warning ("Failed to add emblem. “%s” not found in the icon theme",
+ icon_string);
+ continue;
+ }
+
+ gtk_box_append (GTK_BOX (self->emblems_box),
+ gtk_image_new_from_gicon (l->data));
+ }
+}
+
+static void
+on_file_changed (NautilusNameCell *self)
+{
+ update_icon (self);
+ update_labels (self);
+ update_emblems (self);
+}
+
+static void
+on_item_size_changed (NautilusNameCell *self)
+{
+ update_icon (self);
+}
+
+static void
+on_item_drag_accept_changed (NautilusNameCell *self)
+{
+ gboolean drag_accept;
+ g_autoptr (NautilusViewItem) item = NULL;
+ GtkWidget *list_row = gtk_widget_get_parent (gtk_widget_get_parent (GTK_WIDGET (self)));
+
+ item = nautilus_view_cell_get_item (NAUTILUS_VIEW_CELL (self));
+ g_object_get (item, "drag-accept", &drag_accept, NULL);
+ if (drag_accept)
+ {
+ gtk_widget_set_state_flags (list_row, GTK_STATE_FLAG_DROP_ACTIVE, FALSE);
+ }
+ else
+ {
+ gtk_widget_unset_state_flags (list_row, GTK_STATE_FLAG_DROP_ACTIVE);
+ }
+}
+
+static void
+on_item_is_cut_changed (NautilusNameCell *self)
+{
+ gboolean is_cut;
+ g_autoptr (NautilusViewItem) item = NULL;
+
+ item = nautilus_view_cell_get_item (NAUTILUS_VIEW_CELL (self));
+ g_object_get (item,
+ "is-cut", &is_cut,
+ NULL);
+ if (is_cut)
+ {
+ gtk_widget_add_css_class (self->icon, "cut");
+ }
+ else
+ {
+ gtk_widget_remove_css_class (self->icon, "cut");
+ }
+}
+
+static void
+nautilus_name_cell_init (NautilusNameCell *self)
+{
+ gtk_widget_init_template (GTK_WIDGET (self));
+
+ /* Connect automatically to an item. */
+ self->item_signal_group = g_signal_group_new (NAUTILUS_TYPE_VIEW_ITEM);
+ g_signal_group_connect_swapped (self->item_signal_group, "notify::icon-size",
+ (GCallback) on_item_size_changed, self);
+ g_signal_group_connect_swapped (self->item_signal_group, "notify::drag-accept",
+ (GCallback) on_item_drag_accept_changed, self);
+ g_signal_group_connect_swapped (self->item_signal_group, "notify::is-cut",
+ (GCallback) on_item_is_cut_changed, self);
+ g_signal_group_connect_swapped (self->item_signal_group, "file-changed",
+ (GCallback) on_file_changed, self);
+ g_signal_connect_object (self->item_signal_group, "bind",
+ (GCallback) on_file_changed, self,
+ G_CONNECT_SWAPPED);
+
+ g_object_bind_property (self, "item",
+ self->item_signal_group, "target",
+ G_BINDING_SYNC_CREATE);
+}
+
+static void
+nautilus_name_cell_finalize (GObject *object)
+{
+ NautilusNameCell *self = (NautilusNameCell *) object;
+
+ g_clear_object (&self->item_signal_group);
+ g_clear_object (&self->file_path_base_location);
+ G_OBJECT_CLASS (nautilus_name_cell_parent_class)->finalize (object);
+}
+
+static void
+nautilus_name_cell_class_init (NautilusNameCellClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+
+ object_class->finalize = nautilus_name_cell_finalize;
+
+ gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/nautilus/ui/nautilus-name-cell.ui");
+
+ gtk_widget_class_bind_template_child (widget_class, NautilusNameCell, fixed_height_box);
+ gtk_widget_class_bind_template_child (widget_class, NautilusNameCell, icon);
+ gtk_widget_class_bind_template_child (widget_class, NautilusNameCell, label);
+ gtk_widget_class_bind_template_child (widget_class, NautilusNameCell, emblems_box);
+ gtk_widget_class_bind_template_child (widget_class, NautilusNameCell, snippet_button);
+ gtk_widget_class_bind_template_child (widget_class, NautilusNameCell, snippet);
+ gtk_widget_class_bind_template_child (widget_class, NautilusNameCell, path);
+}
+
+NautilusViewCell *
+nautilus_name_cell_new (NautilusListBase *view)
+{
+ return NAUTILUS_VIEW_CELL (g_object_new (NAUTILUS_TYPE_NAME_CELL,
+ "view", view,
+ NULL));
+}
+
+void
+nautilus_name_cell_set_path (NautilusNameCell *self,
+ GQuark path_attribute_q,
+ GFile *base_location)
+{
+ self->path_attribute_q = path_attribute_q;
+ g_set_object (&self->file_path_base_location, base_location);
+}
+
+void
+nautilus_name_cell_show_snippet (NautilusNameCell *self)
+{
+ self->show_snippet = TRUE;
+}
diff --git a/src/nautilus-name-cell.h b/src/nautilus-name-cell.h
new file mode 100644
index 0000000..62862cd
--- /dev/null
+++ b/src/nautilus-name-cell.h
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2022 António Fernandes <antoniof@gnome.org>
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#pragma once
+
+#include "nautilus-view-cell.h"
+
+G_BEGIN_DECLS
+
+#define NAUTILUS_TYPE_NAME_CELL (nautilus_name_cell_get_type())
+
+G_DECLARE_FINAL_TYPE (NautilusNameCell, nautilus_name_cell, NAUTILUS, NAME_CELL, NautilusViewCell)
+
+NautilusViewCell * nautilus_name_cell_new (NautilusListBase *view);
+void nautilus_name_cell_set_path (NautilusNameCell *self,
+ GQuark path_attribute_q,
+ GFile *base_location);
+void nautilus_name_cell_show_snippet (NautilusNameCell *self);
+
+G_END_DECLS
diff --git a/src/nautilus-new-folder-dialog-controller.c b/src/nautilus-new-folder-dialog-controller.c
new file mode 100644
index 0000000..9638c87
--- /dev/null
+++ b/src/nautilus-new-folder-dialog-controller.c
@@ -0,0 +1,191 @@
+/* nautilus-new-folder-dialog-controller.c
+ *
+ * Copyright (C) 2016 the Nautilus developers
+ *
+ * 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/>.
+ *
+ */
+
+#include <glib/gi18n.h>
+
+#include <eel/eel-vfs-extensions.h>
+
+#include "nautilus-new-folder-dialog-controller.h"
+
+
+struct _NautilusNewFolderDialogController
+{
+ NautilusFileNameWidgetController parent_instance;
+
+ GtkWidget *new_folder_dialog;
+
+ gboolean with_selection;
+
+ gulong response_handler_id;
+};
+
+G_DEFINE_TYPE (NautilusNewFolderDialogController, nautilus_new_folder_dialog_controller, NAUTILUS_TYPE_FILE_NAME_WIDGET_CONTROLLER)
+
+static gboolean
+nautilus_new_folder_dialog_controller_name_is_valid (NautilusFileNameWidgetController *self,
+ gchar *name,
+ gchar **error_message)
+{
+ gboolean is_valid;
+
+ is_valid = TRUE;
+ if (strlen (name) == 0)
+ {
+ is_valid = FALSE;
+ }
+ else if (strstr (name, "/") != NULL)
+ {
+ is_valid = FALSE;
+ *error_message = _("Folder names cannot contain “/”.");
+ }
+ else if (strcmp (name, ".") == 0)
+ {
+ is_valid = FALSE;
+ *error_message = _("A folder cannot be called “.”.");
+ }
+ else if (strcmp (name, "..") == 0)
+ {
+ is_valid = FALSE;
+ *error_message = _("A folder cannot be called “..”.");
+ }
+ else if (nautilus_file_name_widget_controller_is_name_too_long (self, name))
+ {
+ is_valid = FALSE;
+ *error_message = _("Folder name is too long.");
+ }
+
+ if (is_valid && g_str_has_prefix (name, "."))
+ {
+ /* We must warn about the side effect */
+ *error_message = _("Folders with “.” at the beginning of their name are hidden.");
+ return TRUE;
+ }
+
+ return is_valid;
+}
+
+static void
+new_folder_dialog_controller_on_response (GtkDialog *dialog,
+ gint response_id,
+ gpointer user_data)
+{
+ NautilusNewFolderDialogController *controller;
+
+ controller = NAUTILUS_NEW_FOLDER_DIALOG_CONTROLLER (user_data);
+
+ if (response_id != GTK_RESPONSE_OK)
+ {
+ g_signal_emit_by_name (controller, "cancelled");
+ }
+}
+
+NautilusNewFolderDialogController *
+nautilus_new_folder_dialog_controller_new (GtkWindow *parent_window,
+ NautilusDirectory *destination_directory,
+ gboolean with_selection,
+ gchar *initial_name)
+{
+ NautilusNewFolderDialogController *self;
+ g_autoptr (GtkBuilder) builder = NULL;
+ GtkWidget *new_folder_dialog;
+ GtkWidget *error_revealer;
+ GtkWidget *error_label;
+ GtkWidget *name_entry;
+ GtkWidget *activate_button;
+ GtkWidget *name_label;
+
+ builder = gtk_builder_new_from_resource ("/org/gnome/nautilus/ui/nautilus-create-folder-dialog.ui");
+ new_folder_dialog = GTK_WIDGET (gtk_builder_get_object (builder, "create_folder_dialog"));
+ error_revealer = GTK_WIDGET (gtk_builder_get_object (builder, "error_revealer"));
+ error_label = GTK_WIDGET (gtk_builder_get_object (builder, "error_label"));
+ name_entry = GTK_WIDGET (gtk_builder_get_object (builder, "name_entry"));
+ activate_button = GTK_WIDGET (gtk_builder_get_object (builder, "ok_button"));
+ name_label = GTK_WIDGET (gtk_builder_get_object (builder, "name_label"));
+
+ gtk_window_set_transient_for (GTK_WINDOW (new_folder_dialog),
+ parent_window);
+
+ self = g_object_new (NAUTILUS_TYPE_NEW_FOLDER_DIALOG_CONTROLLER,
+ "error-revealer", error_revealer,
+ "error-label", error_label,
+ "name-entry", name_entry,
+ "activate-button", activate_button,
+ "containing-directory", destination_directory, NULL);
+
+ self->with_selection = with_selection;
+
+ self->new_folder_dialog = new_folder_dialog;
+
+ self->response_handler_id = g_signal_connect (new_folder_dialog,
+ "response",
+ (GCallback) new_folder_dialog_controller_on_response,
+ self);
+
+ if (initial_name != NULL)
+ {
+ gtk_editable_set_text (GTK_EDITABLE (name_entry), initial_name);
+ }
+
+ gtk_button_set_label (GTK_BUTTON (activate_button), _("Create"));
+ gtk_label_set_text (GTK_LABEL (name_label), _("Folder name"));
+ gtk_window_set_title (GTK_WINDOW (new_folder_dialog), _("New Folder"));
+
+ gtk_widget_show (new_folder_dialog);
+
+ return self;
+}
+
+gboolean
+nautilus_new_folder_dialog_controller_get_with_selection (NautilusNewFolderDialogController *self)
+{
+ return self->with_selection;
+}
+
+static void
+nautilus_new_folder_dialog_controller_init (NautilusNewFolderDialogController *self)
+{
+}
+
+static void
+nautilus_new_folder_dialog_controller_finalize (GObject *object)
+{
+ NautilusNewFolderDialogController *self;
+
+ self = NAUTILUS_NEW_FOLDER_DIALOG_CONTROLLER (object);
+
+ if (self->new_folder_dialog != NULL)
+ {
+ g_clear_signal_handler (&self->response_handler_id, self->new_folder_dialog);
+ gtk_window_destroy (GTK_WINDOW (self->new_folder_dialog));
+ self->new_folder_dialog = NULL;
+ }
+
+ G_OBJECT_CLASS (nautilus_new_folder_dialog_controller_parent_class)->finalize (object);
+}
+
+static void
+nautilus_new_folder_dialog_controller_class_init (NautilusNewFolderDialogControllerClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ NautilusFileNameWidgetControllerClass *parent_class = NAUTILUS_FILE_NAME_WIDGET_CONTROLLER_CLASS (klass);
+
+ object_class->finalize = nautilus_new_folder_dialog_controller_finalize;
+
+ parent_class->name_is_valid = nautilus_new_folder_dialog_controller_name_is_valid;
+}
diff --git a/src/nautilus-new-folder-dialog-controller.h b/src/nautilus-new-folder-dialog-controller.h
new file mode 100644
index 0000000..a4a22a6
--- /dev/null
+++ b/src/nautilus-new-folder-dialog-controller.h
@@ -0,0 +1,36 @@
+/* nautilus-new-folder-dialog-controller.h
+ *
+ * Copyright (C) 2016 the Nautilus developers
+ *
+ * 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/>.
+ *
+ */
+
+#pragma once
+
+#include <glib.h>
+#include <gtk/gtk.h>
+
+#include "nautilus-file-name-widget-controller.h"
+#include "nautilus-directory.h"
+
+#define NAUTILUS_TYPE_NEW_FOLDER_DIALOG_CONTROLLER nautilus_new_folder_dialog_controller_get_type ()
+G_DECLARE_FINAL_TYPE (NautilusNewFolderDialogController, nautilus_new_folder_dialog_controller, NAUTILUS, NEW_FOLDER_DIALOG_CONTROLLER, NautilusFileNameWidgetController)
+
+NautilusNewFolderDialogController * nautilus_new_folder_dialog_controller_new (GtkWindow *parent_window,
+ NautilusDirectory *destination_directory,
+ gboolean with_selection,
+ gchar *initial_name);
+
+gboolean nautilus_new_folder_dialog_controller_get_with_selection (NautilusNewFolderDialogController *controller); \ No newline at end of file
diff --git a/src/nautilus-operations-ui-manager.c b/src/nautilus-operations-ui-manager.c
new file mode 100644
index 0000000..2371339
--- /dev/null
+++ b/src/nautilus-operations-ui-manager.c
@@ -0,0 +1,688 @@
+#include <glib/gi18n.h>
+
+#include "nautilus-operations-ui-manager.h"
+
+#include "nautilus-file.h"
+#include "nautilus-file-operations.h"
+#include "nautilus-file-conflict-dialog.h"
+#include "nautilus-mime-actions.h"
+#include "nautilus-program-choosing.h"
+
+typedef struct
+{
+ GSourceFunc source_func;
+ GMutex mutex;
+ GCond cond;
+ gboolean completed;
+} ContextInvokeData;
+
+G_LOCK_DEFINE_STATIC (main_context_sync);
+
+static gboolean
+invoke_main_context_source_func_wrapper (gpointer user_data)
+{
+ ContextInvokeData *data = (ContextInvokeData *) user_data;
+
+ g_mutex_lock (&data->mutex);
+
+ while (data->source_func (user_data))
+ {
+ }
+
+ return G_SOURCE_REMOVE;
+}
+
+static void
+invoke_main_context_completed (gpointer user_data)
+{
+ ContextInvokeData *data = (ContextInvokeData *) user_data;
+
+ data->completed = TRUE;
+
+ g_cond_signal (&data->cond);
+ g_mutex_unlock (&data->mutex);
+}
+
+/* This function is used to run UI on the main thread in order to ask the user
+ * for an action during an operation. Since the operation cannot progress until
+ * an action is provided by the user, the current thread needs to be blocked.
+ * For this we wait on a condition on the shared data. We proceed further
+ * unblocking the thread when invoke_main_context_completed() is called in the
+ * UI thread. The user_data pointer must reference a struct whose first member
+ * is of type ContextInvokeData.
+ */
+static void
+invoke_main_context_sync (GMainContext *main_context,
+ GSourceFunc source_func,
+ gpointer user_data)
+{
+ ContextInvokeData *data = (ContextInvokeData *) user_data;
+ /* Allow only one thread at a time to invoke the main context so we
+ * don't get race conditions which could lead to multiple dialogs being
+ * displayed at the same time
+ */
+ G_LOCK (main_context_sync);
+
+ data->source_func = source_func;
+
+ g_mutex_init (&data->mutex);
+ g_cond_init (&data->cond);
+ data->completed = FALSE;
+
+ g_mutex_lock (&data->mutex);
+
+ g_main_context_invoke (main_context,
+ invoke_main_context_source_func_wrapper,
+ user_data);
+
+ while (!data->completed)
+ {
+ g_cond_wait (&data->cond, &data->mutex);
+ }
+
+ g_mutex_unlock (&data->mutex);
+
+ G_UNLOCK (main_context_sync);
+
+ g_mutex_clear (&data->mutex);
+ g_cond_clear (&data->cond);
+}
+
+typedef struct
+{
+ ContextInvokeData parent_type;
+
+ GFile *source_name;
+ GFile *destination_name;
+ GFile *destination_directory_name;
+
+ gchar *suggestion;
+
+ GtkWindow *parent;
+
+ gboolean should_start_inactive;
+
+ FileConflictResponse *response;
+
+ NautilusFile *source;
+ NautilusFile *destination;
+ NautilusFile *destination_directory_file;
+
+ NautilusFileConflictDialog *dialog;
+
+ NautilusFileListCallback on_file_list_ready;
+ NautilusFileListHandle *handle;
+ gulong source_handler_id;
+ gulong destination_handler_id;
+} FileConflictDialogData;
+
+void
+file_conflict_response_free (FileConflictResponse *response)
+{
+ g_free (response->new_name);
+ g_slice_free (FileConflictResponse, response);
+}
+
+static void
+set_copy_move_dialog_text (FileConflictDialogData *data)
+{
+ g_autofree gchar *primary_text = NULL;
+ g_autofree gchar *secondary_text = NULL;
+ const gchar *message_extra;
+ time_t source_mtime;
+ time_t destination_mtime;
+ g_autofree gchar *message = NULL;
+ g_autofree gchar *destination_name = NULL;
+ g_autofree gchar *destination_directory_name = NULL;
+ gboolean source_is_directory;
+ gboolean destination_is_directory;
+
+ source_mtime = nautilus_file_get_mtime (data->source);
+ destination_mtime = nautilus_file_get_mtime (data->destination);
+
+ destination_name = nautilus_file_get_display_name (data->destination);
+ destination_directory_name = nautilus_file_get_display_name (data->destination_directory_file);
+
+ source_is_directory = nautilus_file_is_directory (data->source);
+ destination_is_directory = nautilus_file_is_directory (data->destination);
+
+ if (destination_is_directory)
+ {
+ if (nautilus_file_is_symbolic_link (data->source)
+ && !nautilus_file_is_symbolic_link (data->destination))
+ {
+ primary_text = g_strdup_printf (_("You are trying to replace the destination folder “%s” with a symbolic link."),
+ destination_name);
+ message = g_strdup_printf (_("This is not allowed in order to avoid the deletion of the destination folder’s contents."));
+ message_extra = _("Please rename the symbolic link or press the skip button.");
+ }
+ else if (source_is_directory)
+ {
+ primary_text = g_strdup_printf (_("Merge folder “%s”?"),
+ destination_name);
+
+ message_extra = _("Merging will ask for confirmation before replacing any files in "
+ "the folder that conflict with the files being copied.");
+
+ if (source_mtime > destination_mtime)
+ {
+ message = g_strdup_printf (_("An older folder with the same name already exists in “%s”."),
+ destination_directory_name);
+ }
+ else if (source_mtime < destination_mtime)
+ {
+ message = g_strdup_printf (_("A newer folder with the same name already exists in “%s”."),
+ destination_directory_name);
+ }
+ else
+ {
+ message = g_strdup_printf (_("Another folder with the same name already exists in “%s”."),
+ destination_directory_name);
+ }
+ }
+ else
+ {
+ primary_text = g_strdup_printf (_("Replace folder “%s”?"),
+ destination_name);
+ message_extra = _("Replacing it will remove all files in the folder.");
+ message = g_strdup_printf (_("A folder with the same name already exists in “%s”."),
+ destination_directory_name);
+ }
+ }
+ else
+ {
+ primary_text = g_strdup_printf (_("Replace file “%s”?"),
+ destination_name);
+
+ message_extra = _("Replacing it will overwrite its content.");
+
+ if (source_mtime > destination_mtime)
+ {
+ message = g_strdup_printf (_("An older file with the same name already exists in “%s”."),
+ destination_directory_name);
+ }
+ else if (source_mtime < destination_mtime)
+ {
+ message = g_strdup_printf (_("A newer file with the same name already exists in “%s”."),
+ destination_directory_name);
+ }
+ else
+ {
+ message = g_strdup_printf (_("Another file with the same name already exists in “%s”."),
+ destination_directory_name);
+ }
+ }
+
+ secondary_text = g_strdup_printf ("%s\n%s", message, message_extra);
+
+ nautilus_file_conflict_dialog_set_text (data->dialog,
+ primary_text,
+ secondary_text);
+}
+
+static void
+set_images (FileConflictDialogData *data)
+{
+ GdkPaintable *source_paintable;
+ GdkPaintable *destination_paintable;
+
+ destination_paintable = nautilus_file_get_icon_paintable (data->destination,
+ NAUTILUS_GRID_ICON_SIZE_SMALL,
+ 1,
+ NAUTILUS_FILE_ICON_FLAGS_USE_THUMBNAILS);
+
+ source_paintable = nautilus_file_get_icon_paintable (data->source,
+ NAUTILUS_GRID_ICON_SIZE_SMALL,
+ 1,
+ NAUTILUS_FILE_ICON_FLAGS_USE_THUMBNAILS);
+
+ nautilus_file_conflict_dialog_set_images (data->dialog,
+ destination_paintable,
+ source_paintable);
+
+ g_object_unref (destination_paintable);
+ g_object_unref (source_paintable);
+}
+
+static void
+set_file_labels (FileConflictDialogData *data)
+{
+ GString *destination_label;
+ GString *source_label;
+ gboolean source_is_directory;
+ gboolean destination_is_directory;
+ gboolean should_show_type;
+ g_autofree char *destination_mime_type = NULL;
+ g_autofree char *destination_date = NULL;
+ g_autofree char *destination_size = NULL;
+ g_autofree char *destination_type = NULL;
+ g_autofree char *source_date = NULL;
+ g_autofree char *source_size = NULL;
+ g_autofree char *source_type = NULL;
+
+ source_is_directory = nautilus_file_is_directory (data->source);
+ destination_is_directory = nautilus_file_is_directory (data->destination);
+
+ destination_mime_type = nautilus_file_get_mime_type (data->destination);
+ should_show_type = !nautilus_file_is_mime_type (data->source,
+ destination_mime_type);
+
+ destination_date = nautilus_file_get_string_attribute_with_default (data->destination,
+ "date_modified");
+ destination_size = nautilus_file_get_string_attribute_with_default (data->destination,
+ "size");
+
+ if (should_show_type)
+ {
+ destination_type = nautilus_file_get_string_attribute_with_default (data->destination,
+ "type");
+ }
+
+ destination_label = g_string_new (NULL);
+ if (destination_is_directory)
+ {
+ g_string_append_printf (destination_label, "<b>%s</b>\n", _("Original folder"));
+ g_string_append_printf (destination_label, "%s %s\n", _("Contents:"), destination_size);
+ }
+ else
+ {
+ g_string_append_printf (destination_label, "<b>%s</b>\n", _("Original file"));
+ g_string_append_printf (destination_label, "%s %s\n", _("Size:"), destination_size);
+ }
+
+ if (should_show_type)
+ {
+ g_string_append_printf (destination_label, "%s %s\n", _("Type:"), destination_type);
+ }
+
+ g_string_append_printf (destination_label, "%s %s", _("Last modified:"), destination_date);
+
+ source_date = nautilus_file_get_string_attribute_with_default (data->source,
+ "date_modified");
+ source_size = nautilus_file_get_string_attribute_with_default (data->source,
+ "size");
+
+ if (should_show_type)
+ {
+ source_type = nautilus_file_get_string_attribute_with_default (data->source,
+ "type");
+ }
+
+ source_label = g_string_new (NULL);
+ if (source_is_directory)
+ {
+ g_string_append_printf (source_label, "<b>%s</b>\n",
+ destination_is_directory ?
+ _("Merge with") : _("Replace with"));
+ g_string_append_printf (source_label, "%s %s\n", _("Contents:"), source_size);
+ }
+ else
+ {
+ g_string_append_printf (source_label, "<b>%s</b>\n", _("Replace with"));
+ g_string_append_printf (source_label, "%s %s\n", _("Size:"), source_size);
+ }
+
+ if (should_show_type)
+ {
+ g_string_append_printf (source_label, "%s %s\n", _("Type:"), source_type);
+ }
+
+ g_string_append_printf (source_label, "%s %s", _("Last modified:"), source_date);
+
+ nautilus_file_conflict_dialog_set_file_labels (data->dialog,
+ destination_label->str,
+ source_label->str);
+
+ g_string_free (destination_label, TRUE);
+ g_string_free (source_label, TRUE);
+}
+
+static void
+set_conflict_and_suggested_names (FileConflictDialogData *data)
+{
+ g_autofree gchar *conflict_name = NULL;
+
+ conflict_name = nautilus_file_get_edit_name (data->destination);
+
+ nautilus_file_conflict_dialog_set_conflict_name (data->dialog,
+ conflict_name);
+
+ nautilus_file_conflict_dialog_set_suggested_name (data->dialog,
+ data->suggestion);
+}
+
+static void
+set_replace_button_label (FileConflictDialogData *data)
+{
+ gboolean source_is_directory, destination_is_directory;
+
+ source_is_directory = nautilus_file_is_directory (data->source);
+ destination_is_directory = nautilus_file_is_directory (data->destination);
+
+ if (destination_is_directory)
+ {
+ if (nautilus_file_is_symbolic_link (data->source)
+ && !nautilus_file_is_symbolic_link (data->destination))
+ {
+ nautilus_file_conflict_dialog_disable_replace (data->dialog);
+ nautilus_file_conflict_dialog_disable_apply_to_all (data->dialog);
+ }
+ else if (source_is_directory)
+ {
+ nautilus_file_conflict_dialog_set_replace_button_label (data->dialog,
+ _("Merge"));
+ }
+ }
+}
+
+static void
+file_icons_changed (NautilusFile *file,
+ FileConflictDialogData *data)
+{
+ set_images (data);
+}
+
+static void
+copy_move_conflict_on_file_list_ready (GList *files,
+ gpointer user_data)
+{
+ FileConflictDialogData *data = user_data;
+ g_autofree gchar *title = NULL;
+
+ data->handle = NULL;
+
+ if (nautilus_file_is_directory (data->source))
+ {
+ title = g_strdup (nautilus_file_is_directory (data->destination) ?
+ _("Merge Folder") :
+ _("File and Folder conflict"));
+ }
+ else
+ {
+ title = g_strdup (nautilus_file_is_directory (data->destination) ?
+ _("File and Folder conflict") :
+ _("File conflict"));
+ }
+
+ gtk_window_set_title (GTK_WINDOW (data->dialog), title);
+
+ set_copy_move_dialog_text (data);
+
+ set_images (data);
+
+ set_file_labels (data);
+
+ set_conflict_and_suggested_names (data);
+
+ set_replace_button_label (data);
+
+ nautilus_file_monitor_add (data->source, data, NAUTILUS_FILE_ATTRIBUTES_FOR_ICON);
+ nautilus_file_monitor_add (data->destination, data, NAUTILUS_FILE_ATTRIBUTES_FOR_ICON);
+
+ data->source_handler_id = g_signal_connect (data->source, "changed",
+ G_CALLBACK (file_icons_changed), data);
+ data->destination_handler_id = g_signal_connect (data->destination, "changed",
+ G_CALLBACK (file_icons_changed), data);
+}
+
+static void
+on_conflict_dialog_response (GtkDialog *dialog,
+ gint response_id,
+ gpointer user_data)
+{
+ FileConflictDialogData *data = user_data;
+
+ if (data->handle != NULL)
+ {
+ nautilus_file_list_cancel_call_when_ready (data->handle);
+ }
+
+ if (data->source_handler_id)
+ {
+ g_signal_handler_disconnect (data->source, data->source_handler_id);
+ nautilus_file_monitor_remove (data->source, data);
+ }
+
+ if (data->destination_handler_id)
+ {
+ g_signal_handler_disconnect (data->destination, data->destination_handler_id);
+ nautilus_file_monitor_remove (data->destination, data);
+ }
+
+ if (response_id == CONFLICT_RESPONSE_RENAME)
+ {
+ data->response->new_name =
+ nautilus_file_conflict_dialog_get_new_name (data->dialog);
+ }
+ else if (response_id != GTK_RESPONSE_CANCEL &&
+ response_id != GTK_RESPONSE_NONE)
+ {
+ data->response->apply_to_all =
+ nautilus_file_conflict_dialog_get_apply_to_all (data->dialog);
+ }
+
+ data->response->id = response_id;
+
+ gtk_window_destroy (GTK_WINDOW (data->dialog));
+
+ nautilus_file_unref (data->source);
+ nautilus_file_unref (data->destination);
+ nautilus_file_unref (data->destination_directory_file);
+
+ invoke_main_context_completed (user_data);
+}
+
+static gboolean
+run_file_conflict_dialog (gpointer user_data)
+{
+ FileConflictDialogData *data = user_data;
+ GList *files = NULL;
+
+ data->source = nautilus_file_get (data->source_name);
+ data->destination = nautilus_file_get (data->destination_name);
+ data->destination_directory_file = nautilus_file_get (data->destination_directory_name);
+
+ data->dialog = nautilus_file_conflict_dialog_new (data->parent);
+
+ if (data->should_start_inactive)
+ {
+ nautilus_file_conflict_dialog_delay_buttons_activation (data->dialog);
+ }
+
+ files = g_list_prepend (files, data->source);
+ files = g_list_prepend (files, data->destination);
+ files = g_list_prepend (files, data->destination_directory_file);
+
+ nautilus_file_list_call_when_ready (files,
+ NAUTILUS_FILE_ATTRIBUTES_FOR_ICON | NAUTILUS_FILE_ATTRIBUTE_DIRECTORY_ITEM_COUNT,
+ &data->handle,
+ data->on_file_list_ready,
+ data);
+
+ g_signal_connect (data->dialog, "response", G_CALLBACK (on_conflict_dialog_response), data);
+ gtk_widget_show (GTK_WIDGET (data->dialog));
+
+ g_list_free (files);
+
+ return G_SOURCE_REMOVE;
+}
+
+FileConflictResponse *
+copy_move_conflict_ask_user_action (GtkWindow *parent_window,
+ gboolean should_start_inactive,
+ GFile *source_name,
+ GFile *destination_name,
+ GFile *destination_directory_name,
+ gchar *suggestion)
+{
+ FileConflictDialogData *data;
+ FileConflictResponse *response;
+
+ data = g_slice_new0 (FileConflictDialogData);
+ data->parent = parent_window;
+ data->should_start_inactive = should_start_inactive;
+ data->source_name = source_name;
+ data->destination_name = destination_name;
+ data->destination_directory_name = destination_directory_name;
+ data->suggestion = suggestion;
+
+ data->response = g_slice_new0 (FileConflictResponse);
+ data->response->new_name = NULL;
+
+ data->on_file_list_ready = copy_move_conflict_on_file_list_ready;
+
+ invoke_main_context_sync (NULL,
+ run_file_conflict_dialog,
+ data);
+
+ response = g_steal_pointer (&data->response);
+ g_slice_free (FileConflictDialogData, data);
+
+ return response;
+}
+
+typedef struct
+{
+ ContextInvokeData parent_type;
+ GtkWindow *parent_window;
+ NautilusFile *file;
+} HandleUnsupportedFileData;
+
+static void
+on_app_chooser_response (GtkDialog *dialog,
+ gint response_id,
+ gpointer user_data)
+{
+ HandleUnsupportedFileData *data = user_data;
+ g_autoptr (GAppInfo) application = NULL;
+
+ if (response_id == GTK_RESPONSE_OK)
+ {
+ application = gtk_app_chooser_get_app_info (GTK_APP_CHOOSER (dialog));
+ }
+
+ gtk_window_destroy (GTK_WINDOW (dialog));
+
+ if (application != NULL)
+ {
+ GList files = {data->file, NULL, NULL};
+ nautilus_launch_application (application, &files, data->parent_window);
+ }
+
+ invoke_main_context_completed (user_data);
+}
+
+static gboolean
+open_file_in_application (gpointer user_data)
+{
+ HandleUnsupportedFileData *data;
+ g_autofree gchar *mime_type = NULL;
+ GtkWidget *dialog;
+ const char *heading;
+
+ data = user_data;
+ mime_type = nautilus_file_get_mime_type (data->file);
+ dialog = gtk_app_chooser_dialog_new_for_content_type (data->parent_window,
+ GTK_DIALOG_MODAL |
+ GTK_DIALOG_DESTROY_WITH_PARENT |
+ GTK_DIALOG_USE_HEADER_BAR,
+ mime_type);
+ heading = _("Password-protected archives are not yet supported. "
+ "This list contains applications that can open the archive.");
+
+ gtk_app_chooser_dialog_set_heading (GTK_APP_CHOOSER_DIALOG (dialog), heading);
+
+ g_signal_connect (dialog, "response", G_CALLBACK (on_app_chooser_response), data);
+ gtk_widget_show (dialog);
+
+ return G_SOURCE_REMOVE;
+}
+
+/* This is used to open compressed files that are not supported by gnome-autoar
+ * in another application
+ */
+void
+handle_unsupported_compressed_file (GtkWindow *parent_window,
+ GFile *compressed_file)
+{
+ HandleUnsupportedFileData *data;
+
+ data = g_slice_new0 (HandleUnsupportedFileData);
+ data->parent_window = parent_window;
+ data->file = nautilus_file_get (compressed_file);
+
+ invoke_main_context_sync (NULL, open_file_in_application, data);
+
+ nautilus_file_unref (data->file);
+ g_slice_free (HandleUnsupportedFileData, data);
+
+ return;
+}
+
+typedef struct
+{
+ ContextInvokeData parent_type;
+ GtkWindow *parent_window;
+ const gchar *basename;
+ GtkEntry *passphrase_entry;
+ gchar *passphrase;
+} PassphraseRequestData;
+
+static void
+on_request_passphrase_cb (GtkDialog *dialog,
+ gint response_id,
+ gpointer user_data)
+{
+ PassphraseRequestData *data = user_data;
+
+ if (response_id != GTK_RESPONSE_CANCEL &&
+ response_id != GTK_RESPONSE_DELETE_EVENT)
+ {
+ data->passphrase = g_strdup (gtk_editable_get_text (GTK_EDITABLE (data->passphrase_entry)));
+ }
+
+ gtk_window_destroy (GTK_WINDOW (dialog));
+ invoke_main_context_completed (data);
+}
+
+static gboolean
+run_passphrase_dialog (gpointer user_data)
+{
+ PassphraseRequestData *data = user_data;
+ g_autofree gchar *label_str = NULL;
+ g_autoptr (GtkBuilder) builder = NULL;
+ GObject *dialog;
+ GObject *label;
+
+ builder = gtk_builder_new_from_resource ("/org/gnome/nautilus/ui/nautilus-operations-ui-manager-request-passphrase.ui");
+ dialog = gtk_builder_get_object (builder, "request_passphrase_dialog");
+ label = gtk_builder_get_object (builder, "label");
+ data->passphrase_entry = GTK_ENTRY (gtk_builder_get_object (builder, "entry"));
+
+ label_str = g_strdup_printf (_("“%s” is password-protected."), data->basename);
+ gtk_label_set_text (GTK_LABEL (label), label_str);
+
+ g_signal_connect (dialog, "response", G_CALLBACK (on_request_passphrase_cb), data);
+ gtk_window_set_transient_for (GTK_WINDOW (dialog), data->parent_window);
+ gtk_widget_show (GTK_WIDGET (dialog));
+
+ return G_SOURCE_REMOVE;
+}
+
+gchar *
+extract_ask_passphrase (GtkWindow *parent_window,
+ const gchar *archive_basename)
+{
+ PassphraseRequestData *data;
+ gchar *passphrase;
+
+ data = g_new0 (PassphraseRequestData, 1);
+ data->parent_window = parent_window;
+ data->basename = archive_basename;
+ invoke_main_context_sync (NULL, run_passphrase_dialog, data);
+
+ passphrase = g_steal_pointer (&data->passphrase);
+ g_free (data);
+
+ return passphrase;
+}
diff --git a/src/nautilus-operations-ui-manager.h b/src/nautilus-operations-ui-manager.h
new file mode 100644
index 0000000..3bd4512
--- /dev/null
+++ b/src/nautilus-operations-ui-manager.h
@@ -0,0 +1,34 @@
+#pragma once
+
+#include <gio/gio.h>
+#include <gtk/gtk.h>
+
+#define BUTTON_ACTIVATION_DELAY_IN_SECONDS 2
+
+typedef struct {
+ int id;
+ char *new_name;
+ gboolean apply_to_all;
+} FileConflictResponse;
+
+void file_conflict_response_free (FileConflictResponse *data);
+
+FileConflictResponse * copy_move_conflict_ask_user_action (GtkWindow *parent_window,
+ gboolean should_start_inactive,
+ GFile *src,
+ GFile *dest,
+ GFile *dest_dir,
+ gchar *suggestion);
+
+enum
+{
+ CONFLICT_RESPONSE_SKIP = 1,
+ CONFLICT_RESPONSE_REPLACE = 2,
+ CONFLICT_RESPONSE_RENAME = 3,
+};
+
+void handle_unsupported_compressed_file (GtkWindow *parent_window,
+ GFile *compressed_file);
+
+gchar *extract_ask_passphrase (GtkWindow *parent_window,
+ const gchar *archive_basename);
diff --git a/src/nautilus-pathbar.c b/src/nautilus-pathbar.c
new file mode 100644
index 0000000..0c880f3
--- /dev/null
+++ b/src/nautilus-pathbar.c
@@ -0,0 +1,1216 @@
+/* nautilus-pathbar.c
+ * Copyright (C) 2004 Red Hat, Inc., Jonathan Blandford <jrb@gnome.org>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+
+#include <config.h>
+#include <string.h>
+#include <gtk/gtk.h>
+#include <glib/gi18n.h>
+#include <gio/gio.h>
+
+#include "nautilus-pathbar.h"
+#include "nautilus-properties-window.h"
+
+#include "nautilus-enums.h"
+#include "nautilus-enum-types.h"
+#include "nautilus-file.h"
+#include "nautilus-file-utilities.h"
+#include "nautilus-global-preferences.h"
+#include "nautilus-icon-names.h"
+#include "nautilus-trash-monitor.h"
+#include "nautilus-ui-utilities.h"
+
+#include "nautilus-window-slot-dnd.h"
+
+enum
+{
+ OPEN_LOCATION,
+ LAST_SIGNAL
+};
+
+typedef enum
+{
+ NORMAL_BUTTON,
+ OTHER_LOCATIONS_BUTTON,
+ ROOT_BUTTON,
+ ADMIN_ROOT_BUTTON,
+ HOME_BUTTON,
+ STARRED_BUTTON,
+ RECENT_BUTTON,
+ MOUNT_BUTTON,
+ TRASH_BUTTON,
+} ButtonType;
+
+#define BUTTON_DATA(x) ((ButtonData *) (x))
+
+static guint path_bar_signals[LAST_SIGNAL] = { 0 };
+
+#define NAUTILUS_PATH_BAR_BUTTON_ELLISPIZE_MINIMUM_CHARS 7
+
+typedef struct
+{
+ GtkWidget *button;
+ ButtonType type;
+ char *dir_name;
+ GFile *path;
+ NautilusFile *file;
+ unsigned int file_changed_signal_id;
+
+ GtkWidget *image;
+ GtkWidget *label;
+ GtkWidget *separator;
+ GtkWidget *container;
+
+ NautilusPathBar *path_bar;
+
+ guint ignore_changes : 1;
+ guint is_root : 1;
+} ButtonData;
+
+struct _NautilusPathBar
+{
+ GtkBox parent_instance;
+
+ GtkWidget *scrolled;
+ GtkWidget *buttons_box;
+
+ GFile *current_path;
+ gpointer current_button_data;
+
+ GList *button_list;
+
+ GActionGroup *action_group;
+
+ NautilusFile *context_menu_file;
+ GtkPopoverMenu *current_view_menu_popover;
+ GtkWidget *current_view_menu_button;
+ GtkWidget *button_menu_popover;
+ GMenu *current_view_menu;
+ GMenu *extensions_section;
+ GMenu *templates_submenu;
+ GMenu *button_menu;
+
+ gchar *os_name;
+};
+
+G_DEFINE_TYPE (NautilusPathBar, nautilus_path_bar, GTK_TYPE_BOX);
+
+static void nautilus_path_bar_update_button_state (ButtonData *button_data,
+ gboolean current_dir);
+static void nautilus_path_bar_update_path (NautilusPathBar *self,
+ GFile *file_path);
+
+static void unschedule_pop_up_context_menu (NautilusPathBar *self);
+static void action_pathbar_open_item_new_window (GSimpleAction *action,
+ GVariant *state,
+ gpointer user_data);
+static void action_pathbar_open_item_new_tab (GSimpleAction *action,
+ GVariant *state,
+ gpointer user_data);
+static void action_pathbar_properties (GSimpleAction *action,
+ GVariant *state,
+ gpointer user_data);
+static void pop_up_pathbar_context_menu (NautilusPathBar *self,
+ NautilusFile *file);
+static void nautilus_path_bar_clear_buttons (NautilusPathBar *self);
+
+const GActionEntry path_bar_actions[] =
+{
+ { "open-item-new-tab", action_pathbar_open_item_new_tab },
+ { "open-item-new-window", action_pathbar_open_item_new_window },
+ { "properties", action_pathbar_properties}
+};
+
+
+static void
+action_pathbar_open_item_new_tab (GSimpleAction *action,
+ GVariant *state,
+ gpointer user_data)
+{
+ NautilusPathBar *self;
+ GFile *location;
+
+ self = NAUTILUS_PATH_BAR (user_data);
+
+ if (self->context_menu_file == NULL)
+ {
+ return;
+ }
+
+ location = nautilus_file_get_location (self->context_menu_file);
+
+ if (location)
+ {
+ g_signal_emit (user_data, path_bar_signals[OPEN_LOCATION], 0, location,
+ NAUTILUS_OPEN_FLAG_NEW_TAB | NAUTILUS_OPEN_FLAG_DONT_MAKE_ACTIVE);
+ g_object_unref (location);
+ }
+}
+
+static void
+action_pathbar_open_item_new_window (GSimpleAction *action,
+ GVariant *state,
+ gpointer user_data)
+{
+ NautilusPathBar *self;
+ GFile *location;
+
+ self = NAUTILUS_PATH_BAR (user_data);
+
+ if (self->context_menu_file == NULL)
+ {
+ return;
+ }
+
+ location = nautilus_file_get_location (self->context_menu_file);
+
+ if (location)
+ {
+ g_signal_emit (user_data, path_bar_signals[OPEN_LOCATION], 0, location, NAUTILUS_OPEN_FLAG_NEW_WINDOW);
+ g_object_unref (location);
+ }
+}
+
+static void
+action_pathbar_properties (GSimpleAction *action,
+ GVariant *state,
+ gpointer user_data)
+{
+ NautilusPathBar *self;
+ GList *files;
+
+ self = NAUTILUS_PATH_BAR (user_data);
+
+ g_return_if_fail (NAUTILUS_IS_FILE (self->context_menu_file));
+
+ files = g_list_append (NULL, nautilus_file_ref (self->context_menu_file));
+
+ nautilus_properties_window_present (files, GTK_WIDGET (self), NULL, NULL,
+ NULL);
+
+ nautilus_file_list_free (files);
+}
+
+static void
+on_adjustment_changed (GtkAdjustment *adjustment,
+ NautilusPathBar *self)
+{
+ /* Automatically scroll to the end, to reveal the current folder. */
+ g_autoptr (AdwAnimation) anim = NULL;
+ anim = adw_timed_animation_new (GTK_WIDGET (self),
+ gtk_adjustment_get_value (adjustment),
+ gtk_adjustment_get_upper (adjustment),
+ 800,
+ adw_property_animation_target_new (G_OBJECT (adjustment), "value"));
+ adw_timed_animation_set_easing (ADW_TIMED_ANIMATION (anim), ADW_EASE_OUT_CUBIC);
+ adw_animation_play (anim);
+}
+
+static void
+on_page_size_changed (GtkAdjustment *adjustment)
+{
+ /* When window is resized, immediately set new value, otherwise we would get
+ * an underflow gradient for an moment. */
+ gtk_adjustment_set_value (adjustment, gtk_adjustment_get_upper (adjustment));
+}
+
+static gboolean
+bind_current_view_menu_model_to_popover (NautilusPathBar *self)
+{
+ gtk_popover_menu_set_menu_model (self->current_view_menu_popover,
+ G_MENU_MODEL (self->current_view_menu));
+ return G_SOURCE_REMOVE;
+}
+
+static void
+nautilus_path_bar_init (NautilusPathBar *self)
+{
+ GtkAdjustment *adjustment;
+ GtkBuilder *builder;
+ g_autoptr (GError) error = NULL;
+
+ self->os_name = g_get_os_info (G_OS_INFO_KEY_NAME);
+
+ self->scrolled = gtk_scrolled_window_new ();
+ /* Scroll horizontally only and don't use internal scrollbar. */
+ gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (self->scrolled),
+ /* hscrollbar-policy */ GTK_POLICY_EXTERNAL,
+ /* vscrollbar-policy */ GTK_POLICY_NEVER);
+ gtk_widget_set_hexpand (self->scrolled, TRUE);
+ gtk_box_append (GTK_BOX (self), self->scrolled);
+
+ adjustment = gtk_scrolled_window_get_hadjustment (GTK_SCROLLED_WINDOW (self->scrolled));
+ g_signal_connect (adjustment, "changed", G_CALLBACK (on_adjustment_changed), self);
+ g_signal_connect (adjustment, "notify::page-size", G_CALLBACK (on_page_size_changed), self);
+
+ self->buttons_box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
+ gtk_scrolled_window_set_child (GTK_SCROLLED_WINDOW (self->scrolled), self->buttons_box);
+
+ self->current_view_menu_button = gtk_menu_button_new ();
+ gtk_widget_add_css_class (self->current_view_menu_button, "flat");
+ gtk_menu_button_set_child (GTK_MENU_BUTTON (self->current_view_menu_button),
+ gtk_image_new_from_icon_name ("view-more-symbolic"));
+ gtk_box_append (GTK_BOX (self), self->current_view_menu_button);
+
+ gtk_widget_set_tooltip_text (self->current_view_menu_button, _("Current Folder Menu"));
+
+ builder = gtk_builder_new ();
+
+ /* Add context menu for pathbar buttons */
+ gtk_builder_add_from_resource (builder,
+ "/org/gnome/nautilus/ui/nautilus-pathbar-context-menu.ui",
+ &error);
+ if (error != NULL)
+ {
+ g_error ("Failed to add pathbar-context-menu.ui: %s", error->message);
+ }
+ self->button_menu = g_object_ref_sink (G_MENU (gtk_builder_get_object (builder, "button-menu")));
+ self->button_menu_popover = gtk_popover_menu_new_from_model (G_MENU_MODEL (self->button_menu));
+ gtk_widget_set_parent (self->button_menu_popover, GTK_WIDGET (self));
+ gtk_popover_set_has_arrow (GTK_POPOVER (self->button_menu_popover), FALSE);
+ gtk_widget_set_halign (self->button_menu_popover, GTK_ALIGN_START);
+
+ /* Add current location menu, which shares features with the view's background context menu */
+ self->current_view_menu = g_object_ref_sink (G_MENU (gtk_builder_get_object (builder, "current-view-menu")));
+ self->extensions_section = g_object_ref (G_MENU (gtk_builder_get_object (builder, "background-extensions-section")));
+ self->templates_submenu = g_object_ref (G_MENU (gtk_builder_get_object (builder, "templates-submenu")));
+ self->current_view_menu_popover = g_object_ref_sink (GTK_POPOVER_MENU (gtk_popover_menu_new_from_model (NULL)));
+
+ g_object_unref (builder);
+
+ gtk_menu_button_set_popover (GTK_MENU_BUTTON (self->current_view_menu_button),
+ GTK_WIDGET (self->current_view_menu_popover));
+ bind_current_view_menu_model_to_popover (self);
+
+ gtk_widget_set_name (GTK_WIDGET (self), "NautilusPathBar");
+ gtk_widget_add_css_class (GTK_WIDGET (self), "linked");
+
+ /* Action group */
+ self->action_group = G_ACTION_GROUP (g_simple_action_group_new ());
+ g_action_map_add_action_entries (G_ACTION_MAP (self->action_group),
+ path_bar_actions,
+ G_N_ELEMENTS (path_bar_actions),
+ self);
+ gtk_widget_insert_action_group (GTK_WIDGET (self),
+ "pathbar",
+ G_ACTION_GROUP (self->action_group));
+}
+
+static void
+nautilus_path_bar_finalize (GObject *object)
+{
+ NautilusPathBar *self;
+
+ self = NAUTILUS_PATH_BAR (object);
+
+ g_clear_object (&self->current_view_menu);
+ g_clear_object (&self->extensions_section);
+ g_clear_object (&self->templates_submenu);
+ g_clear_object (&self->button_menu);
+ g_clear_object (&self->current_view_menu_popover);
+ g_free (self->os_name);
+
+ unschedule_pop_up_context_menu (NAUTILUS_PATH_BAR (object));
+
+ G_OBJECT_CLASS (nautilus_path_bar_parent_class)->finalize (object);
+}
+
+static void
+nautilus_path_bar_dispose (GObject *object)
+{
+ NautilusPathBar *self = NAUTILUS_PATH_BAR (object);
+
+ nautilus_path_bar_clear_buttons (self);
+
+ G_OBJECT_CLASS (nautilus_path_bar_parent_class)->dispose (object);
+}
+
+static const char *
+get_dir_name (ButtonData *button_data)
+{
+ switch (button_data->type)
+ {
+ case ROOT_BUTTON:
+ {
+ if (button_data->path_bar != NULL &&
+ button_data->path_bar->os_name != NULL)
+ {
+ return button_data->path_bar->os_name;
+ }
+ /* Translators: This is the label used in the pathbar when seeing
+ * the root directory (also known as /) */
+ return _("Operating System");
+ }
+
+ case ADMIN_ROOT_BUTTON:
+ {
+ /* Translators: This is the filesystem root directory (also known
+ * as /) when seen as administrator */
+ return _("Administrator Root");
+ }
+
+ case HOME_BUTTON:
+ {
+ return _("Home");
+ }
+
+ case OTHER_LOCATIONS_BUTTON:
+ {
+ return _("Other Locations");
+ }
+
+ case STARRED_BUTTON:
+ {
+ return _("Starred");
+ }
+
+ default:
+ {
+ return button_data->dir_name;
+ }
+ }
+}
+
+static void
+button_data_free (ButtonData *button_data)
+{
+ g_object_unref (button_data->path);
+ g_free (button_data->dir_name);
+ if (button_data->file != NULL)
+ {
+ g_signal_handler_disconnect (button_data->file,
+ button_data->file_changed_signal_id);
+ nautilus_file_monitor_remove (button_data->file, button_data);
+ nautilus_file_unref (button_data->file);
+ }
+
+ g_free (button_data);
+}
+
+static void
+nautilus_path_bar_class_init (NautilusPathBarClass *path_bar_class)
+{
+ GObjectClass *gobject_class;
+
+ gobject_class = (GObjectClass *) path_bar_class;
+
+ gobject_class->finalize = nautilus_path_bar_finalize;
+ gobject_class->dispose = nautilus_path_bar_dispose;
+
+ path_bar_signals [OPEN_LOCATION] =
+ g_signal_new ("open-location",
+ G_OBJECT_CLASS_TYPE (path_bar_class),
+ G_SIGNAL_RUN_FIRST | G_SIGNAL_RUN_LAST,
+ 0,
+ NULL, NULL, NULL,
+ G_TYPE_NONE, 2,
+ G_TYPE_FILE,
+ NAUTILUS_TYPE_OPEN_FLAGS);
+}
+
+void
+nautilus_path_bar_set_extensions_background_menu (NautilusPathBar *self,
+ GMenuModel *menu)
+{
+ g_return_if_fail (NAUTILUS_IS_PATH_BAR (self));
+
+ nautilus_gmenu_set_from_model (self->extensions_section, menu);
+}
+
+void
+nautilus_path_bar_set_templates_menu (NautilusPathBar *self,
+ GMenuModel *menu)
+{
+ gint i;
+
+ g_return_if_fail (NAUTILUS_IS_PATH_BAR (self));
+
+ if (!gtk_widget_is_visible (GTK_WIDGET (self->current_view_menu_popover)))
+ {
+ /* Workaround to avoid leaking duplicated GtkStack pages each time the
+ * templates menu is set. Unbinding the model is the only way to clear
+ * all children. After that's done, on idle, we rebind it.
+ * See https://gitlab.gnome.org/GNOME/nautilus/-/issues/1705 */
+ gtk_popover_menu_set_menu_model (self->current_view_menu_popover, NULL);
+ }
+
+ nautilus_gmenu_set_from_model (self->templates_submenu, menu);
+ g_idle_add ((GSourceFunc) bind_current_view_menu_model_to_popover, self);
+
+ i = nautilus_g_menu_model_find_by_string (G_MENU_MODEL (self->current_view_menu),
+ "nautilus-menu-item",
+ "templates-submenu");
+ nautilus_g_menu_replace_string_in_item (self->current_view_menu, i,
+ "hidden-when",
+ (menu == NULL) ? "action-missing" : NULL);
+}
+
+/* Public functions and their helpers */
+static void
+nautilus_path_bar_clear_buttons (NautilusPathBar *self)
+{
+ while (self->button_list != NULL)
+ {
+ ButtonData *button_data;
+
+ button_data = BUTTON_DATA (self->button_list->data);
+
+ gtk_box_remove (GTK_BOX (self->buttons_box), button_data->container);
+
+ self->button_list = g_list_remove (self->button_list, button_data);
+ button_data_free (button_data);
+ }
+}
+
+void
+nautilus_path_bar_show_current_location_menu (NautilusPathBar *self)
+{
+ g_return_if_fail (NAUTILUS_IS_PATH_BAR (self));
+
+ gtk_menu_button_popup (GTK_MENU_BUTTON (self->current_view_menu_button));
+}
+
+static void
+button_clicked_cb (GtkButton *button,
+ gpointer data)
+{
+ ButtonData *button_data;
+ NautilusPathBar *self;
+
+ button_data = BUTTON_DATA (data);
+ if (button_data->ignore_changes)
+ {
+ return;
+ }
+
+ self = button_data->path_bar;
+
+ if (g_file_equal (button_data->path, self->current_path))
+ {
+ return;
+ }
+ else
+ {
+ g_signal_emit (self, path_bar_signals[OPEN_LOCATION], 0,
+ button_data->path,
+ 0);
+ }
+}
+
+static void
+real_pop_up_pathbar_context_menu (NautilusPathBar *self)
+{
+ gtk_popover_popup (GTK_POPOVER (self->button_menu_popover));
+}
+
+static void
+pathbar_popup_file_attributes_ready (NautilusFile *file,
+ gpointer data)
+{
+ NautilusPathBar *self;
+
+ g_return_if_fail (NAUTILUS_IS_PATH_BAR (data));
+
+ self = NAUTILUS_PATH_BAR (data);
+
+ g_return_if_fail (file == self->context_menu_file);
+
+ real_pop_up_pathbar_context_menu (self);
+}
+
+static void
+unschedule_pop_up_context_menu (NautilusPathBar *self)
+{
+ if (self->context_menu_file != NULL)
+ {
+ g_return_if_fail (NAUTILUS_IS_FILE (self->context_menu_file));
+ nautilus_file_cancel_call_when_ready (self->context_menu_file,
+ pathbar_popup_file_attributes_ready,
+ self);
+ g_clear_pointer (&self->context_menu_file, nautilus_file_unref);
+ }
+}
+
+static void
+schedule_pop_up_context_menu (NautilusPathBar *self,
+ NautilusFile *file)
+{
+ g_return_if_fail (NAUTILUS_IS_FILE (file));
+
+ if (file == self->context_menu_file)
+ {
+ if (nautilus_file_check_if_ready (file,
+ NAUTILUS_FILE_ATTRIBUTE_INFO |
+ NAUTILUS_FILE_ATTRIBUTE_MOUNT |
+ NAUTILUS_FILE_ATTRIBUTE_FILESYSTEM_INFO))
+ {
+ real_pop_up_pathbar_context_menu (self);
+ }
+ }
+ else
+ {
+ unschedule_pop_up_context_menu (self);
+
+ self->context_menu_file = nautilus_file_ref (file);
+ nautilus_file_call_when_ready (self->context_menu_file,
+ NAUTILUS_FILE_ATTRIBUTE_INFO |
+ NAUTILUS_FILE_ATTRIBUTE_MOUNT |
+ NAUTILUS_FILE_ATTRIBUTE_FILESYSTEM_INFO,
+ pathbar_popup_file_attributes_ready,
+ self);
+ }
+}
+
+static void
+pop_up_pathbar_context_menu (NautilusPathBar *self,
+ NautilusFile *file)
+{
+ if (file != NULL)
+ {
+ schedule_pop_up_context_menu (self, file);
+ }
+}
+
+
+static void
+on_click_gesture_pressed (GtkGestureClick *gesture,
+ gint n_press,
+ gdouble x,
+ gdouble y,
+ gpointer user_data)
+{
+ ButtonData *button_data;
+ NautilusPathBar *self;
+ guint current_button;
+ GdkModifierType state;
+ double x_in_pathbar, y_in_pathbar;
+
+ if (n_press != 1)
+ {
+ return;
+ }
+
+ button_data = BUTTON_DATA (user_data);
+ self = button_data->path_bar;
+ current_button = gtk_gesture_single_get_current_button (GTK_GESTURE_SINGLE (gesture));
+ state = gtk_event_controller_get_current_event_state (GTK_EVENT_CONTROLLER (gesture));
+
+ gtk_widget_translate_coordinates (GTK_WIDGET (button_data->button),
+ GTK_WIDGET (self),
+ x, y,
+ &x_in_pathbar, &y_in_pathbar);
+
+ switch (current_button)
+ {
+ case GDK_BUTTON_MIDDLE:
+ {
+ if ((state & gtk_accelerator_get_default_mod_mask ()) == 0)
+ {
+ g_signal_emit (self, path_bar_signals[OPEN_LOCATION], 0,
+ button_data->path,
+ NAUTILUS_OPEN_FLAG_NEW_TAB | NAUTILUS_OPEN_FLAG_DONT_MAKE_ACTIVE);
+ }
+ }
+ break;
+
+ case GDK_BUTTON_SECONDARY:
+ {
+ if (g_file_equal (button_data->path, self->current_path))
+ {
+ break;
+ }
+ else
+ {
+ gtk_popover_set_pointing_to (GTK_POPOVER (self->button_menu_popover),
+ &(GdkRectangle){x_in_pathbar, y_in_pathbar, 0, 0});
+ pop_up_pathbar_context_menu (self, button_data->file);
+ }
+ }
+ break;
+
+ case GDK_BUTTON_PRIMARY:
+ {
+ if ((state & GDK_CONTROL_MASK) != 0)
+ {
+ g_signal_emit (button_data->path_bar, path_bar_signals[OPEN_LOCATION], 0,
+ button_data->path,
+ NAUTILUS_OPEN_FLAG_NEW_WINDOW);
+ }
+ else
+ {
+ /* GtkButton will claim the primary button presses and emit the
+ * "clicked" signal. Handle it in the singal callback, not here.
+ */
+ return;
+ }
+ }
+ break;
+
+ default:
+ {
+ /* Ignore other buttons in this gesture. */
+ return;
+ }
+ break;
+ }
+
+ /* Both middle- and secondary-clicking the title bar can have interesting
+ * effects (minimizing the window, popping up a window manager menu, etc.),
+ * and this avoids all that.
+ */
+ gtk_gesture_set_state (GTK_GESTURE (gesture), GTK_EVENT_SEQUENCE_CLAIMED);
+}
+
+static GIcon *
+get_gicon_for_mount (ButtonData *button_data)
+{
+ GIcon *icon;
+ GMount *mount;
+
+ icon = NULL;
+ mount = nautilus_get_mounted_mount_for_root (button_data->path);
+
+ if (mount != NULL)
+ {
+ icon = g_mount_get_symbolic_icon (mount);
+ g_object_unref (mount);
+ }
+
+ return icon;
+}
+
+static GIcon *
+get_gicon (ButtonData *button_data)
+{
+ switch (button_data->type)
+ {
+ case ROOT_BUTTON:
+ case ADMIN_ROOT_BUTTON:
+ {
+ return g_themed_icon_new (NAUTILUS_ICON_FILESYSTEM);
+ }
+
+ case HOME_BUTTON:
+ {
+ return g_themed_icon_new (NAUTILUS_ICON_HOME);
+ }
+
+ case MOUNT_BUTTON:
+ {
+ return get_gicon_for_mount (button_data);
+ }
+
+ case STARRED_BUTTON:
+ {
+ return g_themed_icon_new ("starred-symbolic");
+ }
+
+ case RECENT_BUTTON:
+ {
+ return g_themed_icon_new ("document-open-recent-symbolic");
+ }
+
+ case OTHER_LOCATIONS_BUTTON:
+ {
+ return g_themed_icon_new ("list-add-symbolic");
+ }
+
+ case TRASH_BUTTON:
+ {
+ return nautilus_trash_monitor_get_symbolic_icon ();
+ }
+
+ default:
+ {
+ return NULL;
+ }
+ }
+
+ return NULL;
+}
+
+static void
+nautilus_path_bar_update_button_appearance (ButtonData *button_data,
+ gboolean current_dir)
+{
+ const gchar *dir_name = get_dir_name (button_data);
+ gint min_chars = NAUTILUS_PATH_BAR_BUTTON_ELLISPIZE_MINIMUM_CHARS;
+ GIcon *icon;
+
+ if (button_data->label != NULL)
+ {
+ gtk_label_set_text (GTK_LABEL (button_data->label), dir_name);
+
+ gtk_widget_set_tooltip_text (button_data->button, dir_name);
+
+ if (current_dir)
+ {
+ /* We want to avoid ellipsizing the current directory name, but
+ * still need to set a limit. */
+ min_chars = 4 * min_chars;
+ }
+
+ /* Labels can ellipsize until they become a single ellipsis character.
+ * We don't want that, so we must set a minimum.
+ *
+ * However, for labels shorter than the minimum, setting this minimum
+ * width would make them unnecessarily wide. In that case, just make it
+ * not ellipsize instead.
+ *
+ * Due to variable width fonts, labels can be shorter than the space
+ * that would be reserved by setting a minimum amount of characters.
+ * Compensate for this with a tolerance of +50% characters.
+ */
+ if (g_utf8_strlen (dir_name, -1) > min_chars * 1.5)
+ {
+ gtk_label_set_width_chars (GTK_LABEL (button_data->label), min_chars);
+ gtk_label_set_ellipsize (GTK_LABEL (button_data->label), PANGO_ELLIPSIZE_MIDDLE);
+ }
+ else
+ {
+ gtk_label_set_width_chars (GTK_LABEL (button_data->label), -1);
+ gtk_label_set_ellipsize (GTK_LABEL (button_data->label), PANGO_ELLIPSIZE_NONE);
+ }
+ }
+
+ icon = get_gicon (button_data);
+ if (icon != NULL)
+ {
+ gtk_image_set_from_gicon (GTK_IMAGE (button_data->image), icon);
+ gtk_widget_show (GTK_WIDGET (button_data->image));
+ g_object_unref (icon);
+ }
+ else
+ {
+ gtk_widget_hide (GTK_WIDGET (button_data->image));
+ }
+}
+
+static void
+nautilus_path_bar_update_button_state (ButtonData *button_data,
+ gboolean current_dir)
+{
+ if (button_data->label != NULL)
+ {
+ gtk_label_set_label (GTK_LABEL (button_data->label), NULL);
+ }
+
+ nautilus_path_bar_update_button_appearance (button_data, current_dir);
+}
+
+static void
+setup_button_type (ButtonData *button_data,
+ NautilusPathBar *self,
+ GFile *location)
+{
+ g_autoptr (GMount) mount = NULL;
+ g_autofree gchar *uri = NULL;
+
+ if (nautilus_is_root_directory (location))
+ {
+ button_data->type = ROOT_BUTTON;
+ }
+ else if (nautilus_is_home_directory (location))
+ {
+ button_data->type = HOME_BUTTON;
+ button_data->is_root = TRUE;
+ }
+ else if (nautilus_is_recent_directory (location))
+ {
+ button_data->type = RECENT_BUTTON;
+ button_data->is_root = TRUE;
+ }
+ else if (nautilus_is_starred_directory (location))
+ {
+ button_data->type = STARRED_BUTTON;
+ button_data->is_root = TRUE;
+ }
+ else if ((mount = nautilus_get_mounted_mount_for_root (location)) != NULL)
+ {
+ button_data->dir_name = g_mount_get_name (mount);
+ button_data->type = MOUNT_BUTTON;
+ button_data->is_root = TRUE;
+ }
+ else if (nautilus_is_other_locations_directory (location))
+ {
+ button_data->type = OTHER_LOCATIONS_BUTTON;
+ button_data->is_root = TRUE;
+ }
+ else if (strcmp ((uri = g_file_get_uri (location)), "admin:///") == 0)
+ {
+ button_data->type = ADMIN_ROOT_BUTTON;
+ button_data->is_root = TRUE;
+ }
+ else if (strcmp (uri, "trash:///") == 0)
+ {
+ button_data->type = TRASH_BUTTON;
+ button_data->is_root = TRUE;
+ }
+ else
+ {
+ button_data->type = NORMAL_BUTTON;
+ }
+}
+
+static void
+button_data_file_changed (NautilusFile *file,
+ ButtonData *button_data)
+{
+ GtkWidget *ancestor;
+ GFile *location;
+ GFile *current_location;
+ GFile *parent;
+ GFile *button_parent;
+ ButtonData *current_button_data;
+ char *display_name;
+ NautilusPathBar *self;
+ gboolean renamed;
+ gboolean child;
+ gboolean current_dir;
+
+ ancestor = gtk_widget_get_ancestor (button_data->button, NAUTILUS_TYPE_PATH_BAR);
+ if (ancestor == NULL)
+ {
+ return;
+ }
+ self = NAUTILUS_PATH_BAR (ancestor);
+
+ g_return_if_fail (self->current_path != NULL);
+ g_return_if_fail (self->current_button_data != NULL);
+
+ current_button_data = self->current_button_data;
+
+ location = nautilus_file_get_location (file);
+ if (!g_file_equal (button_data->path, location))
+ {
+ parent = g_file_get_parent (location);
+ button_parent = g_file_get_parent (button_data->path);
+
+ renamed = (parent != NULL && button_parent != NULL) &&
+ g_file_equal (parent, button_parent);
+
+ if (parent != NULL)
+ {
+ g_object_unref (parent);
+ }
+ if (button_parent != NULL)
+ {
+ g_object_unref (button_parent);
+ }
+
+ if (renamed)
+ {
+ button_data->path = g_object_ref (location);
+ }
+ else
+ {
+ /* the file has been moved.
+ * If it was below the currently displayed location, remove it.
+ * If it was not below the currently displayed location, update the path bar
+ */
+ child = g_file_has_prefix (button_data->path,
+ self->current_path);
+
+ if (child)
+ {
+ /* moved file inside current path hierarchy */
+ g_object_unref (location);
+ location = g_file_get_parent (button_data->path);
+ current_location = g_object_ref (self->current_path);
+ }
+ else
+ {
+ /* moved current path, or file outside current path hierarchy.
+ * Update path bar to new locations.
+ */
+ current_location = nautilus_file_get_location (current_button_data->file);
+ }
+
+ nautilus_path_bar_update_path (self, location);
+ nautilus_path_bar_set_path (self, current_location);
+ g_object_unref (location);
+ g_object_unref (current_location);
+ return;
+ }
+ }
+ else if (nautilus_file_is_gone (file))
+ {
+ gint idx, position;
+
+ /* if the current or a parent location are gone, clear all the buttons,
+ * the view will set the new path.
+ */
+ current_location = nautilus_file_get_location (current_button_data->file);
+
+ if (g_file_has_prefix (current_location, location) ||
+ g_file_equal (current_location, location))
+ {
+ nautilus_path_bar_clear_buttons (self);
+ }
+ else if (g_file_has_prefix (location, current_location))
+ {
+ /* remove this and the following buttons */
+ position = g_list_position (self->button_list,
+ g_list_find (self->button_list, button_data));
+
+ if (position != -1)
+ {
+ for (idx = 0; idx <= position; idx++)
+ {
+ ButtonData *data;
+
+ data = BUTTON_DATA (self->button_list->data);
+
+ gtk_box_remove (GTK_BOX (self->buttons_box), data->container);
+ self->button_list = g_list_remove (self->button_list, data);
+ button_data_free (data);
+ }
+ }
+ }
+
+ g_object_unref (current_location);
+ g_object_unref (location);
+ return;
+ }
+ g_object_unref (location);
+
+ /* MOUNTs use the GMount as the name, so don't update for those */
+ if (button_data->type != MOUNT_BUTTON)
+ {
+ display_name = nautilus_file_get_display_name (file);
+ if (g_strcmp0 (display_name, button_data->dir_name) != 0)
+ {
+ g_free (button_data->dir_name);
+ button_data->dir_name = g_strdup (display_name);
+ }
+
+ g_free (display_name);
+ }
+ current_dir = g_file_equal (self->current_path, button_data->path);
+ nautilus_path_bar_update_button_appearance (button_data, current_dir);
+}
+
+static ButtonData *
+make_button_data (NautilusPathBar *self,
+ NautilusFile *file,
+ gboolean current_dir)
+{
+ GFile *path;
+ GtkWidget *child = NULL;
+ GtkEventController *controller;
+ ButtonData *button_data;
+
+ path = nautilus_file_get_location (file);
+
+ /* Is it a special button? */
+ button_data = g_new0 (ButtonData, 1);
+
+ setup_button_type (button_data, self, path);
+ button_data->button = gtk_button_new ();
+ gtk_widget_set_focus_on_click (button_data->button, FALSE);
+ gtk_widget_set_name (button_data->button, "NautilusPathButton");
+
+ /* TODO update button type when xdg directories change */
+
+ button_data->image = gtk_image_new ();
+
+ switch (button_data->type)
+ {
+ case ROOT_BUTTON:
+ case ADMIN_ROOT_BUTTON:
+ case HOME_BUTTON:
+ case MOUNT_BUTTON:
+ case TRASH_BUTTON:
+ case RECENT_BUTTON:
+ case STARRED_BUTTON:
+ case OTHER_LOCATIONS_BUTTON:
+ {
+ button_data->label = gtk_label_new (NULL);
+ child = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 6);
+ button_data->container = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 6);
+ gtk_box_append (GTK_BOX (button_data->container), button_data->button);
+
+ gtk_box_append (GTK_BOX (child), button_data->image);
+ gtk_box_append (GTK_BOX (child), button_data->label);
+ }
+ break;
+
+ case NORMAL_BUTTON:
+ /* Fall through */
+ default:
+ {
+ GtkWidget *separator_label;
+
+ separator_label = gtk_label_new (G_DIR_SEPARATOR_S);
+ gtk_style_context_add_class (gtk_widget_get_style_context (separator_label), "dim-label");
+ button_data->label = gtk_label_new (NULL);
+ child = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 2);
+ button_data->container = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
+ gtk_box_append (GTK_BOX (button_data->container), separator_label);
+ gtk_box_append (GTK_BOX (button_data->container), button_data->button);
+
+ gtk_box_append (GTK_BOX (child), button_data->label);
+ }
+ break;
+ }
+
+ if (current_dir)
+ {
+ gtk_style_context_add_class (gtk_widget_get_style_context (button_data->button),
+ "current-dir");
+ gtk_widget_set_hexpand (button_data->button, TRUE);
+ gtk_widget_set_halign (button_data->label, GTK_ALIGN_START);
+ }
+
+ if (button_data->label != NULL)
+ {
+ PangoAttrList *attrs;
+
+ gtk_label_set_single_line_mode (GTK_LABEL (button_data->label), TRUE);
+
+ attrs = pango_attr_list_new ();
+ pango_attr_list_insert (attrs, pango_attr_weight_new (PANGO_WEIGHT_BOLD));
+ gtk_label_set_attributes (GTK_LABEL (button_data->label), attrs);
+ pango_attr_list_unref (attrs);
+
+ if (!current_dir)
+ {
+ gtk_style_context_add_class (gtk_widget_get_style_context (button_data->label), "dim-label");
+ gtk_style_context_add_class (gtk_widget_get_style_context (button_data->image), "dim-label");
+ }
+ }
+
+ if (button_data->path == NULL)
+ {
+ button_data->path = g_object_ref (path);
+ }
+ if (button_data->dir_name == NULL)
+ {
+ button_data->dir_name = nautilus_file_get_display_name (file);
+ }
+ if (button_data->file == NULL)
+ {
+ button_data->file = nautilus_file_ref (file);
+ nautilus_file_monitor_add (button_data->file, button_data,
+ NAUTILUS_FILE_ATTRIBUTES_FOR_ICON);
+ button_data->file_changed_signal_id =
+ g_signal_connect (button_data->file, "changed",
+ G_CALLBACK (button_data_file_changed),
+ button_data);
+ }
+
+ gtk_button_set_child (GTK_BUTTON (button_data->button), child);
+ gtk_widget_show (button_data->container);
+
+ button_data->path_bar = self;
+
+ nautilus_path_bar_update_button_state (button_data, current_dir);
+
+ g_signal_connect (button_data->button, "clicked", G_CALLBACK (button_clicked_cb), button_data);
+
+ /* A gesture is needed here, because GtkButton doesn’t react to middle- or
+ * secondary-clicking.
+ */
+ controller = GTK_EVENT_CONTROLLER (gtk_gesture_click_new ());
+ gtk_widget_add_controller (button_data->button, controller);
+ gtk_gesture_single_set_button (GTK_GESTURE_SINGLE (controller), 0);
+ g_signal_connect (controller, "pressed",
+ G_CALLBACK (on_click_gesture_pressed), button_data);
+
+ nautilus_drag_slot_proxy_init (button_data->button, button_data->file, NULL);
+
+ g_object_unref (path);
+
+ return button_data;
+}
+
+static void
+nautilus_path_bar_update_path (NautilusPathBar *self,
+ GFile *file_path)
+{
+ NautilusFile *file;
+ gboolean first_directory;
+ GList *new_buttons, *l;
+ ButtonData *button_data;
+
+ g_return_if_fail (NAUTILUS_IS_PATH_BAR (self));
+ g_return_if_fail (file_path != NULL);
+
+ first_directory = TRUE;
+ new_buttons = NULL;
+
+ file = nautilus_file_get (file_path);
+
+ while (file != NULL)
+ {
+ NautilusFile *parent_file;
+
+ parent_file = nautilus_file_get_parent (file);
+ button_data = make_button_data (self, file, first_directory);
+ nautilus_file_unref (file);
+
+ if (first_directory)
+ {
+ first_directory = FALSE;
+ }
+
+ new_buttons = g_list_prepend (new_buttons, button_data);
+
+ if (parent_file != NULL &&
+ button_data->is_root)
+ {
+ nautilus_file_unref (parent_file);
+ break;
+ }
+
+ file = parent_file;
+ }
+
+ nautilus_path_bar_clear_buttons (self);
+
+ /* Buttons are listed in reverse order such that the current location is
+ * always the first link. */
+ self->button_list = g_list_reverse (new_buttons);
+
+ for (l = self->button_list; l; l = l->next)
+ {
+ GtkWidget *container;
+ container = BUTTON_DATA (l->data)->container;
+ gtk_box_prepend (GTK_BOX (self->buttons_box), container);
+ }
+}
+
+void
+nautilus_path_bar_set_path (NautilusPathBar *self,
+ GFile *file_path)
+{
+ ButtonData *button_data;
+
+ g_return_if_fail (NAUTILUS_IS_PATH_BAR (self));
+ g_return_if_fail (file_path != NULL);
+
+ nautilus_path_bar_update_path (self, file_path);
+ button_data = g_list_nth_data (self->button_list, 0);
+
+ if (self->current_path != NULL)
+ {
+ g_object_unref (self->current_path);
+ }
+
+ self->current_path = g_object_ref (file_path);
+ self->current_button_data = button_data;
+}
diff --git a/src/nautilus-pathbar.h b/src/nautilus-pathbar.h
new file mode 100644
index 0000000..1052e4d
--- /dev/null
+++ b/src/nautilus-pathbar.h
@@ -0,0 +1,34 @@
+/* nautilus-pathbar.h
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ *
+ *
+ */
+
+#pragma once
+
+#include <gtk/gtk.h>
+#include <gio/gio.h>
+
+#define NAUTILUS_TYPE_PATH_BAR (nautilus_path_bar_get_type ())
+G_DECLARE_FINAL_TYPE (NautilusPathBar, nautilus_path_bar, NAUTILUS, PATH_BAR, GtkBox)
+
+void nautilus_path_bar_set_path (NautilusPathBar *path_bar,
+ GFile *file);
+
+void nautilus_path_bar_set_extensions_background_menu (NautilusPathBar *path_bar,
+ GMenuModel *menu);
+void nautilus_path_bar_set_templates_menu (NautilusPathBar *path_bar,
+ GMenuModel *menu);
+void nautilus_path_bar_show_current_location_menu (NautilusPathBar *path_bar);
diff --git a/src/nautilus-places-view.c b/src/nautilus-places-view.c
new file mode 100644
index 0000000..e9e7785
--- /dev/null
+++ b/src/nautilus-places-view.c
@@ -0,0 +1,413 @@
+/* nautilus-places-view.c
+ *
+ * Copyright (C) 2015 Georges Basile Stavracas Neto <georges.stavracas@gmail.com>
+ *
+ * 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 3 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/>.
+ */
+
+#include <libadwaita-1/adwaita.h>
+
+#include "nautilus-mime-actions.h"
+#include "nautilus-places-view.h"
+
+#include "gtk/nautilusgtkplacesviewprivate.h"
+
+#include "nautilus-application.h"
+#include "nautilus-file.h"
+#include "nautilus-toolbar-menu-sections.h"
+#include "nautilus-view.h"
+#include "nautilus-window-slot.h"
+
+typedef struct
+{
+ GFile *location;
+ NautilusQuery *search_query;
+
+ GtkWidget *places_view;
+} NautilusPlacesViewPrivate;
+
+struct _NautilusPlacesView
+{
+ GtkFrameClass parent;
+};
+
+static void nautilus_places_view_iface_init (NautilusViewInterface *iface);
+
+G_DEFINE_TYPE_WITH_CODE (NautilusPlacesView, nautilus_places_view, GTK_TYPE_BOX,
+ G_ADD_PRIVATE (NautilusPlacesView)
+ G_IMPLEMENT_INTERFACE (NAUTILUS_TYPE_VIEW, nautilus_places_view_iface_init));
+
+enum
+{
+ PROP_0,
+ PROP_LOCATION,
+ PROP_SEARCH_QUERY,
+ PROP_LOADING,
+ PROP_SEARCHING,
+ PROP_SELECTION,
+ PROP_EXTENSIONS_BACKGROUND_MENU,
+ PROP_TEMPLATES_MENU,
+ LAST_PROP
+};
+
+static void
+open_location_cb (NautilusPlacesView *view,
+ GFile *location,
+ NautilusGtkPlacesOpenFlags open_flags)
+{
+ NautilusOpenFlags flags;
+ GtkWidget *slot;
+
+ slot = gtk_widget_get_ancestor (GTK_WIDGET (view), NAUTILUS_TYPE_WINDOW_SLOT);
+
+ switch (open_flags)
+ {
+ case NAUTILUS_GTK_PLACES_OPEN_NEW_TAB:
+ {
+ flags = NAUTILUS_OPEN_FLAG_NEW_TAB |
+ NAUTILUS_OPEN_FLAG_DONT_MAKE_ACTIVE;
+ }
+ break;
+
+ case NAUTILUS_GTK_PLACES_OPEN_NEW_WINDOW:
+ {
+ flags = NAUTILUS_OPEN_FLAG_NEW_WINDOW;
+ }
+ break;
+
+ case NAUTILUS_GTK_PLACES_OPEN_NORMAL: /* fall-through */
+ default:
+ {
+ flags = 0;
+ }
+ break;
+ }
+
+ if (slot)
+ {
+ NautilusFile *file;
+ GtkRoot *window;
+ char *path;
+
+ path = "other-locations:///";
+ file = nautilus_file_get (location);
+ window = gtk_widget_get_root (GTK_WIDGET (view));
+
+ nautilus_mime_activate_file (GTK_WINDOW (window),
+ NAUTILUS_WINDOW_SLOT (slot),
+ file,
+ path,
+ flags);
+ nautilus_file_unref (file);
+ }
+}
+
+static void
+loading_cb (NautilusView *view)
+{
+ g_object_notify (G_OBJECT (view), "loading");
+}
+
+static void
+show_error_message_cb (NautilusGtkPlacesView *view,
+ const gchar *primary,
+ const gchar *secondary)
+{
+ GtkWidget *dialog;
+ GtkRoot *window;
+
+ window = gtk_widget_get_root (GTK_WIDGET (view));
+
+ dialog = adw_message_dialog_new (GTK_WINDOW (window), primary, secondary);
+ adw_message_dialog_add_response (ADW_MESSAGE_DIALOG (dialog),
+ "close", _("_Close"));
+
+ gtk_window_present (GTK_WINDOW (dialog));
+}
+
+static void
+nautilus_places_view_finalize (GObject *object)
+{
+ NautilusPlacesView *self = (NautilusPlacesView *) object;
+ NautilusPlacesViewPrivate *priv = nautilus_places_view_get_instance_private (self);
+
+ g_clear_object (&priv->location);
+ g_clear_object (&priv->search_query);
+
+ G_OBJECT_CLASS (nautilus_places_view_parent_class)->finalize (object);
+}
+
+static void
+nautilus_places_view_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ NautilusView *view = NAUTILUS_VIEW (object);
+
+ switch (prop_id)
+ {
+ case PROP_LOCATION:
+ {
+ g_value_set_object (value, nautilus_view_get_location (view));
+ }
+ break;
+
+ case PROP_SEARCH_QUERY:
+ {
+ g_value_set_object (value, nautilus_view_get_search_query (view));
+ }
+ break;
+
+ /* Collect all unused properties and do nothing. Ideally, this wouldn’t
+ * have to be done in the first place.
+ */
+ case PROP_SEARCHING:
+ case PROP_SELECTION:
+ case PROP_EXTENSIONS_BACKGROUND_MENU:
+ case PROP_TEMPLATES_MENU:
+ {
+ }
+ break;
+
+ default:
+ {
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+ }
+}
+
+static void
+nautilus_places_view_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ NautilusView *view = NAUTILUS_VIEW (object);
+
+ switch (prop_id)
+ {
+ case PROP_LOCATION:
+ {
+ nautilus_view_set_location (view, g_value_get_object (value));
+ }
+ break;
+
+ case PROP_SEARCH_QUERY:
+ {
+ nautilus_view_set_search_query (view, g_value_get_object (value));
+ }
+ break;
+
+ default:
+ {
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+ }
+}
+
+static GFile *
+nautilus_places_view_get_location (NautilusView *view)
+{
+ NautilusPlacesViewPrivate *priv;
+
+ priv = nautilus_places_view_get_instance_private (NAUTILUS_PLACES_VIEW (view));
+
+ return priv->location;
+}
+
+static void
+nautilus_places_view_set_location (NautilusView *view,
+ GFile *location)
+{
+ if (location)
+ {
+ NautilusPlacesViewPrivate *priv;
+ gchar *uri;
+
+ priv = nautilus_places_view_get_instance_private (NAUTILUS_PLACES_VIEW (view));
+ uri = g_file_get_uri (location);
+
+ /*
+ * If it's not trying to open the places view itself, simply
+ * delegates the location to application, which takes care of
+ * selecting the appropriate view.
+ */
+ if (g_strcmp0 (uri, "other-locations:///") != 0)
+ {
+ nautilus_application_open_location_full (NAUTILUS_APPLICATION (g_application_get_default ()),
+ location, 0, NULL, NULL, NULL);
+ }
+ else
+ {
+ g_set_object (&priv->location, location);
+ }
+
+ g_free (uri);
+ }
+}
+
+static GList *
+nautilus_places_view_get_selection (NautilusView *view)
+{
+ /* STUB */
+ return NULL;
+}
+
+static void
+nautilus_places_view_set_selection (NautilusView *view,
+ GList *selection)
+{
+ /* STUB */
+}
+
+static NautilusQuery *
+nautilus_places_view_get_search_query (NautilusView *view)
+{
+ NautilusPlacesViewPrivate *priv;
+
+ priv = nautilus_places_view_get_instance_private (NAUTILUS_PLACES_VIEW (view));
+
+ return priv->search_query;
+}
+
+static void
+nautilus_places_view_set_search_query (NautilusView *view,
+ NautilusQuery *query)
+{
+ NautilusPlacesViewPrivate *priv;
+ gchar *text;
+
+ priv = nautilus_places_view_get_instance_private (NAUTILUS_PLACES_VIEW (view));
+
+ g_set_object (&priv->search_query, query);
+
+ text = query ? nautilus_query_get_text (query) : NULL;
+
+ nautilus_gtk_places_view_set_search_query (NAUTILUS_GTK_PLACES_VIEW (priv->places_view), text);
+
+ g_free (text);
+}
+
+static NautilusToolbarMenuSections *
+nautilus_places_view_get_toolbar_menu_sections (NautilusView *view)
+{
+ return NULL;
+}
+
+static gboolean
+nautilus_places_view_is_loading (NautilusView *view)
+{
+ NautilusPlacesViewPrivate *priv;
+
+ priv = nautilus_places_view_get_instance_private (NAUTILUS_PLACES_VIEW (view));
+
+ return nautilus_gtk_places_view_get_loading (NAUTILUS_GTK_PLACES_VIEW (priv->places_view));
+}
+
+static gboolean
+nautilus_places_view_is_searching (NautilusView *view)
+{
+ NautilusPlacesViewPrivate *priv;
+
+ priv = nautilus_places_view_get_instance_private (NAUTILUS_PLACES_VIEW (view));
+
+ return priv->search_query != NULL;
+}
+
+static guint
+nautilus_places_view_get_view_id (NautilusView *view)
+{
+ return NAUTILUS_VIEW_OTHER_LOCATIONS_ID;
+}
+
+static void
+nautilus_places_view_iface_init (NautilusViewInterface *iface)
+{
+ iface->get_location = nautilus_places_view_get_location;
+ iface->set_location = nautilus_places_view_set_location;
+ iface->get_selection = nautilus_places_view_get_selection;
+ iface->set_selection = nautilus_places_view_set_selection;
+ iface->get_search_query = nautilus_places_view_get_search_query;
+ iface->set_search_query = nautilus_places_view_set_search_query;
+ iface->get_toolbar_menu_sections = nautilus_places_view_get_toolbar_menu_sections;
+ iface->is_loading = nautilus_places_view_is_loading;
+ iface->is_searching = nautilus_places_view_is_searching;
+ iface->get_view_id = nautilus_places_view_get_view_id;
+}
+
+static void
+nautilus_places_view_class_init (NautilusPlacesViewClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->finalize = nautilus_places_view_finalize;
+ object_class->get_property = nautilus_places_view_get_property;
+ object_class->set_property = nautilus_places_view_set_property;
+
+ g_object_class_override_property (object_class, PROP_LOADING, "loading");
+ g_object_class_override_property (object_class, PROP_SEARCHING, "searching");
+ g_object_class_override_property (object_class, PROP_LOCATION, "location");
+ g_object_class_override_property (object_class, PROP_SELECTION, "selection");
+ g_object_class_override_property (object_class, PROP_SEARCH_QUERY, "search-query");
+ g_object_class_override_property (object_class,
+ PROP_EXTENSIONS_BACKGROUND_MENU,
+ "extensions-background-menu");
+ g_object_class_override_property (object_class,
+ PROP_TEMPLATES_MENU,
+ "templates-menu");
+}
+
+static void
+nautilus_places_view_init (NautilusPlacesView *self)
+{
+ NautilusPlacesViewPrivate *priv;
+
+ priv = nautilus_places_view_get_instance_private (self);
+
+ /* Location */
+ priv->location = g_file_new_for_uri ("other-locations:///");
+
+ /* Places view */
+ priv->places_view = nautilus_gtk_places_view_new ();
+ nautilus_gtk_places_view_set_open_flags (NAUTILUS_GTK_PLACES_VIEW (priv->places_view),
+ NAUTILUS_OPEN_FLAG_NEW_TAB | NAUTILUS_OPEN_FLAG_NEW_WINDOW | NAUTILUS_OPEN_FLAG_NORMAL);
+ gtk_widget_set_hexpand (priv->places_view, TRUE);
+ gtk_widget_set_vexpand (priv->places_view, TRUE);
+ gtk_widget_show (priv->places_view);
+ gtk_box_append (GTK_BOX (self), priv->places_view);
+
+ g_signal_connect_object (priv->places_view, "notify::loading",
+ G_CALLBACK (loading_cb), self, G_CONNECT_SWAPPED);
+
+ g_signal_connect_object (priv->places_view, "open-location",
+ G_CALLBACK (open_location_cb), self, G_CONNECT_SWAPPED);
+
+ g_signal_connect_object (priv->places_view, "show-error-message",
+ G_CALLBACK (show_error_message_cb), self, G_CONNECT_SWAPPED);
+}
+
+NautilusPlacesView *
+nautilus_places_view_new (void)
+{
+ NautilusPlacesView *view;
+
+ view = g_object_new (NAUTILUS_TYPE_PLACES_VIEW, NULL);
+ if (g_object_is_floating (view))
+ {
+ g_object_ref_sink (view);
+ }
+
+ return view;
+}
diff --git a/src/nautilus-places-view.h b/src/nautilus-places-view.h
new file mode 100644
index 0000000..e961d39
--- /dev/null
+++ b/src/nautilus-places-view.h
@@ -0,0 +1,32 @@
+/* nautilus-places-view.h
+ *
+ * Copyright (C) 2015 Georges Basile Stavracas Neto <georges.stavracas@gmail.com>
+ *
+ * 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 3 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/>.
+ */
+
+#pragma once
+
+#include <glib-object.h>
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+#define NAUTILUS_TYPE_PLACES_VIEW (nautilus_places_view_get_type())
+
+G_DECLARE_FINAL_TYPE (NautilusPlacesView, nautilus_places_view, NAUTILUS, PLACES_VIEW, GtkBox)
+
+NautilusPlacesView* nautilus_places_view_new (void);
+
+G_END_DECLS
diff --git a/src/nautilus-preferences-window.c b/src/nautilus-preferences-window.c
new file mode 100644
index 0000000..e8d6663
--- /dev/null
+++ b/src/nautilus-preferences-window.c
@@ -0,0 +1,411 @@
+/* nautilus-preferences-window.c - Functions to create and show the nautilus
+ * preference window.
+ *
+ * Copyright (C) 2002 Jan Arne Petersen
+ * Copyright (C) 2016 Carlos Soriano <csoriano@gnome.com>
+ *
+ * The Gnome Library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * The Gnome Library 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
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with the Gnome Library; see the file COPYING.LIB. If not,
+ * see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Jan Arne Petersen <jpetersen@uni-bonn.de>
+ */
+
+#include <config.h>
+
+#include "nautilus-preferences-window.h"
+
+#include <gtk/gtk.h>
+#include <gio/gio.h>
+#include <libadwaita-1/adwaita.h>
+
+#include <glib/gi18n.h>
+
+#include <nautilus-extension.h>
+
+#include "nautilus-column-utilities.h"
+#include "nautilus-global-preferences.h"
+
+/* bool preferences */
+#define NAUTILUS_PREFERENCES_DIALOG_FOLDERS_FIRST_WIDGET \
+ "sort_folders_first_switch"
+#define NAUTILUS_PREFERENCES_DIALOG_DELETE_PERMANENTLY_WIDGET \
+ "show_delete_permanently_switch"
+#define NAUTILUS_PREFERENCES_DIALOG_CREATE_LINK_WIDGET \
+ "show_create_link_switch"
+#define NAUTILUS_PREFERENCES_DIALOG_LIST_VIEW_USE_TREE_WIDGET \
+ "use_tree_view_switch"
+
+/* combo preferences */
+#define NAUTILUS_PREFERENCES_DIALOG_OPEN_ACTION_COMBO \
+ "open_action_row"
+#define NAUTILUS_PREFERENCES_DIALOG_SEARCH_RECURSIVE_ROW \
+ "search_recursive_row"
+#define NAUTILUS_PREFERENCES_DIALOG_THUMBNAILS_ROW \
+ "thumbnails_row"
+#define NAUTILUS_PREFERENCES_DIALOG_COUNT_ROW \
+ "count_row"
+
+static const char * const speed_tradeoff_values[] =
+{
+ "local-only", "always", "never",
+ NULL
+};
+
+static const char * const click_behavior_values[] = {"single", "double", NULL};
+
+static const char * const icon_captions_components[] =
+{
+ "captions_0_comborow", "captions_1_comborow", "captions_2_comborow", NULL
+};
+
+static GtkWidget *preferences_window = NULL;
+
+static void list_store_append_string (GListStore *list_store,
+ const gchar *string)
+{
+ g_autoptr (GtkStringObject) obj = gtk_string_object_new (string);
+ g_list_store_append (list_store, obj);
+}
+
+static void free_column_names_array(GPtrArray *column_names)
+{
+ g_ptr_array_foreach (column_names, (GFunc) g_free, NULL);
+ g_ptr_array_free (column_names, TRUE);
+}
+
+static void create_icon_caption_combo_row_items(AdwComboRow *combo_row,
+ GList *columns)
+{
+ GListStore *list_store = g_list_store_new (GTK_TYPE_STRING_OBJECT);
+ GList *l;
+ GPtrArray *column_names;
+
+ column_names = g_ptr_array_new ();
+
+ /* Translators: this is referred to captions under icons. */
+ list_store_append_string (list_store, _("None"));
+ g_ptr_array_add (column_names, g_strdup ("none"));
+
+ for (l = columns; l != NULL; l = l->next)
+ {
+ NautilusColumn *column;
+ char *name;
+ char *label;
+
+ column = NAUTILUS_COLUMN (l->data);
+
+ g_object_get (G_OBJECT (column), "name", &name, "label", &label, NULL);
+
+ /* Don't show name here, it doesn't make sense */
+ if (!strcmp (name, "name"))
+ {
+ g_free (name);
+ g_free (label);
+ continue;
+ }
+
+ list_store_append_string (list_store, label);
+ g_ptr_array_add (column_names, name);
+
+ g_free (label);
+ }
+ adw_combo_row_set_model (combo_row, G_LIST_MODEL (list_store));
+ g_object_set_data_full (G_OBJECT (combo_row), "column_names", column_names,
+ (GDestroyNotify) free_column_names_array);
+}
+
+static void icon_captions_changed_callback(AdwComboRow *widget,
+ GParamSpec *pspec,
+ gpointer user_data)
+{
+ GPtrArray *captions;
+ GtkBuilder *builder;
+ int i;
+
+ builder = GTK_BUILDER (user_data);
+
+ captions = g_ptr_array_new ();
+
+ for (i = 0; icon_captions_components[i] != NULL; i++)
+ {
+ GtkWidget *combo_row;
+ int selected_index;
+ GPtrArray *column_names;
+ char *name;
+
+ combo_row = GTK_WIDGET (
+ gtk_builder_get_object (builder, icon_captions_components[i]));
+ selected_index = adw_combo_row_get_selected (ADW_COMBO_ROW (combo_row));
+
+ column_names = g_object_get_data (G_OBJECT (combo_row), "column_names");
+
+ name = g_ptr_array_index (column_names, selected_index);
+ g_ptr_array_add (captions, name);
+ }
+ g_ptr_array_add (captions, NULL);
+
+ g_settings_set_strv (nautilus_icon_view_preferences,
+ NAUTILUS_PREFERENCES_ICON_VIEW_CAPTIONS,
+ (const char **) captions->pdata);
+ g_ptr_array_free (captions, TRUE);
+}
+
+static void update_caption_combo_row(GtkBuilder *builder,
+ const char *combo_row_name,
+ const char *name)
+{
+ GtkWidget *combo_row;
+ int i;
+ GPtrArray *column_names;
+
+ combo_row = GTK_WIDGET (gtk_builder_get_object (builder, combo_row_name));
+
+ g_signal_handlers_block_by_func (
+ combo_row, G_CALLBACK (icon_captions_changed_callback), builder);
+
+ column_names = g_object_get_data (G_OBJECT (combo_row), "column_names");
+
+ for (i = 0; i < column_names->len; ++i)
+ {
+ if (!strcmp (name, g_ptr_array_index (column_names, i)))
+ {
+ adw_combo_row_set_selected (ADW_COMBO_ROW (combo_row), i);
+ break;
+ }
+ }
+
+ g_signal_handlers_unblock_by_func (
+ combo_row, G_CALLBACK (icon_captions_changed_callback), builder);
+}
+
+static void update_icon_captions_from_settings(GtkBuilder *builder)
+{
+ char **captions;
+ int i, j;
+
+ captions = g_settings_get_strv (nautilus_icon_view_preferences,
+ NAUTILUS_PREFERENCES_ICON_VIEW_CAPTIONS);
+ if (captions == NULL)
+ {
+ return;
+ }
+
+ for (i = 0, j = 0; icon_captions_components[i] != NULL; i++)
+ {
+ char *data;
+
+ if (captions[j])
+ {
+ data = captions[j];
+ ++j;
+ }
+ else
+ {
+ data = "none";
+ }
+
+ update_caption_combo_row (builder, icon_captions_components[i], data);
+ }
+
+ g_strfreev (captions);
+}
+
+static void
+nautilus_preferences_window_setup_icon_caption_page (GtkBuilder *builder)
+{
+ GList *columns;
+ int i;
+ gboolean writable;
+
+ writable = g_settings_is_writable (nautilus_icon_view_preferences,
+ NAUTILUS_PREFERENCES_ICON_VIEW_CAPTIONS);
+
+ columns = nautilus_get_common_columns ();
+
+ for (i = 0; icon_captions_components[i] != NULL; i++)
+ {
+ GtkWidget *combo_row;
+
+ combo_row = GTK_WIDGET (
+ gtk_builder_get_object (builder, icon_captions_components[i]));
+
+ create_icon_caption_combo_row_items (ADW_COMBO_ROW (combo_row), columns);
+ gtk_widget_set_sensitive (combo_row, writable);
+
+ g_signal_connect_data (
+ combo_row, "notify::selected", G_CALLBACK (icon_captions_changed_callback),
+ g_object_ref (builder), (GClosureNotify) g_object_unref, 0);
+ }
+
+ nautilus_column_list_free (columns);
+
+ update_icon_captions_from_settings (builder);
+}
+
+static void bind_builder_bool(GtkBuilder *builder,
+ GSettings *settings,
+ const char *widget_name,
+ const char *prefs)
+{
+ g_settings_bind (settings, prefs, gtk_builder_get_object (builder, widget_name),
+ "active", G_SETTINGS_BIND_DEFAULT);
+}
+
+static GVariant *combo_row_mapping_set(const GValue *gvalue,
+ const GVariantType *expected_type,
+ gpointer user_data)
+{
+ const gchar **values = user_data;
+
+ return g_variant_new_string (values[g_value_get_uint (gvalue)]);
+}
+
+static gboolean combo_row_mapping_get(GValue *gvalue,
+ GVariant *variant,
+ gpointer user_data)
+{
+ const gchar **values = user_data;
+ const gchar *value;
+
+ value = g_variant_get_string (variant, NULL);
+
+ for (int i = 0; values[i]; i++)
+ {
+ if (g_strcmp0 (value, values[i]) == 0)
+ {
+ g_value_set_uint (gvalue, i);
+
+ return TRUE;
+ }
+ }
+
+ return FALSE;
+}
+
+static void bind_builder_combo_row(GtkBuilder *builder,
+ GSettings *settings,
+ const char *widget_name,
+ const char *prefs,
+ const char **values)
+{
+ g_settings_bind_with_mapping (settings, prefs, gtk_builder_get_object (builder, widget_name),
+ "selected", G_SETTINGS_BIND_DEFAULT,
+ combo_row_mapping_get, combo_row_mapping_set,
+ (gpointer) values, NULL);
+}
+
+static void setup_combo (GtkBuilder *builder,
+ const char *widget_name,
+ const char **strings)
+{
+ AdwComboRow *combo_row;
+ GListStore *list_store;
+
+ combo_row = (AdwComboRow *) gtk_builder_get_object (builder, widget_name);
+ g_assert (ADW_IS_COMBO_ROW (combo_row));
+
+ list_store = g_list_store_new (GTK_TYPE_STRING_OBJECT);
+
+ for (gsize i = 0; strings[i]; i++)
+ {
+ list_store_append_string (list_store, strings[i]);
+ }
+
+ adw_combo_row_set_model (combo_row, G_LIST_MODEL (list_store));
+}
+
+static void nautilus_preferences_window_setup(GtkBuilder *builder,
+ GtkWindow *parent_window)
+{
+ GtkWidget *window;
+
+ setup_combo (builder, NAUTILUS_PREFERENCES_DIALOG_OPEN_ACTION_COMBO,
+ (const char *[]) { _("Single click"), _("Double click"), NULL });
+ setup_combo (builder, NAUTILUS_PREFERENCES_DIALOG_SEARCH_RECURSIVE_ROW,
+ (const char *[]) { _("On this computer only"), _("All locations"), _("Never"), NULL });
+ setup_combo (builder, NAUTILUS_PREFERENCES_DIALOG_THUMBNAILS_ROW,
+ (const char *[]) { _("On this computer only"), _("All files"), _("Never"), NULL });
+ setup_combo (builder, NAUTILUS_PREFERENCES_DIALOG_COUNT_ROW,
+ (const char *[]) { _("On this computer only"), _("All folders"), _("Never"), NULL });
+
+ /* setup preferences */
+ bind_builder_bool (builder, gtk_filechooser_preferences,
+ NAUTILUS_PREFERENCES_DIALOG_FOLDERS_FIRST_WIDGET,
+ NAUTILUS_PREFERENCES_SORT_DIRECTORIES_FIRST);
+ bind_builder_bool (builder, nautilus_list_view_preferences,
+ NAUTILUS_PREFERENCES_DIALOG_LIST_VIEW_USE_TREE_WIDGET,
+ NAUTILUS_PREFERENCES_LIST_VIEW_USE_TREE);
+ bind_builder_bool (builder, nautilus_preferences,
+ NAUTILUS_PREFERENCES_DIALOG_CREATE_LINK_WIDGET,
+ NAUTILUS_PREFERENCES_SHOW_CREATE_LINK);
+ bind_builder_bool (builder, nautilus_preferences,
+ NAUTILUS_PREFERENCES_DIALOG_DELETE_PERMANENTLY_WIDGET,
+ NAUTILUS_PREFERENCES_SHOW_DELETE_PERMANENTLY);
+
+ bind_builder_combo_row (builder, nautilus_preferences,
+ NAUTILUS_PREFERENCES_DIALOG_OPEN_ACTION_COMBO,
+ NAUTILUS_PREFERENCES_CLICK_POLICY,
+ (const char **) click_behavior_values);
+ bind_builder_combo_row (builder, nautilus_preferences,
+ NAUTILUS_PREFERENCES_DIALOG_SEARCH_RECURSIVE_ROW,
+ NAUTILUS_PREFERENCES_RECURSIVE_SEARCH,
+ (const char **) speed_tradeoff_values);
+ bind_builder_combo_row (builder, nautilus_preferences,
+ NAUTILUS_PREFERENCES_DIALOG_THUMBNAILS_ROW,
+ NAUTILUS_PREFERENCES_SHOW_FILE_THUMBNAILS,
+ (const char **) speed_tradeoff_values);
+ bind_builder_combo_row (builder, nautilus_preferences,
+ NAUTILUS_PREFERENCES_DIALOG_COUNT_ROW,
+ NAUTILUS_PREFERENCES_SHOW_DIRECTORY_ITEM_COUNTS,
+ (const char **) speed_tradeoff_values);
+
+ nautilus_preferences_window_setup_icon_caption_page (builder);
+
+ /* UI callbacks */
+ window = GTK_WIDGET (gtk_builder_get_object (builder, "preferences_window"));
+ preferences_window = window;
+
+ gtk_window_set_icon_name (GTK_WINDOW (preferences_window), APPLICATION_ID);
+
+ g_object_add_weak_pointer (G_OBJECT (window), (gpointer *) &preferences_window);
+
+ gtk_window_set_transient_for (GTK_WINDOW (preferences_window), parent_window);
+
+ gtk_widget_show (preferences_window);
+}
+
+void nautilus_preferences_window_show(GtkWindow *window)
+{
+ GtkBuilder *builder;
+ g_autoptr (GError) error = NULL;
+
+ if (preferences_window != NULL)
+ {
+ gtk_window_present (GTK_WINDOW (preferences_window));
+ return;
+ }
+
+ builder = gtk_builder_new ();
+
+ gtk_builder_add_from_resource (
+ builder, "/org/gnome/nautilus/ui/nautilus-preferences-window.ui", &error);
+ if (error != NULL)
+ {
+ g_error ("Failed to add nautilus-preferences-window.ui: %s", error->message);
+ }
+
+ nautilus_preferences_window_setup (builder, window);
+
+ g_object_unref (builder);
+}
diff --git a/src/nautilus-preferences-window.h b/src/nautilus-preferences-window.h
new file mode 100644
index 0000000..dfd64d6
--- /dev/null
+++ b/src/nautilus-preferences-window.h
@@ -0,0 +1,34 @@
+
+/* nautilus-preferences-window.h - Function to show the nautilus preference
+ window.
+
+ Copyright (C) 2002 Jan Arne Petersen
+ Copyright (C) 2016 Carlos Soriano <csoriano@gnome.org>
+
+ The Gnome Library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Library General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version.
+
+ The Gnome Library 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
+ Library General Public License for more details.
+
+ You should have received a copy of the GNU Library General Public
+ License along with the Gnome Library; see the file COPYING.LIB. If not,
+ see <http://www.gnu.org/licenses/>.
+
+ Authors: Jan Arne Petersen <jpetersen@uni-bonn.de>
+*/
+
+#pragma once
+
+#include <glib-object.h>
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+void nautilus_preferences_window_show(GtkWindow *window);
+
+G_END_DECLS \ No newline at end of file
diff --git a/src/nautilus-previewer.c b/src/nautilus-previewer.c
new file mode 100644
index 0000000..66074ab
--- /dev/null
+++ b/src/nautilus-previewer.c
@@ -0,0 +1,207 @@
+/*
+ * nautilus-previewer: nautilus previewer DBus wrapper
+ *
+ * Copyright (C) 2011, Red Hat, Inc.
+ *
+ * Nautilus is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * Nautilus is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ *
+ * Author: Cosimo Cecchi <cosimoc@redhat.com>
+ *
+ */
+
+#include "config.h"
+
+#include "nautilus-previewer.h"
+
+#include "nautilus-files-view.h"
+#include "nautilus-window.h"
+#include "nautilus-window-slot.h"
+
+#define DEBUG_FLAG NAUTILUS_DEBUG_PREVIEWER
+#include "nautilus-debug.h"
+
+#include <gio/gio.h>
+
+#define PREVIEWER_DBUS_NAME "org.gnome.NautilusPreviewer"
+#define PREVIEWER2_DBUS_IFACE "org.gnome.NautilusPreviewer2"
+#define PREVIEWER_DBUS_PATH "/org/gnome/NautilusPreviewer"
+
+static GDBusProxy *previewer_v2_proxy = NULL;
+
+static gboolean
+ensure_previewer_v2_proxy (void)
+{
+ if (previewer_v2_proxy == NULL)
+ {
+ g_autoptr (GError) error = NULL;
+ GDBusConnection *connection = g_application_get_dbus_connection (g_application_get_default ());
+
+ previewer_v2_proxy = g_dbus_proxy_new_sync (connection,
+ G_DBUS_PROXY_FLAGS_DO_NOT_AUTO_START_AT_CONSTRUCTION,
+ NULL,
+ PREVIEWER_DBUS_NAME,
+ PREVIEWER_DBUS_PATH,
+ PREVIEWER2_DBUS_IFACE,
+ NULL,
+ &error);
+
+ if (error != NULL)
+ {
+ DEBUG ("Unable to create NautilusPreviewer2 proxy: %s", error->message);
+ return FALSE;
+ }
+ }
+
+ return TRUE;
+}
+
+static void
+previewer2_method_ready_cb (GObject *source,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ GDBusProxy *proxy = G_DBUS_PROXY (source);
+ g_autoptr (GError) error = NULL;
+
+ g_dbus_proxy_call_finish (proxy, res, &error);
+
+ if (error != NULL)
+ {
+ DEBUG ("Unable to call method on NautilusPreviewer: %s", error->message);
+ }
+}
+
+void
+nautilus_previewer_call_show_file (const gchar *uri,
+ const gchar *window_handle,
+ guint xid,
+ gboolean close_if_already_visible)
+{
+ if (!ensure_previewer_v2_proxy ())
+ {
+ return;
+ }
+
+ g_dbus_proxy_call (previewer_v2_proxy,
+ "ShowFile",
+ g_variant_new ("(ssb)",
+ uri, window_handle, close_if_already_visible),
+ G_DBUS_CALL_FLAGS_NONE,
+ -1,
+ NULL,
+ previewer2_method_ready_cb,
+ NULL);
+}
+
+void
+nautilus_previewer_call_close (void)
+{
+ if (!ensure_previewer_v2_proxy ())
+ {
+ return;
+ }
+
+ /* don't autostart the previewer if it's not running */
+ g_dbus_proxy_call (previewer_v2_proxy,
+ "Close",
+ NULL,
+ G_DBUS_CALL_FLAGS_NO_AUTO_START,
+ -1,
+ NULL,
+ previewer2_method_ready_cb,
+ NULL);
+}
+
+static void
+previewer_selection_event (GDBusConnection *connection,
+ const gchar *sender_name,
+ const gchar *object_path,
+ const gchar *interface_name,
+ const gchar *signal_name,
+ GVariant *parameters,
+ gpointer user_data)
+{
+ GApplication *application = g_application_get_default ();
+ GList *l, *windows = gtk_application_get_windows (GTK_APPLICATION (application));
+ NautilusWindow *window = NULL;
+ NautilusWindowSlot *slot;
+ NautilusView *view;
+ GtkDirectionType direction;
+
+ for (l = windows; l != NULL; l = l->next)
+ {
+ if (NAUTILUS_IS_WINDOW (l->data))
+ {
+ window = l->data;
+ break;
+ }
+ }
+
+ if (window == NULL)
+ {
+ return;
+ }
+
+ slot = nautilus_window_get_active_slot (window);
+ view = nautilus_window_slot_get_current_view (slot);
+
+ if (!NAUTILUS_IS_FILES_VIEW (view))
+ {
+ return;
+ }
+
+ g_variant_get (parameters, "(u)", &direction);
+ nautilus_files_view_preview_selection_event (NAUTILUS_FILES_VIEW (view), direction);
+}
+
+guint
+nautilus_previewer_connect_selection_event (GDBusConnection *connection)
+{
+ return g_dbus_connection_signal_subscribe (connection,
+ PREVIEWER_DBUS_NAME,
+ PREVIEWER2_DBUS_IFACE,
+ "SelectionEvent",
+ PREVIEWER_DBUS_PATH,
+ NULL,
+ G_DBUS_SIGNAL_FLAGS_NONE,
+ previewer_selection_event,
+ NULL,
+ NULL);
+}
+
+void
+nautilus_previewer_disconnect_selection_event (GDBusConnection *connection,
+ guint event_id)
+{
+ g_dbus_connection_signal_unsubscribe (connection, event_id);
+}
+
+gboolean
+nautilus_previewer_is_visible (void)
+{
+ g_autoptr (GVariant) variant = NULL;
+
+ if (!ensure_previewer_v2_proxy ())
+ {
+ return FALSE;
+ }
+
+ variant = g_dbus_proxy_get_cached_property (previewer_v2_proxy, "Visible");
+ if (variant)
+ {
+ return g_variant_get_boolean (variant);
+ }
+
+ return FALSE;
+}
diff --git a/src/nautilus-previewer.h b/src/nautilus-previewer.h
new file mode 100644
index 0000000..73270fa
--- /dev/null
+++ b/src/nautilus-previewer.h
@@ -0,0 +1,42 @@
+/*
+ * nautilus-previewer: nautilus previewer DBus wrapper
+ *
+ * Copyright (C) 2011, Red Hat, Inc.
+ *
+ * Nautilus is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * Nautilus is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ *
+ * Author: Cosimo Cecchi <cosimoc@redhat.com>
+ *
+ */
+
+#pragma once
+
+#include <gio/gio.h>
+#include <glib.h>
+
+G_BEGIN_DECLS
+
+void nautilus_previewer_call_show_file (const gchar *uri,
+ const gchar *window_handle,
+ guint xid,
+ gboolean close_if_already_visible);
+void nautilus_previewer_call_close (void);
+
+gboolean nautilus_previewer_is_visible (void);
+
+guint nautilus_previewer_connect_selection_event (GDBusConnection *connection);
+void nautilus_previewer_disconnect_selection_event (GDBusConnection *connection,
+ guint event_id);
+
+G_END_DECLS
diff --git a/src/nautilus-profile.c b/src/nautilus-profile.c
new file mode 100644
index 0000000..3f6c640
--- /dev/null
+++ b/src/nautilus-profile.c
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2005 William Jon McCann <mccann@jhu.edu>
+ *
+ * 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: William Jon McCann <mccann@jhu.edu>
+ *
+ */
+
+#include "config.h"
+
+#include <stdio.h>
+#include <string.h>
+#include <stdarg.h>
+#include <signal.h>
+#include <time.h>
+#include <unistd.h>
+
+#include <glib.h>
+#include <glib/gstdio.h>
+
+#include "nautilus-profile.h"
+
+void
+_nautilus_profile_log (const char *func,
+ const char *note,
+ const char *format,
+ ...)
+{
+ va_list args;
+ char *str;
+ char *formatted;
+
+ if (format == NULL)
+ {
+ formatted = g_strdup ("");
+ }
+ else
+ {
+ va_start (args, format);
+ formatted = g_strdup_vprintf (format, args);
+ va_end (args);
+ }
+
+ if (func != NULL)
+ {
+ str = g_strdup_printf ("MARK: %s %s: %s %s", g_get_prgname (), func, note ? note : "", formatted);
+ }
+ else
+ {
+ str = g_strdup_printf ("MARK: %s: %s %s", g_get_prgname (), note ? note : "", formatted);
+ }
+
+ g_free (formatted);
+
+ g_access (str, F_OK);
+ g_free (str);
+}
diff --git a/src/nautilus-profile.h b/src/nautilus-profile.h
new file mode 100644
index 0000000..47f8131
--- /dev/null
+++ b/src/nautilus-profile.h
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2005 William Jon McCann <mccann@jhu.edu>
+ *
+ * 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: William Jon McCann <mccann@jhu.edu>
+ *
+ * Can be profiled like so:
+ * strace -ttt -f -o /tmp/logfile.strace nautilus
+ * python plot-timeline.py -o prettygraph.png /tmp/logfile.strace
+ *
+ * See: http://www.gnome.org/~federico/news-2006-03.html#09
+ */
+
+#pragma once
+
+#include <glib.h>
+
+G_BEGIN_DECLS
+
+#ifdef ENABLE_PROFILING
+#ifdef G_HAVE_ISO_VARARGS
+#define nautilus_profile_start(...) _nautilus_profile_log (G_STRFUNC, "start", __VA_ARGS__)
+#define nautilus_profile_end(...) _nautilus_profile_log (G_STRFUNC, "end", __VA_ARGS__)
+#define nautilus_profile_msg(...) _nautilus_profile_log (NULL, NULL, __VA_ARGS__)
+#elif defined(G_HAVE_GNUC_VARARGS)
+#define nautilus_profile_start(format...) _nautilus_profile_log (G_STRFUNC, "start", format)
+#define nautilus_profile_end(format...) _nautilus_profile_log (G_STRFUNC, "end", format)
+#define nautilus_profile_msg(format...) _nautilus_profile_log (NULL, NULL, format)
+#endif
+#else
+#define nautilus_profile_start(...)
+#define nautilus_profile_end(...)
+#define nautilus_profile_msg(...)
+#endif
+
+void _nautilus_profile_log (const char *func,
+ const char *note,
+ const char *format,
+ ...) G_GNUC_PRINTF (3, 4);
+
+G_END_DECLS \ No newline at end of file
diff --git a/src/nautilus-program-choosing.c b/src/nautilus-program-choosing.c
new file mode 100644
index 0000000..ceaee0a
--- /dev/null
+++ b/src/nautilus-program-choosing.c
@@ -0,0 +1,611 @@
+/* nautilus-program-choosing.c - functions for selecting and activating
+ * programs for opening/viewing particular files.
+ *
+ * Copyright (C) 2000 Eazel, Inc.
+ *
+ * The Gnome Library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * The Gnome Library 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
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with the Gnome Library; see the file COPYING.LIB. If not,
+ * see <http://www.gnu.org/licenses/>.
+ *
+ * Author: John Sullivan <sullivan@eazel.com>
+ */
+
+#include <config.h>
+#include "nautilus-program-choosing.h"
+
+#include "nautilus-global-preferences.h"
+#include "nautilus-icon-info.h"
+#include "nautilus-ui-utilities.h"
+#include "nautilus-window.h"
+#include <eel/eel-vfs-extensions.h>
+#include <gtk/gtk.h>
+#include <glib/gi18n.h>
+#include <gio/gio.h>
+#include <gio/gdesktopappinfo.h>
+#include <stdlib.h>
+
+#include <gdk/gdk.h>
+
+static void
+add_file_to_recent (NautilusFile *file,
+ GAppInfo *application)
+{
+ GtkRecentData recent_data;
+ char *uri;
+
+ uri = nautilus_file_get_activation_uri (file);
+ if (uri == NULL)
+ {
+ uri = nautilus_file_get_uri (file);
+ }
+
+ /* do not add trash:// etc */
+ if (eel_uri_is_trash (uri) ||
+ eel_uri_is_search (uri) ||
+ eel_uri_is_recent (uri))
+ {
+ g_free (uri);
+ return;
+ }
+
+ recent_data.display_name = NULL;
+ recent_data.description = NULL;
+
+ recent_data.mime_type = nautilus_file_get_mime_type (file);
+ recent_data.app_name = g_strdup (g_get_application_name ());
+ recent_data.app_exec = g_strdup (g_app_info_get_commandline (application));
+
+ recent_data.groups = NULL;
+ recent_data.is_private = FALSE;
+
+ gtk_recent_manager_add_full (gtk_recent_manager_get_default (),
+ uri, &recent_data);
+
+ g_free (recent_data.mime_type);
+ g_free (recent_data.app_name);
+ g_free (recent_data.app_exec);
+
+ g_free (uri);
+}
+void
+nautilus_launch_application_for_mount (GAppInfo *app_info,
+ GMount *mount,
+ GtkWindow *parent_window)
+{
+ GFile *root;
+ NautilusFile *file;
+ GList *files;
+
+ root = g_mount_get_root (mount);
+ file = nautilus_file_get (root);
+ g_object_unref (root);
+
+ files = g_list_append (NULL, file);
+ nautilus_launch_application (app_info,
+ files,
+ parent_window);
+
+ g_list_free_full (files, (GDestroyNotify) nautilus_file_unref);
+}
+
+/**
+ * nautilus_launch_application:
+ *
+ * Fork off a process to launch an application with a given file as a
+ * parameter. Provide a parent window for error dialogs.
+ *
+ * @application: The application to be launched.
+ * @uris: The files whose locations should be passed as a parameter to the application.
+ * @parent_window: A window to use as the parent for any error dialogs.
+ */
+void
+nautilus_launch_application (GAppInfo *application,
+ GList *files,
+ GtkWindow *parent_window)
+{
+ GList *uris, *l;
+
+ uris = NULL;
+ for (l = files; l != NULL; l = l->next)
+ {
+ uris = g_list_prepend (uris, nautilus_file_get_activation_uri (l->data));
+ }
+ uris = g_list_reverse (uris);
+ nautilus_launch_application_by_uri (application, uris,
+ parent_window);
+ g_list_free_full (uris, g_free);
+}
+
+static GdkAppLaunchContext *
+get_launch_context (GtkWindow *parent_window)
+{
+ GdkDisplay *display;
+ GdkAppLaunchContext *launch_context;
+
+ if (parent_window != NULL)
+ {
+ display = gtk_widget_get_display (GTK_WIDGET (parent_window));
+ }
+ else
+ {
+ display = gdk_display_get_default ();
+ }
+
+ launch_context = gdk_display_get_app_launch_context (display);
+
+ return launch_context;
+}
+
+void
+nautilus_launch_application_by_uri (GAppInfo *application,
+ GList *uris,
+ GtkWindow *parent_window)
+{
+ char *uri;
+ GList *locations, *l;
+ GFile *location;
+ NautilusFile *file;
+ gboolean result;
+ GError *error;
+ g_autoptr (GdkAppLaunchContext) launch_context = NULL;
+ NautilusIconInfo *icon;
+ int count, total;
+
+ g_assert (uris != NULL);
+
+ /* count the number of uris with local paths */
+ count = 0;
+ total = g_list_length (uris);
+ locations = NULL;
+ for (l = uris; l != NULL; l = l->next)
+ {
+ uri = l->data;
+
+ location = g_file_new_for_uri (uri);
+ if (g_file_is_native (location))
+ {
+ count++;
+ }
+ locations = g_list_prepend (locations, location);
+ }
+ locations = g_list_reverse (locations);
+
+ launch_context = get_launch_context (parent_window);
+
+ file = nautilus_file_get_by_uri (uris->data);
+ icon = nautilus_file_get_icon (file,
+ 48, gtk_widget_get_scale_factor (GTK_WIDGET (parent_window)),
+ 0);
+ nautilus_file_unref (file);
+ if (icon)
+ {
+ gdk_app_launch_context_set_icon_name (launch_context,
+ nautilus_icon_info_get_used_name (icon));
+ g_object_unref (icon);
+ }
+
+ error = NULL;
+
+ if (count == total)
+ {
+ /* All files are local, so we can use g_app_info_launch () with
+ * the file list we constructed before.
+ */
+ result = g_app_info_launch (application,
+ locations,
+ G_APP_LAUNCH_CONTEXT (launch_context),
+ &error);
+ }
+ else
+ {
+ /* Some files are non local, better use g_app_info_launch_uris ().
+ */
+ result = g_app_info_launch_uris (application,
+ uris,
+ G_APP_LAUNCH_CONTEXT (launch_context),
+ &error);
+ }
+
+ if (result)
+ {
+ for (l = uris; l != NULL; l = l->next)
+ {
+ file = nautilus_file_get_by_uri (l->data);
+ add_file_to_recent (file, application);
+ nautilus_file_unref (file);
+ }
+ }
+
+ g_list_free_full (locations, g_object_unref);
+}
+
+static void
+launch_application_from_command_internal (const gchar *full_command,
+ GdkDisplay *display,
+ gboolean use_terminal)
+{
+ GAppInfoCreateFlags flags;
+ g_autoptr (GError) error = NULL;
+ g_autoptr (GAppInfo) app = NULL;
+
+ flags = G_APP_INFO_CREATE_NONE;
+ if (use_terminal)
+ {
+ flags = G_APP_INFO_CREATE_NEEDS_TERMINAL;
+ }
+
+ app = g_app_info_create_from_commandline (full_command, NULL, flags, &error);
+ if (app != NULL && !(use_terminal && display == NULL))
+ {
+ g_autoptr (GdkAppLaunchContext) context = NULL;
+
+ context = gdk_display_get_app_launch_context (display);
+
+ g_app_info_launch (app, NULL, G_APP_LAUNCH_CONTEXT (context), &error);
+ }
+
+ if (error != NULL)
+ {
+ g_message ("Could not start application: %s", error->message);
+ }
+}
+
+/**
+ * nautilus_launch_application_from_command:
+ *
+ * Fork off a process to launch an application with a given uri as
+ * a parameter.
+ *
+ * @command_string: The application to be launched, with any desired
+ * command-line options.
+ * @...: Passed as parameters to the application after quoting each of them.
+ */
+void
+nautilus_launch_application_from_command (GdkDisplay *display,
+ const char *command_string,
+ gboolean use_terminal,
+ ...)
+{
+ char *full_command, *tmp;
+ char *quoted_parameter;
+ char *parameter;
+ va_list ap;
+
+ full_command = g_strdup (command_string);
+
+ va_start (ap, use_terminal);
+
+ while ((parameter = va_arg (ap, char *)) != NULL)
+ {
+ quoted_parameter = g_shell_quote (parameter);
+ tmp = g_strconcat (full_command, " ", quoted_parameter, NULL);
+ g_free (quoted_parameter);
+
+ g_free (full_command);
+ full_command = tmp;
+ }
+
+ va_end (ap);
+
+ launch_application_from_command_internal (full_command, display, use_terminal);
+
+ g_free (full_command);
+}
+
+/**
+ * nautilus_launch_application_from_command:
+ *
+ * Fork off a process to launch an application with a given uri as
+ * a parameter.
+ *
+ * @command_string: The application to be launched, with any desired
+ * command-line options.
+ * @parameters: Passed as parameters to the application after quoting each of them.
+ */
+void
+nautilus_launch_application_from_command_array (GdkDisplay *display,
+ const char *command_string,
+ gboolean use_terminal,
+ const char * const *parameters)
+{
+ char *full_command, *tmp;
+ char *quoted_parameter;
+ const char * const *p;
+
+ full_command = g_strdup (command_string);
+
+ if (parameters != NULL)
+ {
+ for (p = parameters; *p != NULL; p++)
+ {
+ quoted_parameter = g_shell_quote (*p);
+ tmp = g_strconcat (full_command, " ", quoted_parameter, NULL);
+ g_free (quoted_parameter);
+
+ g_free (full_command);
+ full_command = tmp;
+ }
+ }
+
+ launch_application_from_command_internal (full_command, display, use_terminal);
+
+ g_free (full_command);
+}
+
+void
+nautilus_launch_desktop_file (const char *desktop_file_uri,
+ const GList *parameter_uris,
+ GtkWindow *parent_window)
+{
+ GError *error;
+ char *message, *desktop_file_path;
+ const GList *p;
+ GList *files;
+ int total, count;
+ GFile *file, *desktop_file;
+ GDesktopAppInfo *app_info;
+ GdkAppLaunchContext *context;
+
+ /* Don't allow command execution from remote locations
+ * to partially mitigate the security
+ * risk of executing arbitrary commands.
+ */
+ desktop_file = g_file_new_for_uri (desktop_file_uri);
+ desktop_file_path = g_file_get_path (desktop_file);
+ if (!g_file_is_native (desktop_file))
+ {
+ g_free (desktop_file_path);
+ g_object_unref (desktop_file);
+ show_dialog (_("Sorry, but you cannot execute commands from a remote site."),
+ _("This is disabled due to security considerations."),
+ parent_window,
+ GTK_MESSAGE_ERROR);
+
+ return;
+ }
+ g_object_unref (desktop_file);
+
+ app_info = g_desktop_app_info_new_from_filename (desktop_file_path);
+ g_free (desktop_file_path);
+ if (app_info == NULL)
+ {
+ show_dialog (_("There was an error launching the application."),
+ NULL,
+ parent_window,
+ GTK_MESSAGE_ERROR);
+ return;
+ }
+
+ /* count the number of uris with local paths */
+ count = 0;
+ total = g_list_length ((GList *) parameter_uris);
+ files = NULL;
+ for (p = parameter_uris; p != NULL; p = p->next)
+ {
+ file = g_file_new_for_uri ((const char *) p->data);
+ if (g_file_is_native (file))
+ {
+ count++;
+ }
+ files = g_list_prepend (files, file);
+ }
+
+ /* check if this app only supports local files */
+ if (g_app_info_supports_files (G_APP_INFO (app_info)) &&
+ !g_app_info_supports_uris (G_APP_INFO (app_info)) &&
+ parameter_uris != NULL)
+ {
+ if (count == 0)
+ {
+ /* all files are non-local */
+ show_dialog (_("This drop target only supports local files."),
+ _("To open non-local files copy them to a local folder and then drop them again."),
+ parent_window,
+ GTK_MESSAGE_ERROR);
+
+ g_list_free_full (files, g_object_unref);
+ g_object_unref (app_info);
+ return;
+ }
+ else if (count != total)
+ {
+ /* some files are non-local */
+ show_dialog (_("This drop target only supports local files."),
+ _("To open non-local files copy them to a local folder and then"
+ " drop them again. The local files you dropped have already been opened."),
+ parent_window,
+ GTK_MESSAGE_WARNING);
+ }
+ }
+
+ error = NULL;
+ context = gdk_display_get_app_launch_context (gtk_widget_get_display (GTK_WIDGET (parent_window)));
+ /* TODO: Ideally we should accept a timestamp here instead of using GDK_CURRENT_TIME */
+ gdk_app_launch_context_set_timestamp (context, GDK_CURRENT_TIME);
+ if (count == total)
+ {
+ /* All files are local, so we can use g_app_info_launch () with
+ * the file list we constructed before.
+ */
+ g_app_info_launch (G_APP_INFO (app_info),
+ files,
+ G_APP_LAUNCH_CONTEXT (context),
+ &error);
+ }
+ else
+ {
+ /* Some files are non local, better use g_app_info_launch_uris ().
+ */
+ g_app_info_launch_uris (G_APP_INFO (app_info),
+ (GList *) parameter_uris,
+ G_APP_LAUNCH_CONTEXT (context),
+ &error);
+ }
+ if (error != NULL)
+ {
+ message = g_strconcat (_("Details: "), error->message, NULL);
+ show_dialog (_("There was an error launching the application."),
+ message,
+ parent_window,
+ GTK_MESSAGE_ERROR);
+
+ g_error_free (error);
+ g_free (message);
+ }
+
+ g_list_free_full (files, g_object_unref);
+ g_object_unref (context);
+ g_object_unref (app_info);
+}
+
+/* HAX
+ *
+ * TODO: remove everything below once it’s doable from GTK+.
+ *
+ * Context: https://bugzilla.gnome.org/show_bug.cgi?id=781132 and
+ * https://bugzilla.gnome.org/show_bug.cgi?id=779312
+ *
+ * In a sandboxed environment, this is needed to able to get the actual
+ * result of the operation, since gtk_show_uri_on_window () neither blocks
+ * nor returns a useful value.
+ */
+
+static void
+on_launch_default_for_uri (GObject *source,
+ GAsyncResult *result,
+ gpointer data)
+{
+ GTask *task;
+ NautilusWindow *window;
+ gboolean success;
+ GError *error = NULL;
+
+ task = data;
+ window = g_task_get_source_object (task);
+
+ success = g_app_info_launch_default_for_uri_finish (result, &error);
+
+ if (window)
+ {
+ nautilus_window_unexport_handle (window);
+ }
+
+ if (success)
+ {
+ g_task_return_boolean (task, success);
+ }
+ else
+ {
+ g_task_return_error (task, error);
+ }
+
+ /* Reffed in the call to nautilus_window_export_handle */
+ g_object_unref (task);
+}
+
+static void
+on_window_handle_export (NautilusWindow *window,
+ const char *handle_str,
+ guint xid,
+ gpointer user_data)
+{
+ GTask *task = user_data;
+ GAppLaunchContext *context = g_task_get_task_data (task);
+ const char *uri;
+
+ uri = g_object_get_data (G_OBJECT (context), "uri");
+
+ g_app_launch_context_setenv (context, "PARENT_WINDOW_ID", handle_str);
+
+ g_app_info_launch_default_for_uri_async (uri,
+ context,
+ g_task_get_cancellable (task),
+ on_launch_default_for_uri,
+ task);
+}
+
+static void
+launch_default_for_uri_thread_func (GTask *task,
+ gpointer source_object,
+ gpointer task_data,
+ GCancellable *cancellable)
+{
+ GAppLaunchContext *launch_context;
+ const char *uri;
+ gboolean success;
+ GError *error = NULL;
+
+ launch_context = task_data;
+ uri = g_object_get_data (G_OBJECT (launch_context), "uri");
+ success = g_app_info_launch_default_for_uri (uri, launch_context, &error);
+
+ if (success)
+ {
+ g_task_return_boolean (task, success);
+ }
+ else
+ {
+ g_task_return_error (task, error);
+ }
+}
+
+void
+nautilus_launch_default_for_uri_async (const char *uri,
+ GtkWindow *parent_window,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer callback_data)
+{
+ g_autoptr (GdkAppLaunchContext) launch_context = NULL;
+ g_autoptr (GTask) task = NULL;
+
+ g_return_if_fail (uri != NULL);
+
+ launch_context = get_launch_context (parent_window);
+ task = g_task_new (parent_window, cancellable, callback, callback_data);
+
+ gdk_app_launch_context_set_timestamp (launch_context, GDK_CURRENT_TIME);
+
+ g_object_set_data_full (G_OBJECT (launch_context),
+ "uri", g_strdup (uri), g_free);
+ g_task_set_task_data (task,
+ g_object_ref (launch_context), g_object_unref);
+
+ if (parent_window != NULL)
+ {
+ gboolean handle_exported;
+
+ handle_exported = nautilus_window_export_handle (NAUTILUS_WINDOW (parent_window),
+ on_window_handle_export,
+ g_object_ref (task));
+
+ if (handle_exported)
+ {
+ /* Launching will now be handled from the callback */
+ return;
+ }
+ }
+
+ g_task_run_in_thread (task, launch_default_for_uri_thread_func);
+}
+
+gboolean
+nautilus_launch_default_for_uri_finish (GAsyncResult *result,
+ GError **error)
+{
+ g_return_val_if_fail (G_IS_ASYNC_RESULT (result), FALSE);
+
+ return g_task_propagate_boolean (G_TASK (result), error);
+}
+
+/* END OF HAX */
diff --git a/src/nautilus-program-choosing.h b/src/nautilus-program-choosing.h
new file mode 100644
index 0000000..00ac6fe
--- /dev/null
+++ b/src/nautilus-program-choosing.h
@@ -0,0 +1,59 @@
+
+/* nautilus-program-choosing.h - functions for selecting and activating
+ programs for opening/viewing particular files.
+
+ Copyright (C) 2000 Eazel, Inc.
+
+ The Gnome Library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Library General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version.
+
+ The Gnome Library 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
+ Library General Public License for more details.
+
+ You should have received a copy of the GNU Library General Public
+ License along with the Gnome Library; see the file COPYING.LIB. If not,
+ see <http://www.gnu.org/licenses/>.
+
+ Author: John Sullivan <sullivan@eazel.com>
+*/
+
+#pragma once
+
+#include <gtk/gtk.h>
+#include <gio/gio.h>
+#include "nautilus-file.h"
+
+typedef void (*NautilusApplicationChoiceCallback) (GAppInfo *application,
+ gpointer callback_data);
+
+void nautilus_launch_application (GAppInfo *application,
+ GList *files,
+ GtkWindow *parent_window);
+void nautilus_launch_application_by_uri (GAppInfo *application,
+ GList *uris,
+ GtkWindow *parent_window);
+void nautilus_launch_application_for_mount (GAppInfo *app_info,
+ GMount *mount,
+ GtkWindow *parent_window);
+void nautilus_launch_application_from_command (GdkDisplay *display,
+ const char *command_string,
+ gboolean use_terminal,
+ ...) G_GNUC_NULL_TERMINATED;
+void nautilus_launch_application_from_command_array (GdkDisplay *display,
+ const char *command_string,
+ gboolean use_terminal,
+ const char * const * parameters);
+void nautilus_launch_desktop_file (const char *desktop_file_uri,
+ const GList *parameter_uris,
+ GtkWindow *parent_window);
+void nautilus_launch_default_for_uri_async (const char *uri,
+ GtkWindow *parent_window,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer callback_data);
+gboolean nautilus_launch_default_for_uri_finish (GAsyncResult *result,
+ GError **error);
diff --git a/src/nautilus-progress-indicator.c b/src/nautilus-progress-indicator.c
new file mode 100644
index 0000000..c49be9f
--- /dev/null
+++ b/src/nautilus-progress-indicator.c
@@ -0,0 +1,548 @@
+/*
+ * Copyright (C) 2022 The GNOME project contributors
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include "nautilus-progress-indicator.h"
+
+#include "nautilus-file-operations.h"
+#include "nautilus-progress-info-manager.h"
+#include "nautilus-progress-info-widget.h"
+#include "nautilus-window.h"
+
+#define OPERATION_MINIMUM_TIME 2 /*s */
+#define NEEDS_ATTENTION_ANIMATION_TIMEOUT 2000 /*ms */
+#define REMOVE_FINISHED_OPERATIONS_TIEMOUT 3 /*s */
+
+struct _NautilusProgressIndicator
+{
+ AdwBin parent_instance;
+
+ guint start_operations_timeout_id;
+ guint remove_finished_operations_timeout_id;
+ guint operations_button_attention_timeout_id;
+
+ GtkWidget *operations_button;
+ GtkWidget *operations_popover;
+ GtkWidget *operations_list;
+ GListStore *progress_infos_model;
+ GtkWidget *operations_revealer;
+ GtkWidget *operations_icon;
+
+ NautilusProgressInfoManager *progress_manager;
+};
+
+G_DEFINE_FINAL_TYPE (NautilusProgressIndicator, nautilus_progress_indicator, ADW_TYPE_BIN);
+
+
+static void update_operations (NautilusProgressIndicator *self);
+
+static gboolean
+should_show_progress_info (NautilusProgressInfo *info)
+{
+ return nautilus_progress_info_get_total_elapsed_time (info) +
+ nautilus_progress_info_get_remaining_time (info) > OPERATION_MINIMUM_TIME;
+}
+
+static GList *
+get_filtered_progress_infos (NautilusProgressIndicator *self)
+{
+ GList *l;
+ GList *filtered_progress_infos;
+ GList *progress_infos;
+
+ progress_infos = nautilus_progress_info_manager_get_all_infos (self->progress_manager);
+ filtered_progress_infos = NULL;
+
+ for (l = progress_infos; l != NULL; l = l->next)
+ {
+ if (should_show_progress_info (l->data))
+ {
+ filtered_progress_infos = g_list_append (filtered_progress_infos, l->data);
+ }
+ }
+
+ return filtered_progress_infos;
+}
+
+static gboolean
+should_hide_operations_button (NautilusProgressIndicator *self)
+{
+ GList *progress_infos;
+ GList *l;
+
+ progress_infos = get_filtered_progress_infos (self);
+
+ for (l = progress_infos; l != NULL; l = l->next)
+ {
+ if (nautilus_progress_info_get_total_elapsed_time (l->data) +
+ nautilus_progress_info_get_remaining_time (l->data) > OPERATION_MINIMUM_TIME &&
+ !nautilus_progress_info_get_is_cancelled (l->data) &&
+ !nautilus_progress_info_get_is_finished (l->data))
+ {
+ return FALSE;
+ }
+ }
+
+ g_list_free (progress_infos);
+
+ return TRUE;
+}
+
+static gboolean
+on_remove_finished_operations_timeout (NautilusProgressIndicator *self)
+{
+ nautilus_progress_info_manager_remove_finished_or_cancelled_infos (self->progress_manager);
+ if (should_hide_operations_button (self))
+ {
+ gtk_revealer_set_reveal_child (GTK_REVEALER (self->operations_revealer),
+ FALSE);
+ }
+ else
+ {
+ update_operations (self);
+ }
+
+ self->remove_finished_operations_timeout_id = 0;
+
+ return G_SOURCE_REMOVE;
+}
+
+static void
+unschedule_remove_finished_operations (NautilusProgressIndicator *self)
+{
+ if (self->remove_finished_operations_timeout_id != 0)
+ {
+ g_source_remove (self->remove_finished_operations_timeout_id);
+ self->remove_finished_operations_timeout_id = 0;
+ }
+}
+
+static void
+schedule_remove_finished_operations (NautilusProgressIndicator *self)
+{
+ if (self->remove_finished_operations_timeout_id == 0)
+ {
+ self->remove_finished_operations_timeout_id =
+ g_timeout_add_seconds (REMOVE_FINISHED_OPERATIONS_TIEMOUT,
+ (GSourceFunc) on_remove_finished_operations_timeout,
+ self);
+ }
+}
+
+static void
+remove_operations_button_attention_style (NautilusProgressIndicator *self)
+{
+ gtk_widget_remove_css_class (self->operations_button,
+ "nautilus-operations-button-needs-attention");
+}
+
+static gboolean
+on_remove_operations_button_attention_style_timeout (NautilusProgressIndicator *self)
+{
+ remove_operations_button_attention_style (self);
+ self->operations_button_attention_timeout_id = 0;
+
+ return G_SOURCE_REMOVE;
+}
+
+static void
+unschedule_operations_button_attention_style (NautilusProgressIndicator *self)
+{
+ if (self->operations_button_attention_timeout_id != 0)
+ {
+ g_source_remove (self->operations_button_attention_timeout_id);
+ self->operations_button_attention_timeout_id = 0;
+ }
+}
+
+static void
+add_operations_button_attention_style (NautilusProgressIndicator *self)
+{
+ unschedule_operations_button_attention_style (self);
+ remove_operations_button_attention_style (self);
+
+ gtk_widget_add_css_class (self->operations_button,
+ "nautilus-operations-button-needs-attention");
+ self->operations_button_attention_timeout_id = g_timeout_add (NEEDS_ATTENTION_ANIMATION_TIMEOUT,
+ (GSourceFunc) on_remove_operations_button_attention_style_timeout,
+ self);
+}
+
+static void
+on_progress_info_cancelled (NautilusProgressIndicator *self)
+{
+ /* Update the pie chart progress */
+ gtk_widget_queue_draw (self->operations_icon);
+
+ if (!nautilus_progress_manager_has_viewers (self->progress_manager))
+ {
+ schedule_remove_finished_operations (self);
+ }
+}
+
+static void
+on_progress_info_progress_changed (NautilusProgressIndicator *self)
+{
+ /* Update the pie chart progress */
+ gtk_widget_queue_draw (self->operations_icon);
+}
+
+static void
+on_progress_info_finished (NautilusProgressIndicator *self,
+ NautilusProgressInfo *info)
+{
+ NautilusWindow *window;
+ gchar *main_label;
+ GFile *folder_to_open;
+
+ window = NAUTILUS_WINDOW (gtk_widget_get_root (GTK_WIDGET (self)));
+
+ /* Update the pie chart progress */
+ gtk_widget_queue_draw (self->operations_icon);
+
+ if (!nautilus_progress_manager_has_viewers (self->progress_manager))
+ {
+ schedule_remove_finished_operations (self);
+ }
+
+ folder_to_open = nautilus_progress_info_get_destination (info);
+ /* If destination is null, don't show a notification. This happens when the
+ * operation is a trash operation, which we already show a diferent kind of
+ * notification */
+ if (!gtk_widget_is_visible (self->operations_popover) &&
+ folder_to_open != NULL)
+ {
+ add_operations_button_attention_style (self);
+ main_label = nautilus_progress_info_get_status (info);
+ nautilus_window_show_operation_notification (window,
+ main_label,
+ folder_to_open);
+ g_free (main_label);
+ }
+
+ g_clear_object (&folder_to_open);
+}
+
+static void
+disconnect_progress_infos (NautilusProgressIndicator *self)
+{
+ GList *progress_infos;
+ GList *l;
+
+ progress_infos = nautilus_progress_info_manager_get_all_infos (self->progress_manager);
+ for (l = progress_infos; l != NULL; l = l->next)
+ {
+ g_signal_handlers_disconnect_by_data (l->data, self);
+ }
+}
+
+static void
+update_operations (NautilusProgressIndicator *self)
+{
+ GList *progress_infos;
+ GList *l;
+ gboolean should_show_progress_button = FALSE;
+
+ disconnect_progress_infos (self);
+ g_list_store_remove_all (self->progress_infos_model);
+
+ progress_infos = get_filtered_progress_infos (self);
+ for (l = progress_infos; l != NULL; l = l->next)
+ {
+ should_show_progress_button = should_show_progress_button ||
+ should_show_progress_info (l->data);
+
+ g_signal_connect_object (l->data, "finished",
+ G_CALLBACK (on_progress_info_finished), self, G_CONNECT_SWAPPED);
+ g_signal_connect_object (l->data, "cancelled",
+ G_CALLBACK (on_progress_info_cancelled), self, G_CONNECT_SWAPPED);
+ g_signal_connect_object (l->data, "progress-changed",
+ G_CALLBACK (on_progress_info_progress_changed), self, G_CONNECT_SWAPPED);
+ g_list_store_append (self->progress_infos_model, l->data);
+ }
+
+ g_list_free (progress_infos);
+
+ if (should_show_progress_button &&
+ !gtk_revealer_get_reveal_child (GTK_REVEALER (self->operations_revealer)))
+ {
+ add_operations_button_attention_style (self);
+ gtk_revealer_set_reveal_child (GTK_REVEALER (self->operations_revealer),
+ TRUE);
+ gtk_widget_queue_draw (self->operations_icon);
+ }
+
+ /* Since we removed the info widgets, we need to restore the focus */
+ if (gtk_widget_get_visible (self->operations_popover))
+ {
+ gtk_widget_grab_focus (self->operations_popover);
+ }
+}
+
+static gboolean
+on_progress_info_started_timeout (NautilusProgressIndicator *self)
+{
+ GList *progress_infos;
+ GList *filtered_progress_infos;
+
+ update_operations (self);
+
+ /* In case we didn't show the operations button because the operation total
+ * time stimation is not good enough, update again to make sure we don't miss
+ * a long time operation because of that */
+
+ progress_infos = nautilus_progress_info_manager_get_all_infos (self->progress_manager);
+ filtered_progress_infos = get_filtered_progress_infos (self);
+ if (!nautilus_progress_manager_are_all_infos_finished_or_cancelled (self->progress_manager) &&
+ g_list_length (progress_infos) != g_list_length (filtered_progress_infos))
+ {
+ g_list_free (filtered_progress_infos);
+ return G_SOURCE_CONTINUE;
+ }
+ else
+ {
+ g_list_free (filtered_progress_infos);
+ self->start_operations_timeout_id = 0;
+ return G_SOURCE_REMOVE;
+ }
+}
+
+static void
+schedule_operations_start (NautilusProgressIndicator *self)
+{
+ if (self->start_operations_timeout_id == 0)
+ {
+ /* Timeout is a little more than what we require for a stimated operation
+ * total time, to make sure the stimated total time is correct */
+ self->start_operations_timeout_id =
+ g_timeout_add (SECONDS_NEEDED_FOR_APROXIMATE_TRANSFER_RATE * 1000 + 500,
+ (GSourceFunc) on_progress_info_started_timeout,
+ self);
+ }
+}
+
+static void
+unschedule_operations_start (NautilusProgressIndicator *self)
+{
+ if (self->start_operations_timeout_id != 0)
+ {
+ g_source_remove (self->start_operations_timeout_id);
+ self->start_operations_timeout_id = 0;
+ }
+}
+
+static void
+on_progress_info_started (NautilusProgressInfo *info,
+ NautilusProgressIndicator *self)
+{
+ g_signal_handlers_disconnect_by_data (info, self);
+ schedule_operations_start (self);
+}
+
+static void
+on_new_progress_info (NautilusProgressInfoManager *manager,
+ NautilusProgressInfo *info,
+ NautilusProgressIndicator *self)
+{
+ g_signal_connect (info, "started",
+ G_CALLBACK (on_progress_info_started), self);
+}
+
+static void
+on_operations_icon_draw (GtkDrawingArea *drawing_area,
+ cairo_t *cr,
+ int width,
+ int height,
+ NautilusProgressIndicator *self)
+{
+ GtkWidget *widget = GTK_WIDGET (drawing_area);
+ gfloat elapsed_progress = 0;
+ gint remaining_progress = 0;
+ gint total_progress;
+ gdouble ratio;
+ GList *progress_infos;
+ GList *l;
+ gboolean all_cancelled;
+ GdkRGBA background;
+ GdkRGBA foreground;
+ GtkStyleContext *style_context;
+
+ style_context = gtk_widget_get_style_context (widget);
+ gtk_style_context_get_color (style_context, &foreground);
+ background = foreground;
+ background.alpha *= 0.3;
+
+ all_cancelled = TRUE;
+ progress_infos = get_filtered_progress_infos (self);
+ for (l = progress_infos; l != NULL; l = l->next)
+ {
+ if (!nautilus_progress_info_get_is_cancelled (l->data))
+ {
+ all_cancelled = FALSE;
+ remaining_progress += nautilus_progress_info_get_remaining_time (l->data);
+ elapsed_progress += nautilus_progress_info_get_elapsed_time (l->data);
+ }
+ }
+
+ g_list_free (progress_infos);
+
+ total_progress = remaining_progress + elapsed_progress;
+
+ if (all_cancelled)
+ {
+ ratio = 1.0;
+ }
+ else
+ {
+ if (total_progress > 0)
+ {
+ ratio = MAX (0.05, elapsed_progress / total_progress);
+ }
+ else
+ {
+ ratio = 0.05;
+ }
+ }
+
+
+ width = gtk_widget_get_allocated_width (widget);
+ height = gtk_widget_get_allocated_height (widget);
+
+ gdk_cairo_set_source_rgba (cr, &background);
+ cairo_arc (cr,
+ width / 2.0, height / 2.0,
+ MIN (width, height) / 2.0,
+ 0, 2 * G_PI);
+ cairo_fill (cr);
+ cairo_move_to (cr, width / 2.0, height / 2.0);
+ gdk_cairo_set_source_rgba (cr, &foreground);
+ cairo_arc (cr,
+ width / 2.0, height / 2.0,
+ MIN (width, height) / 2.0,
+ -G_PI / 2.0, ratio * 2 * G_PI - G_PI / 2.0);
+
+ cairo_fill (cr);
+}
+
+static void
+on_operations_popover_notify_visible (NautilusProgressIndicator *self,
+ GParamSpec *pspec,
+ GObject *popover)
+{
+ if (gtk_widget_get_visible (GTK_WIDGET (popover)))
+ {
+ unschedule_remove_finished_operations (self);
+ nautilus_progress_manager_add_viewer (self->progress_manager,
+ G_OBJECT (self));
+ }
+ else
+ {
+ nautilus_progress_manager_remove_viewer (self->progress_manager,
+ G_OBJECT (self));
+ }
+}
+
+static void
+on_progress_has_viewers_changed (NautilusProgressInfoManager *manager,
+ NautilusProgressIndicator *self)
+{
+ if (nautilus_progress_manager_has_viewers (manager))
+ {
+ unschedule_remove_finished_operations (self);
+ return;
+ }
+
+ if (nautilus_progress_manager_are_all_infos_finished_or_cancelled (manager))
+ {
+ unschedule_remove_finished_operations (self);
+ schedule_remove_finished_operations (self);
+ }
+}
+
+static GtkWidget *
+operations_list_create_widget (GObject *item,
+ gpointer user_data)
+{
+ NautilusProgressInfo *info = NAUTILUS_PROGRESS_INFO (item);
+ GtkWidget *widget;
+
+ widget = nautilus_progress_info_widget_new (info);
+ gtk_widget_show (widget);
+
+ return widget;
+}
+
+static void
+nautilus_progress_indicator_constructed (GObject *object)
+{
+ NautilusProgressIndicator *self = NAUTILUS_PROGRESS_INDICATOR (object);
+
+ self->progress_manager = nautilus_progress_info_manager_dup_singleton ();
+ g_signal_connect (self->progress_manager, "new-progress-info",
+ G_CALLBACK (on_new_progress_info), self);
+ g_signal_connect (self->progress_manager, "has-viewers-changed",
+ G_CALLBACK (on_progress_has_viewers_changed), self);
+
+ self->progress_infos_model = g_list_store_new (NAUTILUS_TYPE_PROGRESS_INFO);
+ gtk_list_box_bind_model (GTK_LIST_BOX (self->operations_list),
+ G_LIST_MODEL (self->progress_infos_model),
+ (GtkListBoxCreateWidgetFunc) operations_list_create_widget,
+ NULL,
+ NULL);
+ update_operations (self);
+
+ g_signal_connect (self->operations_popover, "show",
+ (GCallback) gtk_widget_grab_focus, NULL);
+ g_signal_connect_swapped (self->operations_popover, "closed",
+ (GCallback) gtk_widget_grab_focus, self);
+}
+
+static void
+nautilus_progress_indicator_finalize (GObject *obj)
+{
+ NautilusProgressIndicator *self = NAUTILUS_PROGRESS_INDICATOR (obj);
+
+ disconnect_progress_infos (self);
+ unschedule_remove_finished_operations (self);
+ unschedule_operations_start (self);
+ unschedule_operations_button_attention_style (self);
+
+ g_clear_object (&self->progress_infos_model);
+ g_signal_handlers_disconnect_by_data (self->progress_manager, self);
+ g_clear_object (&self->progress_manager);
+
+ G_OBJECT_CLASS (nautilus_progress_indicator_parent_class)->finalize (obj);
+}
+
+static void
+nautilus_progress_indicator_class_init (NautilusProgressIndicatorClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+
+ object_class->constructed = nautilus_progress_indicator_constructed;
+ object_class->finalize = nautilus_progress_indicator_finalize;
+
+ gtk_widget_class_set_template_from_resource (widget_class,
+ "/org/gnome/nautilus/ui/nautilus-progress-indicator.ui");
+ gtk_widget_class_bind_template_child (widget_class, NautilusProgressIndicator, operations_button);
+ gtk_widget_class_bind_template_child (widget_class, NautilusProgressIndicator, operations_icon);
+ gtk_widget_class_bind_template_child (widget_class, NautilusProgressIndicator, operations_popover);
+ gtk_widget_class_bind_template_child (widget_class, NautilusProgressIndicator, operations_list);
+ gtk_widget_class_bind_template_child (widget_class, NautilusProgressIndicator, operations_revealer);
+
+ gtk_widget_class_bind_template_callback (widget_class, on_operations_popover_notify_visible);
+}
+
+static void
+nautilus_progress_indicator_init (NautilusProgressIndicator *self)
+{
+ gtk_widget_init_template (GTK_WIDGET (self));
+
+ gtk_drawing_area_set_draw_func (GTK_DRAWING_AREA (self->operations_icon),
+ (GtkDrawingAreaDrawFunc) on_operations_icon_draw,
+ self,
+ NULL);
+}
diff --git a/src/nautilus-progress-indicator.h b/src/nautilus-progress-indicator.h
new file mode 100644
index 0000000..9b69ac0
--- /dev/null
+++ b/src/nautilus-progress-indicator.h
@@ -0,0 +1,20 @@
+/*
+ * Copyright (C) 2022 The GNOME project contributors
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#pragma once
+
+#include <gtk/gtk.h>
+#include <libadwaita-1/adwaita.h>
+
+#include "nautilus-window-slot.h"
+
+G_BEGIN_DECLS
+
+#define NAUTILUS_TYPE_PROGRESS_INDICATOR (nautilus_progress_indicator_get_type())
+
+G_DECLARE_FINAL_TYPE (NautilusProgressIndicator, nautilus_progress_indicator, NAUTILUS, PROGRESS_INDICATOR, AdwBin)
+
+G_END_DECLS
diff --git a/src/nautilus-progress-info-manager.c b/src/nautilus-progress-info-manager.c
new file mode 100644
index 0000000..88e76cf
--- /dev/null
+++ b/src/nautilus-progress-info-manager.c
@@ -0,0 +1,239 @@
+/*
+ * Nautilus
+ *
+ * Copyright (C) 2011 Red Hat, Inc.
+ *
+ * Nautilus is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * Nautilus is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; see the file COPYING. If not,
+ * see <http://www.gnu.org/licenses/>.
+ *
+ * Author: Cosimo Cecchi <cosimoc@redhat.com>
+ */
+
+#include <config.h>
+
+#include "nautilus-progress-info-manager.h"
+
+struct _NautilusProgressInfoManager
+{
+ GObject parent_instance;
+
+ GList *progress_infos;
+ GList *current_viewers;
+};
+
+enum
+{
+ NEW_PROGRESS_INFO,
+ HAS_VIEWERS_CHANGED,
+ LAST_SIGNAL
+};
+
+static NautilusProgressInfoManager *singleton = NULL;
+
+static guint signals[LAST_SIGNAL] = { 0, };
+
+G_DEFINE_TYPE (NautilusProgressInfoManager, nautilus_progress_info_manager,
+ G_TYPE_OBJECT);
+
+static void remove_viewer (NautilusProgressInfoManager *self,
+ GObject *viewer);
+
+static void
+nautilus_progress_info_manager_finalize (GObject *obj)
+{
+ GList *l;
+ NautilusProgressInfoManager *self = NAUTILUS_PROGRESS_INFO_MANAGER (obj);
+
+ if (self->progress_infos != NULL)
+ {
+ g_list_free_full (self->progress_infos, g_object_unref);
+ }
+
+ for (l = self->current_viewers; l != NULL; l = l->next)
+ {
+ g_object_weak_unref (l->data, (GWeakNotify) remove_viewer, self);
+ }
+ g_list_free (self->current_viewers);
+
+ G_OBJECT_CLASS (nautilus_progress_info_manager_parent_class)->finalize (obj);
+}
+
+static GObject *
+nautilus_progress_info_manager_constructor (GType type,
+ guint n_props,
+ GObjectConstructParam *props)
+{
+ GObject *retval;
+
+ if (singleton != NULL)
+ {
+ return G_OBJECT (g_object_ref (singleton));
+ }
+
+ retval = G_OBJECT_CLASS (nautilus_progress_info_manager_parent_class)->constructor
+ (type, n_props, props);
+
+ singleton = NAUTILUS_PROGRESS_INFO_MANAGER (retval);
+ g_object_add_weak_pointer (retval, (gpointer) & singleton);
+
+ return retval;
+}
+
+static void
+nautilus_progress_info_manager_init (NautilusProgressInfoManager *self)
+{
+}
+
+static void
+nautilus_progress_info_manager_class_init (NautilusProgressInfoManagerClass *klass)
+{
+ GObjectClass *oclass;
+
+ oclass = G_OBJECT_CLASS (klass);
+ oclass->constructor = nautilus_progress_info_manager_constructor;
+ oclass->finalize = nautilus_progress_info_manager_finalize;
+
+ signals[NEW_PROGRESS_INFO] =
+ g_signal_new ("new-progress-info",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST,
+ 0, NULL, NULL,
+ g_cclosure_marshal_VOID__OBJECT,
+ G_TYPE_NONE,
+ 1,
+ NAUTILUS_TYPE_PROGRESS_INFO);
+
+ signals[HAS_VIEWERS_CHANGED] =
+ g_signal_new ("has-viewers-changed",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST,
+ 0, NULL, NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE,
+ 0);
+}
+
+NautilusProgressInfoManager *
+nautilus_progress_info_manager_dup_singleton (void)
+{
+ return g_object_new (NAUTILUS_TYPE_PROGRESS_INFO_MANAGER, NULL);
+}
+
+void
+nautilus_progress_info_manager_add_new_info (NautilusProgressInfoManager *self,
+ NautilusProgressInfo *info)
+{
+ if (g_list_find (self->progress_infos, info) != NULL)
+ {
+ g_warning ("Adding two times the same progress info object to the manager");
+ return;
+ }
+
+ self->progress_infos =
+ g_list_prepend (self->progress_infos, g_object_ref (info));
+
+ g_signal_emit (self, signals[NEW_PROGRESS_INFO], 0, info);
+}
+
+void
+nautilus_progress_info_manager_remove_finished_or_cancelled_infos (NautilusProgressInfoManager *self)
+{
+ GList *l;
+ GList *next;
+
+ l = self->progress_infos;
+ while (l != NULL)
+ {
+ next = l->next;
+ if (nautilus_progress_info_get_is_finished (l->data) ||
+ nautilus_progress_info_get_is_cancelled (l->data))
+ {
+ self->progress_infos = g_list_remove (self->progress_infos,
+ l->data);
+ }
+ l = next;
+ }
+}
+
+GList *
+nautilus_progress_info_manager_get_all_infos (NautilusProgressInfoManager *self)
+{
+ return self->progress_infos;
+}
+
+gboolean
+nautilus_progress_manager_are_all_infos_finished_or_cancelled (NautilusProgressInfoManager *self)
+{
+ GList *l;
+
+ for (l = self->progress_infos; l != NULL; l = l->next)
+ {
+ if (!(nautilus_progress_info_get_is_finished (l->data) ||
+ nautilus_progress_info_get_is_cancelled (l->data)))
+ {
+ return FALSE;
+ }
+ }
+
+ return TRUE;
+}
+
+static void
+remove_viewer (NautilusProgressInfoManager *self,
+ GObject *viewer)
+{
+ self->current_viewers = g_list_remove (self->current_viewers, viewer);
+
+ if (self->current_viewers == NULL)
+ {
+ g_signal_emit (self, signals[HAS_VIEWERS_CHANGED], 0);
+ }
+}
+
+void
+nautilus_progress_manager_add_viewer (NautilusProgressInfoManager *self,
+ GObject *viewer)
+{
+ GList *viewers;
+
+ viewers = self->current_viewers;
+ if (g_list_find (viewers, viewer) == NULL)
+ {
+ g_object_weak_ref (viewer, (GWeakNotify) remove_viewer, self);
+ viewers = g_list_append (viewers, viewer);
+ self->current_viewers = viewers;
+
+ if (g_list_length (viewers) == 1)
+ {
+ g_signal_emit (self, signals[HAS_VIEWERS_CHANGED], 0);
+ }
+ }
+}
+
+void
+nautilus_progress_manager_remove_viewer (NautilusProgressInfoManager *self,
+ GObject *viewer)
+{
+ if (g_list_find (self->current_viewers, viewer) != NULL)
+ {
+ g_object_weak_unref (viewer, (GWeakNotify) remove_viewer, self);
+ remove_viewer (self, viewer);
+ }
+}
+
+gboolean
+nautilus_progress_manager_has_viewers (NautilusProgressInfoManager *self)
+{
+ return self->current_viewers != NULL;
+}
diff --git a/src/nautilus-progress-info-manager.h b/src/nautilus-progress-info-manager.h
new file mode 100644
index 0000000..9ffc6f4
--- /dev/null
+++ b/src/nautilus-progress-info-manager.h
@@ -0,0 +1,44 @@
+/*
+ * Nautilus
+ *
+ * Copyright (C) 2011 Red Hat, Inc.
+ *
+ * Nautilus is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * Nautilus is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; see the file COPYING. If not,
+ * see <http://www.gnu.org/licenses/>.
+ *
+ * Author: Cosimo Cecchi <cosimoc@redhat.com>
+ */
+
+#pragma once
+
+#include <glib-object.h>
+
+#include "nautilus-progress-info.h"
+
+#define NAUTILUS_TYPE_PROGRESS_INFO_MANAGER nautilus_progress_info_manager_get_type()
+G_DECLARE_FINAL_TYPE (NautilusProgressInfoManager, nautilus_progress_info_manager, NAUTILUS, PROGRESS_INFO_MANAGER, GObject)
+
+NautilusProgressInfoManager* nautilus_progress_info_manager_dup_singleton (void);
+
+void nautilus_progress_info_manager_add_new_info (NautilusProgressInfoManager *self,
+ NautilusProgressInfo *info);
+GList *nautilus_progress_info_manager_get_all_infos (NautilusProgressInfoManager *self);
+void nautilus_progress_info_manager_remove_finished_or_cancelled_infos (NautilusProgressInfoManager *self);
+gboolean nautilus_progress_manager_are_all_infos_finished_or_cancelled (NautilusProgressInfoManager *self);
+
+void nautilus_progress_manager_add_viewer (NautilusProgressInfoManager *self, GObject *viewer);
+void nautilus_progress_manager_remove_viewer (NautilusProgressInfoManager *self, GObject *viewer);
+gboolean nautilus_progress_manager_has_viewers (NautilusProgressInfoManager *self);
+
+G_END_DECLS \ No newline at end of file
diff --git a/src/nautilus-progress-info-widget.c b/src/nautilus-progress-info-widget.c
new file mode 100644
index 0000000..b5be930
--- /dev/null
+++ b/src/nautilus-progress-info-widget.c
@@ -0,0 +1,223 @@
+/*
+ * nautilus-progress-info-widget.h: file operation progress user interface.
+ *
+ * Copyright (C) 2007, 2011 Red Hat, 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: Alexander Larsson <alexl@redhat.com>
+ * Cosimo Cecchi <cosimoc@redhat.com>
+ *
+ */
+
+#include <config.h>
+
+#include "nautilus-progress-info-widget.h"
+struct _NautilusProgressInfoWidgetPrivate
+{
+ NautilusProgressInfo *info;
+
+ GtkWidget *status; /* GtkLabel */
+ GtkWidget *details; /* GtkLabel */
+ GtkWidget *progress_bar;
+ GtkWidget *button;
+};
+
+enum
+{
+ PROP_INFO = 1,
+ NUM_PROPERTIES
+};
+
+static GParamSpec *properties[NUM_PROPERTIES] = { NULL };
+
+G_DEFINE_TYPE_WITH_PRIVATE (NautilusProgressInfoWidget, nautilus_progress_info_widget,
+ GTK_TYPE_GRID);
+
+static void
+info_finished (NautilusProgressInfoWidget *self)
+{
+ gtk_button_set_icon_name (GTK_BUTTON (self->priv->button), "object-select-symbolic");
+ gtk_widget_set_sensitive (self->priv->button, FALSE);
+}
+
+static void
+info_cancelled (NautilusProgressInfoWidget *self)
+{
+ gtk_widget_set_sensitive (self->priv->button, FALSE);
+}
+
+static void
+update_data (NautilusProgressInfoWidget *self)
+{
+ char *status, *details;
+ char *markup;
+
+ status = nautilus_progress_info_get_status (self->priv->info);
+ gtk_label_set_text (GTK_LABEL (self->priv->status), status);
+ g_free (status);
+
+ details = nautilus_progress_info_get_details (self->priv->info);
+ markup = g_markup_printf_escaped ("<span size='small'>%s</span>", details);
+ gtk_label_set_markup (GTK_LABEL (self->priv->details), markup);
+ g_free (details);
+ g_free (markup);
+}
+
+static void
+update_progress (NautilusProgressInfoWidget *self)
+{
+ double progress;
+
+ progress = nautilus_progress_info_get_progress (self->priv->info);
+ if (progress < 0)
+ {
+ gtk_progress_bar_pulse (GTK_PROGRESS_BAR (self->priv->progress_bar));
+ }
+ else
+ {
+ gtk_progress_bar_set_fraction (GTK_PROGRESS_BAR (self->priv->progress_bar), progress);
+ }
+}
+
+static void
+button_clicked (GtkWidget *button,
+ NautilusProgressInfoWidget *self)
+{
+ if (!nautilus_progress_info_get_is_finished (self->priv->info))
+ {
+ nautilus_progress_info_cancel (self->priv->info);
+ }
+}
+
+static void
+nautilus_progress_info_widget_dispose (GObject *obj)
+{
+ NautilusProgressInfoWidget *self = NAUTILUS_PROGRESS_INFO_WIDGET (obj);
+
+ if (self->priv->info != NULL)
+ {
+ g_signal_handlers_disconnect_by_data (self->priv->info, self);
+ }
+ g_clear_object (&self->priv->info);
+
+ G_OBJECT_CLASS (nautilus_progress_info_widget_parent_class)->dispose (obj);
+}
+
+static void
+nautilus_progress_info_widget_constructed (GObject *obj)
+{
+ NautilusProgressInfoWidget *self = NAUTILUS_PROGRESS_INFO_WIDGET (obj);
+
+ G_OBJECT_CLASS (nautilus_progress_info_widget_parent_class)->constructed (obj);
+
+ if (nautilus_progress_info_get_is_finished (self->priv->info))
+ {
+ gtk_button_set_icon_name (GTK_BUTTON (self->priv->button), "object-select-symbolic");
+ }
+
+ gtk_widget_set_sensitive (self->priv->button,
+ !nautilus_progress_info_get_is_finished (self->priv->info) &&
+ !nautilus_progress_info_get_is_cancelled (self->priv->info));
+
+ g_signal_connect_swapped (self->priv->info,
+ "changed",
+ G_CALLBACK (update_data), self);
+ g_signal_connect_swapped (self->priv->info,
+ "progress-changed",
+ G_CALLBACK (update_progress), self);
+ g_signal_connect_swapped (self->priv->info,
+ "finished",
+ G_CALLBACK (info_finished), self);
+ g_signal_connect_swapped (self->priv->info,
+ "cancelled",
+ G_CALLBACK (info_cancelled), self);
+
+ update_data (self);
+ update_progress (self);
+}
+
+static void
+nautilus_progress_info_widget_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ NautilusProgressInfoWidget *self = NAUTILUS_PROGRESS_INFO_WIDGET (object);
+
+ switch (property_id)
+ {
+ case PROP_INFO:
+ {
+ self->priv->info = g_value_dup_object (value);
+ }
+ break;
+
+ default:
+ {
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ }
+ break;
+ }
+}
+
+static void
+nautilus_progress_info_widget_init (NautilusProgressInfoWidget *self)
+{
+ self->priv = nautilus_progress_info_widget_get_instance_private (self);
+
+ gtk_widget_init_template (GTK_WIDGET (self));
+
+ g_signal_connect (self->priv->button, "clicked",
+ G_CALLBACK (button_clicked), self);
+}
+
+static void
+nautilus_progress_info_widget_class_init (NautilusProgressInfoWidgetClass *klass)
+{
+ GObjectClass *oclass;
+ GtkWidgetClass *widget_class;
+
+ widget_class = GTK_WIDGET_CLASS (klass);
+ oclass = G_OBJECT_CLASS (klass);
+ oclass->set_property = nautilus_progress_info_widget_set_property;
+ oclass->constructed = nautilus_progress_info_widget_constructed;
+ oclass->dispose = nautilus_progress_info_widget_dispose;
+
+ properties[PROP_INFO] =
+ g_param_spec_object ("info",
+ "NautilusProgressInfo",
+ "The NautilusProgressInfo associated with this widget",
+ NAUTILUS_TYPE_PROGRESS_INFO,
+ G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY |
+ G_PARAM_STATIC_STRINGS);
+
+ g_object_class_install_properties (oclass, NUM_PROPERTIES, properties);
+
+ gtk_widget_class_set_template_from_resource (widget_class,
+ "/org/gnome/nautilus/ui/nautilus-progress-info-widget.ui");
+
+ gtk_widget_class_bind_template_child_private (widget_class, NautilusProgressInfoWidget, status);
+ gtk_widget_class_bind_template_child_private (widget_class, NautilusProgressInfoWidget, details);
+ gtk_widget_class_bind_template_child_private (widget_class, NautilusProgressInfoWidget, progress_bar);
+ gtk_widget_class_bind_template_child_private (widget_class, NautilusProgressInfoWidget, button);
+}
+
+GtkWidget *
+nautilus_progress_info_widget_new (NautilusProgressInfo *info)
+{
+ return g_object_new (NAUTILUS_TYPE_PROGRESS_INFO_WIDGET,
+ "info", info,
+ NULL);
+}
diff --git a/src/nautilus-progress-info-widget.h b/src/nautilus-progress-info-widget.h
new file mode 100644
index 0000000..62fe7ca
--- /dev/null
+++ b/src/nautilus-progress-info-widget.h
@@ -0,0 +1,57 @@
+/*
+ * nautilus-progress-info-widget.h: file operation progress user interface.
+ *
+ * Copyright (C) 2007, 2011 Red Hat, 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: Alexander Larsson <alexl@redhat.com>
+ * Cosimo Cecchi <cosimoc@redhat.com>
+ *
+ */
+
+#pragma once
+
+#include <gtk/gtk.h>
+
+#include "nautilus-progress-info.h"
+
+#define NAUTILUS_TYPE_PROGRESS_INFO_WIDGET nautilus_progress_info_widget_get_type()
+#define NAUTILUS_PROGRESS_INFO_WIDGET(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST ((obj), NAUTILUS_TYPE_PROGRESS_INFO_WIDGET, NautilusProgressInfoWidget))
+#define NAUTILUS_PROGRESS_INFO_WIDGET_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST ((klass), NAUTILUS_TYPE_PROGRESS_INFO_WIDGET, NautilusProgressInfoWidgetClass))
+#define NAUTILUS_IS_PROGRESS_INFO_WIDGET(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE ((obj), NAUTILUS_TYPE_PROGRESS_INFO_WIDGET))
+#define NAUTILUS_IS_PROGRESS_INFO_WIDGET_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE ((klass), NAUTILUS_TYPE_PROGRESS_INFO_WIDGET))
+#define NAUTILUS_PROGRESS_INFO_WIDGET_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), NAUTILUS_TYPE_PROGRESS_INFO_WIDGET, NautilusProgressInfoWidgetClass))
+
+typedef struct _NautilusProgressInfoWidgetPrivate NautilusProgressInfoWidgetPrivate;
+
+typedef struct {
+ GtkGrid parent;
+
+ /* private */
+ NautilusProgressInfoWidgetPrivate *priv;
+} NautilusProgressInfoWidget;
+
+typedef struct {
+ GtkGridClass parent_class;
+} NautilusProgressInfoWidgetClass;
+
+GType nautilus_progress_info_widget_get_type (void);
+
+GtkWidget * nautilus_progress_info_widget_new (NautilusProgressInfo *info); \ No newline at end of file
diff --git a/src/nautilus-progress-info.c b/src/nautilus-progress-info.c
new file mode 100644
index 0000000..521bd71
--- /dev/null
+++ b/src/nautilus-progress-info.c
@@ -0,0 +1,760 @@
+/*
+ * nautilus-progress-info.h: file operation progress info.
+ *
+ * Copyright (C) 2007 Red Hat, 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/>.
+ *
+ * Author: Alexander Larsson <alexl@redhat.com>
+ */
+
+#include <config.h>
+#include <math.h>
+#include <glib/gi18n.h>
+#include <eel/eel-string.h>
+#include <eel/eel-glib-extensions.h>
+#include "nautilus-progress-info.h"
+#include "nautilus-progress-info-manager.h"
+#include "nautilus-icon-info.h"
+
+enum
+{
+ CHANGED,
+ PROGRESS_CHANGED,
+ STARTED,
+ FINISHED,
+ CANCELLED,
+ LAST_SIGNAL
+};
+
+#define SIGNAL_DELAY_MSEC 100
+
+static guint signals[LAST_SIGNAL] = { 0 };
+
+struct _NautilusProgressInfo
+{
+ GObject parent_instance;
+
+ GCancellable *cancellable;
+ guint cancellable_id;
+
+ GTimer *progress_timer;
+
+ char *status;
+ char *details;
+ double progress;
+ gdouble remaining_time;
+ gdouble elapsed_time;
+ gboolean activity_mode;
+ gboolean started;
+ gboolean finished;
+ gboolean paused;
+
+ GSource *idle_source;
+ gboolean source_is_now;
+
+ gboolean start_at_idle;
+ gboolean finish_at_idle;
+ gboolean cancel_at_idle;
+ gboolean changed_at_idle;
+ gboolean progress_at_idle;
+
+ GFile *destination;
+};
+
+G_LOCK_DEFINE_STATIC (progress_info);
+
+G_DEFINE_TYPE (NautilusProgressInfo, nautilus_progress_info, G_TYPE_OBJECT)
+
+static void set_details (NautilusProgressInfo *info,
+ const char *details);
+static void set_status (NautilusProgressInfo *info,
+ const char *status);
+
+static void
+nautilus_progress_info_finalize (GObject *object)
+{
+ NautilusProgressInfo *info;
+
+ info = NAUTILUS_PROGRESS_INFO (object);
+
+ g_free (info->status);
+ g_free (info->details);
+ g_clear_pointer (&info->progress_timer, g_timer_destroy);
+ g_cancellable_disconnect (info->cancellable, info->cancellable_id);
+ g_object_unref (info->cancellable);
+ g_clear_object (&info->destination);
+
+ if (G_OBJECT_CLASS (nautilus_progress_info_parent_class)->finalize)
+ {
+ (*G_OBJECT_CLASS (nautilus_progress_info_parent_class)->finalize)(object);
+ }
+}
+
+static void
+nautilus_progress_info_dispose (GObject *object)
+{
+ NautilusProgressInfo *info;
+
+ info = NAUTILUS_PROGRESS_INFO (object);
+
+ G_LOCK (progress_info);
+
+ /* Destroy source in dispose, because the callback
+ * could come here before the destroy, which should
+ * ressurect the object for a while */
+ if (info->idle_source)
+ {
+ g_source_destroy (info->idle_source);
+ g_source_unref (info->idle_source);
+ info->idle_source = NULL;
+ }
+ G_UNLOCK (progress_info);
+}
+
+static void
+nautilus_progress_info_class_init (NautilusProgressInfoClass *klass)
+{
+ GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+
+ gobject_class->finalize = nautilus_progress_info_finalize;
+ gobject_class->dispose = nautilus_progress_info_dispose;
+
+ signals[CHANGED] =
+ g_signal_new ("changed",
+ NAUTILUS_TYPE_PROGRESS_INFO,
+ G_SIGNAL_RUN_LAST,
+ 0,
+ NULL, NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+
+ signals[PROGRESS_CHANGED] =
+ g_signal_new ("progress-changed",
+ NAUTILUS_TYPE_PROGRESS_INFO,
+ G_SIGNAL_RUN_LAST,
+ 0,
+ NULL, NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+
+ signals[STARTED] =
+ g_signal_new ("started",
+ NAUTILUS_TYPE_PROGRESS_INFO,
+ G_SIGNAL_RUN_LAST,
+ 0,
+ NULL, NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+
+ signals[FINISHED] =
+ g_signal_new ("finished",
+ NAUTILUS_TYPE_PROGRESS_INFO,
+ G_SIGNAL_RUN_LAST,
+ 0,
+ NULL, NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+
+ signals[CANCELLED] =
+ g_signal_new ("cancelled",
+ NAUTILUS_TYPE_PROGRESS_INFO,
+ G_SIGNAL_RUN_LAST,
+ 0,
+ NULL, NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+}
+
+static gboolean
+idle_callback (gpointer data)
+{
+ NautilusProgressInfo *info = data;
+ gboolean start_at_idle;
+ gboolean finish_at_idle;
+ gboolean changed_at_idle;
+ gboolean progress_at_idle;
+ gboolean cancelled_at_idle;
+ GSource *source;
+
+ G_LOCK (progress_info);
+
+ source = g_main_current_source ();
+
+ /* Protect agains races where the source has
+ * been destroyed on another thread while it
+ * was being dispatched.
+ * Similar to what gdk_threads_add_idle does.
+ */
+ if (g_source_is_destroyed (source))
+ {
+ G_UNLOCK (progress_info);
+ return FALSE;
+ }
+
+ /* We hadn't destroyed the source, so take a ref.
+ * This might ressurect the object from dispose, but
+ * that should be ok.
+ */
+ g_object_ref (info);
+
+ g_assert (source == info->idle_source);
+
+ g_source_unref (source);
+ info->idle_source = NULL;
+
+ start_at_idle = info->start_at_idle;
+ finish_at_idle = info->finish_at_idle;
+ changed_at_idle = info->changed_at_idle;
+ progress_at_idle = info->progress_at_idle;
+ cancelled_at_idle = info->cancel_at_idle;
+
+ info->start_at_idle = FALSE;
+ info->finish_at_idle = FALSE;
+ info->changed_at_idle = FALSE;
+ info->progress_at_idle = FALSE;
+ info->cancel_at_idle = FALSE;
+
+ G_UNLOCK (progress_info);
+
+ if (start_at_idle)
+ {
+ g_signal_emit (info,
+ signals[STARTED],
+ 0);
+ }
+
+ if (changed_at_idle)
+ {
+ g_signal_emit (info,
+ signals[CHANGED],
+ 0);
+ }
+
+ if (progress_at_idle)
+ {
+ g_signal_emit (info,
+ signals[PROGRESS_CHANGED],
+ 0);
+ }
+
+ if (finish_at_idle)
+ {
+ g_signal_emit (info,
+ signals[FINISHED],
+ 0);
+ }
+
+ if (cancelled_at_idle)
+ {
+ g_signal_emit (info,
+ signals[CANCELLED],
+ 0);
+ }
+
+ g_object_unref (info);
+
+ return FALSE;
+}
+
+
+/* Called with lock held */
+static void
+queue_idle (NautilusProgressInfo *info,
+ gboolean now)
+{
+ if (info->idle_source == NULL ||
+ (now && !info->source_is_now))
+ {
+ if (info->idle_source)
+ {
+ g_source_destroy (info->idle_source);
+ g_source_unref (info->idle_source);
+ info->idle_source = NULL;
+ }
+
+ info->source_is_now = now;
+ if (now)
+ {
+ info->idle_source = g_idle_source_new ();
+ }
+ else
+ {
+ info->idle_source = g_timeout_source_new (SIGNAL_DELAY_MSEC);
+ }
+ g_source_set_callback (info->idle_source, idle_callback, info, NULL);
+ g_source_attach (info->idle_source, NULL);
+ }
+}
+
+static void
+on_canceled (GCancellable *cancellable,
+ NautilusProgressInfo *info)
+{
+ G_LOCK (progress_info);
+ set_details (info, _("Canceled"));
+ info->cancel_at_idle = TRUE;
+ g_timer_stop (info->progress_timer);
+ queue_idle (info, TRUE);
+ G_UNLOCK (progress_info);
+}
+
+static void
+nautilus_progress_info_init (NautilusProgressInfo *info)
+{
+ NautilusProgressInfoManager *manager;
+
+ info->cancellable = g_cancellable_new ();
+ info->cancellable_id = g_cancellable_connect (info->cancellable,
+ G_CALLBACK (on_canceled),
+ info,
+ NULL);
+
+ manager = nautilus_progress_info_manager_dup_singleton ();
+ nautilus_progress_info_manager_add_new_info (manager, info);
+ g_object_unref (manager);
+ info->progress_timer = g_timer_new ();
+}
+
+NautilusProgressInfo *
+nautilus_progress_info_new (void)
+{
+ NautilusProgressInfo *info;
+
+ info = g_object_new (NAUTILUS_TYPE_PROGRESS_INFO, NULL);
+
+ return info;
+}
+
+char *
+nautilus_progress_info_get_status (NautilusProgressInfo *info)
+{
+ char *res;
+
+ G_LOCK (progress_info);
+
+ if (info->status)
+ {
+ res = g_strdup (info->status);
+ }
+ else
+ {
+ res = g_strdup (_("Preparing"));
+ }
+
+ G_UNLOCK (progress_info);
+
+ return res;
+}
+
+char *
+nautilus_progress_info_get_details (NautilusProgressInfo *info)
+{
+ char *res;
+
+ G_LOCK (progress_info);
+
+ if (info->details)
+ {
+ res = g_strdup (info->details);
+ }
+ else
+ {
+ res = g_strdup (_("Preparing"));
+ }
+
+ G_UNLOCK (progress_info);
+
+ return res;
+}
+
+double
+nautilus_progress_info_get_progress (NautilusProgressInfo *info)
+{
+ double res;
+
+ G_LOCK (progress_info);
+
+ if (info->activity_mode)
+ {
+ res = -1.0;
+ }
+ else
+ {
+ res = info->progress;
+ }
+
+ G_UNLOCK (progress_info);
+
+ return res;
+}
+
+void
+nautilus_progress_info_cancel (NautilusProgressInfo *info)
+{
+ GCancellable *cancellable;
+
+ cancellable = nautilus_progress_info_get_cancellable (info);
+ g_cancellable_cancel (cancellable);
+ g_object_unref (cancellable);
+}
+
+GCancellable *
+nautilus_progress_info_get_cancellable (NautilusProgressInfo *info)
+{
+ GCancellable *c;
+
+ G_LOCK (progress_info);
+
+ c = g_object_ref (info->cancellable);
+
+ G_UNLOCK (progress_info);
+
+ return c;
+}
+
+gboolean
+nautilus_progress_info_get_is_cancelled (NautilusProgressInfo *info)
+{
+ gboolean cancelled;
+
+ G_LOCK (progress_info);
+ cancelled = g_cancellable_is_cancelled (info->cancellable);
+ G_UNLOCK (progress_info);
+
+ return cancelled;
+}
+
+gboolean
+nautilus_progress_info_get_is_started (NautilusProgressInfo *info)
+{
+ gboolean res;
+
+ G_LOCK (progress_info);
+
+ res = info->started;
+
+ G_UNLOCK (progress_info);
+
+ return res;
+}
+
+gboolean
+nautilus_progress_info_get_is_finished (NautilusProgressInfo *info)
+{
+ gboolean res;
+
+ G_LOCK (progress_info);
+
+ res = info->finished;
+
+ G_UNLOCK (progress_info);
+
+ return res;
+}
+
+gboolean
+nautilus_progress_info_get_is_paused (NautilusProgressInfo *info)
+{
+ gboolean res;
+
+ G_LOCK (progress_info);
+
+ res = info->paused;
+
+ G_UNLOCK (progress_info);
+
+ return res;
+}
+
+void
+nautilus_progress_info_pause (NautilusProgressInfo *info)
+{
+ G_LOCK (progress_info);
+
+ if (!info->paused)
+ {
+ info->paused = TRUE;
+ g_timer_stop (info->progress_timer);
+ }
+
+ G_UNLOCK (progress_info);
+}
+
+void
+nautilus_progress_info_resume (NautilusProgressInfo *info)
+{
+ G_LOCK (progress_info);
+
+ if (info->paused)
+ {
+ info->paused = FALSE;
+ g_timer_continue (info->progress_timer);
+ }
+
+ G_UNLOCK (progress_info);
+}
+
+void
+nautilus_progress_info_start (NautilusProgressInfo *info)
+{
+ G_LOCK (progress_info);
+
+ if (!info->started)
+ {
+ info->started = TRUE;
+ g_timer_start (info->progress_timer);
+
+ info->start_at_idle = TRUE;
+ queue_idle (info, TRUE);
+ }
+
+ G_UNLOCK (progress_info);
+}
+
+void
+nautilus_progress_info_finish (NautilusProgressInfo *info)
+{
+ G_LOCK (progress_info);
+
+ if (!info->finished)
+ {
+ info->finished = TRUE;
+ g_timer_stop (info->progress_timer);
+
+ info->finish_at_idle = TRUE;
+ queue_idle (info, TRUE);
+ }
+
+ G_UNLOCK (progress_info);
+}
+
+static void
+set_status (NautilusProgressInfo *info,
+ const char *status)
+{
+ g_free (info->status);
+ info->status = g_strdup (status);
+
+ info->changed_at_idle = TRUE;
+ queue_idle (info, FALSE);
+}
+
+void
+nautilus_progress_info_take_status (NautilusProgressInfo *info,
+ char *status)
+{
+ G_LOCK (progress_info);
+
+ if (g_strcmp0 (info->status, status) != 0 &&
+ !g_cancellable_is_cancelled (info->cancellable))
+ {
+ set_status (info, status);
+ }
+
+ G_UNLOCK (progress_info);
+
+ g_free (status);
+}
+
+void
+nautilus_progress_info_set_status (NautilusProgressInfo *info,
+ const char *status)
+{
+ G_LOCK (progress_info);
+
+ if (g_strcmp0 (info->status, status) != 0 &&
+ !g_cancellable_is_cancelled (info->cancellable))
+ {
+ set_status (info, status);
+ }
+
+ G_UNLOCK (progress_info);
+}
+
+static void
+set_details (NautilusProgressInfo *info,
+ const char *details)
+{
+ g_free (info->details);
+ info->details = g_strdup (details);
+
+ info->changed_at_idle = TRUE;
+ queue_idle (info, FALSE);
+}
+
+void
+nautilus_progress_info_take_details (NautilusProgressInfo *info,
+ char *details)
+{
+ G_LOCK (progress_info);
+
+ if (g_strcmp0 (info->details, details) != 0 &&
+ !g_cancellable_is_cancelled (info->cancellable))
+ {
+ set_details (info, details);
+ }
+
+ G_UNLOCK (progress_info);
+
+ g_free (details);
+}
+
+void
+nautilus_progress_info_set_details (NautilusProgressInfo *info,
+ const char *details)
+{
+ G_LOCK (progress_info);
+
+ if (g_strcmp0 (info->details, details) != 0 &&
+ !g_cancellable_is_cancelled (info->cancellable))
+ {
+ set_details (info, details);
+ }
+
+ G_UNLOCK (progress_info);
+}
+
+void
+nautilus_progress_info_pulse_progress (NautilusProgressInfo *info)
+{
+ G_LOCK (progress_info);
+
+ info->activity_mode = TRUE;
+ info->progress = 0.0;
+ info->progress_at_idle = TRUE;
+ queue_idle (info, FALSE);
+
+ G_UNLOCK (progress_info);
+}
+
+void
+nautilus_progress_info_set_progress (NautilusProgressInfo *info,
+ double current,
+ double total)
+{
+ double current_percent;
+
+ if (total <= 0)
+ {
+ current_percent = 1.0;
+ }
+ else
+ {
+ current_percent = current / total;
+
+ if (current_percent < 0)
+ {
+ current_percent = 0;
+ }
+
+ if (current_percent > 1.0)
+ {
+ current_percent = 1.0;
+ }
+ }
+
+ G_LOCK (progress_info);
+
+ if ((info->activity_mode || /* emit on switch from activity mode */
+ fabs (current_percent - info->progress) > 0.005) && /* Emit on change of 0.5 percent */
+ !g_cancellable_is_cancelled (info->cancellable))
+ {
+ info->activity_mode = FALSE;
+ info->progress = current_percent;
+ info->progress_at_idle = TRUE;
+ queue_idle (info, FALSE);
+ }
+
+ G_UNLOCK (progress_info);
+}
+
+void
+nautilus_progress_info_set_remaining_time (NautilusProgressInfo *info,
+ gdouble time)
+{
+ G_LOCK (progress_info);
+ info->remaining_time = time;
+ G_UNLOCK (progress_info);
+}
+
+gdouble
+nautilus_progress_info_get_remaining_time (NautilusProgressInfo *info)
+{
+ gint remaining_time;
+
+ G_LOCK (progress_info);
+ remaining_time = info->remaining_time;
+ G_UNLOCK (progress_info);
+
+ return remaining_time;
+}
+
+void
+nautilus_progress_info_set_elapsed_time (NautilusProgressInfo *info,
+ gdouble time)
+{
+ G_LOCK (progress_info);
+ info->elapsed_time = time;
+ G_UNLOCK (progress_info);
+}
+
+gdouble
+nautilus_progress_info_get_elapsed_time (NautilusProgressInfo *info)
+{
+ gint elapsed_time;
+
+ G_LOCK (progress_info);
+ elapsed_time = info->elapsed_time;
+ G_UNLOCK (progress_info);
+
+ return elapsed_time;
+}
+
+gdouble
+nautilus_progress_info_get_total_elapsed_time (NautilusProgressInfo *info)
+{
+ gdouble elapsed_time;
+
+ G_LOCK (progress_info);
+ elapsed_time = g_timer_elapsed (info->progress_timer, NULL);
+ G_UNLOCK (progress_info);
+
+ return elapsed_time;
+}
+
+void
+nautilus_progress_info_set_destination (NautilusProgressInfo *info,
+ GFile *file)
+{
+ G_LOCK (progress_info);
+ g_clear_object (&info->destination);
+ info->destination = g_object_ref (file);
+ G_UNLOCK (progress_info);
+}
+
+GFile *
+nautilus_progress_info_get_destination (NautilusProgressInfo *info)
+{
+ GFile *destination = NULL;
+
+ G_LOCK (progress_info);
+ if (info->destination)
+ {
+ destination = g_object_ref (info->destination);
+ }
+ G_UNLOCK (progress_info);
+
+ return destination;
+}
diff --git a/src/nautilus-progress-info.h b/src/nautilus-progress-info.h
new file mode 100644
index 0000000..8f274b8
--- /dev/null
+++ b/src/nautilus-progress-info.h
@@ -0,0 +1,82 @@
+/*
+ nautilus-progress-info.h: file operation progress info.
+
+ Copyright (C) 2007 Red Hat, 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/>.
+
+ Author: Alexander Larsson <alexl@redhat.com>
+*/
+
+#pragma once
+
+#include <glib-object.h>
+#include <gio/gio.h>
+
+#define NAUTILUS_TYPE_PROGRESS_INFO (nautilus_progress_info_get_type ())
+
+G_DECLARE_FINAL_TYPE (NautilusProgressInfo, nautilus_progress_info, NAUTILUS, PROGRESS_INFO, GObject)
+
+/* Signals:
+ "changed" - status or details changed
+ "progress-changed" - the percentage progress changed (or we pulsed if in activity_mode
+ "started" - emited on job start
+ "finished" - emitted when job is done
+
+ All signals are emitted from idles in main loop.
+ All methods are threadsafe.
+ */
+
+NautilusProgressInfo *nautilus_progress_info_new (void);
+
+GList * nautilus_get_all_progress_info (void);
+
+char * nautilus_progress_info_get_status (NautilusProgressInfo *info);
+char * nautilus_progress_info_get_details (NautilusProgressInfo *info);
+double nautilus_progress_info_get_progress (NautilusProgressInfo *info);
+GCancellable *nautilus_progress_info_get_cancellable (NautilusProgressInfo *info);
+void nautilus_progress_info_cancel (NautilusProgressInfo *info);
+gboolean nautilus_progress_info_get_is_started (NautilusProgressInfo *info);
+gboolean nautilus_progress_info_get_is_finished (NautilusProgressInfo *info);
+gboolean nautilus_progress_info_get_is_paused (NautilusProgressInfo *info);
+gboolean nautilus_progress_info_get_is_cancelled (NautilusProgressInfo *info);
+
+void nautilus_progress_info_start (NautilusProgressInfo *info);
+void nautilus_progress_info_finish (NautilusProgressInfo *info);
+void nautilus_progress_info_pause (NautilusProgressInfo *info);
+void nautilus_progress_info_resume (NautilusProgressInfo *info);
+void nautilus_progress_info_set_status (NautilusProgressInfo *info,
+ const char *status);
+void nautilus_progress_info_take_status (NautilusProgressInfo *info,
+ char *status);
+void nautilus_progress_info_set_details (NautilusProgressInfo *info,
+ const char *details);
+void nautilus_progress_info_take_details (NautilusProgressInfo *info,
+ char *details);
+void nautilus_progress_info_set_progress (NautilusProgressInfo *info,
+ double current,
+ double total);
+void nautilus_progress_info_pulse_progress (NautilusProgressInfo *info);
+
+void nautilus_progress_info_set_remaining_time (NautilusProgressInfo *info,
+ gdouble time);
+gdouble nautilus_progress_info_get_remaining_time (NautilusProgressInfo *info);
+void nautilus_progress_info_set_elapsed_time (NautilusProgressInfo *info,
+ gdouble time);
+gdouble nautilus_progress_info_get_elapsed_time (NautilusProgressInfo *info);
+gdouble nautilus_progress_info_get_total_elapsed_time (NautilusProgressInfo *info);
+
+void nautilus_progress_info_set_destination (NautilusProgressInfo *info,
+ GFile *file);
+GFile *nautilus_progress_info_get_destination (NautilusProgressInfo *info); \ No newline at end of file
diff --git a/src/nautilus-progress-persistence-handler.c b/src/nautilus-progress-persistence-handler.c
new file mode 100644
index 0000000..b10795d
--- /dev/null
+++ b/src/nautilus-progress-persistence-handler.c
@@ -0,0 +1,384 @@
+/*
+ * nautilus-progress-persistence-handler.c: file operation progress notification handler.
+ *
+ * Copyright (C) 2007, 2011, 2015 Red Hat, 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: Alexander Larsson <alexl@redhat.com>
+ * Cosimo Cecchi <cosimoc@redhat.com>
+ * Carlos Soriano <csoriano@gnome.com>
+ *
+ */
+
+#include <config.h>
+
+#include "nautilus-progress-persistence-handler.h"
+
+#include "nautilus-application.h"
+#include "nautilus-progress-info-widget.h"
+
+#include <glib/gi18n.h>
+
+#include "nautilus-progress-info.h"
+#include "nautilus-progress-info-manager.h"
+
+struct _NautilusProgressPersistenceHandler
+{
+ GObject parent_instance;
+
+ NautilusProgressInfoManager *manager;
+
+ NautilusApplication *app;
+ guint active_infos;
+};
+
+G_DEFINE_TYPE (NautilusProgressPersistenceHandler, nautilus_progress_persistence_handler, G_TYPE_OBJECT);
+
+/* Our policy for showing progress notification is the following:
+ * - file operations that end within two seconds do not get notified in any way
+ * - if no file operations are running, and one passes the two seconds
+ * timeout, a window is displayed with the progress
+ * - if the window is closed, we show a resident notification, depending on
+ * the capabilities of the notification daemon running in the session
+ * - if some file operations are running, and another one passes the two seconds
+ * timeout, and the window is showing, we add it to the window directly
+ * - in the same case, but when the window is not showing, we update the resident
+ * notification, changing its message
+ * - when one file operation finishes, if it's not the last one, we only update the
+ * resident notification's message
+ * - in the same case, if it's the last one, we close the resident notification,
+ * and trigger a transient one
+ * - in the same case, but the window was showing, we just hide the window
+ */
+
+static gboolean server_has_persistence (void);
+
+static void
+show_file_transfers (NautilusProgressPersistenceHandler *self)
+{
+ GFile *home;
+
+ home = g_file_new_for_path (g_get_home_dir ());
+ nautilus_application_open_location (self->app, home, NULL, NULL);
+
+ g_object_unref (home);
+}
+
+static void
+action_show_file_transfers (GSimpleAction *action,
+ GVariant *parameter,
+ gpointer user_data)
+{
+ NautilusProgressPersistenceHandler *self;
+
+ self = NAUTILUS_PROGRESS_PERSISTENCE_HANDLER (user_data);
+ show_file_transfers (self);
+}
+
+static GActionEntry progress_persistence_entries[] =
+{
+ { "show-file-transfers", action_show_file_transfers, NULL, NULL, NULL }
+};
+
+static void
+progress_persistence_handler_update_notification (NautilusProgressPersistenceHandler *self)
+{
+ GNotification *notification;
+ gchar *body;
+
+ if (!server_has_persistence ())
+ {
+ return;
+ }
+
+ notification = g_notification_new (_("File Operations"));
+ g_notification_set_default_action (notification, "app.show-file-transfers");
+ g_notification_add_button (notification, _("Show Details"),
+ "app.show-file-transfers");
+
+ body = g_strdup_printf (ngettext ("%'d file operation active",
+ "%'d file operations active",
+ self->active_infos),
+ self->active_infos);
+ g_notification_set_body (notification, body);
+
+ nautilus_application_send_notification (self->app,
+ "progress", notification);
+
+ g_object_unref (notification);
+ g_free (body);
+}
+
+void
+nautilus_progress_persistence_handler_make_persistent (NautilusProgressPersistenceHandler *self)
+{
+ GList *windows;
+
+ windows = nautilus_application_get_windows (self->app);
+ if (self->active_infos > 0 && windows == NULL)
+ {
+ progress_persistence_handler_update_notification (self);
+ }
+}
+
+static void
+progress_persistence_handler_show_complete_notification (NautilusProgressPersistenceHandler *self)
+{
+ GNotification *complete_notification;
+
+ if (!server_has_persistence ())
+ {
+ return;
+ }
+
+ complete_notification = g_notification_new (_("File Operations"));
+ g_notification_set_body (complete_notification,
+ _("All file operations have been completed"));
+ nautilus_application_send_notification (self->app,
+ "transfer-complete",
+ complete_notification);
+
+ g_object_unref (complete_notification);
+}
+
+static void
+progress_persistence_handler_hide_notification (NautilusProgressPersistenceHandler *self)
+{
+ if (!server_has_persistence ())
+ {
+ return;
+ }
+
+ nautilus_application_withdraw_notification (self->app,
+ "progress");
+}
+
+static void
+progress_info_finished_cb (NautilusProgressInfo *info,
+ NautilusProgressPersistenceHandler *self)
+{
+ GtkWindow *last_active_window;
+
+ self->active_infos--;
+
+ last_active_window = gtk_application_get_active_window (GTK_APPLICATION (self->app));
+
+ if (self->active_infos > 0)
+ {
+ if (last_active_window == NULL)
+ {
+ progress_persistence_handler_update_notification (self);
+ }
+ }
+ else
+ {
+ if ((last_active_window == NULL) || !gtk_window_is_active (last_active_window))
+ {
+ progress_persistence_handler_hide_notification (self);
+ progress_persistence_handler_show_complete_notification (self);
+ }
+ }
+}
+
+static void
+handle_new_progress_info (NautilusProgressPersistenceHandler *self,
+ NautilusProgressInfo *info)
+{
+ GList *windows;
+ g_signal_connect (info, "finished",
+ G_CALLBACK (progress_info_finished_cb), self);
+
+ self->active_infos++;
+ windows = nautilus_application_get_windows (self->app);
+
+ if (windows == NULL)
+ {
+ progress_persistence_handler_update_notification (self);
+ }
+}
+
+typedef struct
+{
+ NautilusProgressInfo *info;
+ NautilusProgressPersistenceHandler *self;
+} TimeoutData;
+
+static void
+timeout_data_free (TimeoutData *data)
+{
+ g_clear_object (&data->self);
+ g_clear_object (&data->info);
+
+ g_slice_free (TimeoutData, data);
+}
+
+static TimeoutData *
+timeout_data_new (NautilusProgressPersistenceHandler *self,
+ NautilusProgressInfo *info)
+{
+ TimeoutData *retval;
+
+ retval = g_slice_new0 (TimeoutData);
+ retval->self = g_object_ref (self);
+ retval->info = g_object_ref (info);
+
+ return retval;
+}
+
+static gboolean
+new_op_started_timeout (TimeoutData *data)
+{
+ NautilusProgressInfo *info = data->info;
+ NautilusProgressPersistenceHandler *self = data->self;
+
+ if (nautilus_progress_info_get_is_paused (info))
+ {
+ return TRUE;
+ }
+
+ if (!nautilus_progress_info_get_is_finished (info))
+ {
+ handle_new_progress_info (self, info);
+ }
+
+ timeout_data_free (data);
+
+ return FALSE;
+}
+
+static void
+release_application (NautilusProgressInfo *info,
+ NautilusProgressPersistenceHandler *self)
+{
+ /* release the GApplication hold we acquired */
+ g_application_release (g_application_get_default ());
+}
+
+static void
+progress_info_started_cb (NautilusProgressInfo *info,
+ NautilusProgressPersistenceHandler *self)
+{
+ TimeoutData *data;
+
+ /* hold GApplication so we never quit while there's an operation pending */
+ g_application_hold (g_application_get_default ());
+
+ g_signal_connect (info, "finished",
+ G_CALLBACK (release_application), self);
+
+ data = timeout_data_new (self, info);
+
+ /* timeout for the progress window to appear */
+ g_timeout_add_seconds (2,
+ (GSourceFunc) new_op_started_timeout,
+ data);
+}
+
+static void
+new_progress_info_cb (NautilusProgressInfoManager *manager,
+ NautilusProgressInfo *info,
+ NautilusProgressPersistenceHandler *self)
+{
+ g_signal_connect (info, "started",
+ G_CALLBACK (progress_info_started_cb), self);
+}
+
+static void
+nautilus_progress_persistence_handler_dispose (GObject *obj)
+{
+ NautilusProgressPersistenceHandler *self = NAUTILUS_PROGRESS_PERSISTENCE_HANDLER (obj);
+
+ g_clear_object (&self->manager);
+
+ G_OBJECT_CLASS (nautilus_progress_persistence_handler_parent_class)->dispose (obj);
+}
+
+static gboolean
+server_has_persistence (void)
+{
+ static gboolean retval = FALSE;
+ GDBusConnection *conn;
+ GVariant *result;
+ char **cap, **caps;
+ static gboolean initialized = FALSE;
+
+ if (initialized)
+ {
+ return retval;
+ }
+ initialized = TRUE;
+
+ conn = g_application_get_dbus_connection (g_application_get_default ());
+ result = g_dbus_connection_call_sync (conn,
+ "org.freedesktop.Notifications",
+ "/org/freedesktop/Notifications",
+ "org.freedesktop.Notifications",
+ "GetCapabilities",
+ g_variant_new ("()"),
+ G_VARIANT_TYPE ("(as)"),
+ G_DBUS_CALL_FLAGS_NONE,
+ -1, NULL, NULL);
+
+ if (result == NULL)
+ {
+ return FALSE;
+ }
+
+ g_variant_get (result, "(^a&s)", &caps);
+
+ for (cap = caps; *cap != NULL; cap++)
+ {
+ if (g_strcmp0 ("persistence", *cap) == 0)
+ {
+ retval = TRUE;
+ }
+ }
+
+ g_free (caps);
+ g_variant_unref (result);
+
+ return retval;
+}
+
+static void
+nautilus_progress_persistence_handler_init (NautilusProgressPersistenceHandler *self)
+{
+ self->manager = nautilus_progress_info_manager_dup_singleton ();
+ g_signal_connect (self->manager, "new-progress-info",
+ G_CALLBACK (new_progress_info_cb), self);
+}
+
+static void
+nautilus_progress_persistence_handler_class_init (NautilusProgressPersistenceHandlerClass *klass)
+{
+ GObjectClass *oclass;
+
+ oclass = G_OBJECT_CLASS (klass);
+ oclass->dispose = nautilus_progress_persistence_handler_dispose;
+}
+
+NautilusProgressPersistenceHandler *
+nautilus_progress_persistence_handler_new (GObject *app)
+{
+ NautilusProgressPersistenceHandler *self;
+
+ self = g_object_new (NAUTILUS_TYPE_PROGRESS_PERSISTENCE_HANDLER, NULL);
+ self->app = NAUTILUS_APPLICATION (app);
+
+ g_action_map_add_action_entries (G_ACTION_MAP (self->app),
+ progress_persistence_entries, G_N_ELEMENTS (progress_persistence_entries),
+ self);
+ return self;
+}
diff --git a/src/nautilus-progress-persistence-handler.h b/src/nautilus-progress-persistence-handler.h
new file mode 100644
index 0000000..3fce9df
--- /dev/null
+++ b/src/nautilus-progress-persistence-handler.h
@@ -0,0 +1,37 @@
+/*
+ * nautilus-progress-persistence-handler.h: file operation progress systray icon or notification handler.
+ *
+ * Copyright (C) 2007, 2011 Red Hat, 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: Alexander Larsson <alexl@redhat.com>
+ * Cosimo Cecchi <cosimoc@redhat.com>
+ *
+ */
+
+#pragma once
+
+#include <glib-object.h>
+
+G_BEGIN_DECLS
+
+#define NAUTILUS_TYPE_PROGRESS_PERSISTENCE_HANDLER nautilus_progress_persistence_handler_get_type()
+G_DECLARE_FINAL_TYPE (NautilusProgressPersistenceHandler, nautilus_progress_persistence_handler, NAUTILUS, PROGRESS_PERSISTENCE_HANDLER, GObject)
+
+/* @app is actually a NautilusApplication, but we need to avoid circular dependencies */
+NautilusProgressPersistenceHandler * nautilus_progress_persistence_handler_new (GObject *app);
+void nautilus_progress_persistence_handler_make_persistent (NautilusProgressPersistenceHandler *self);
+
+G_END_DECLS \ No newline at end of file
diff --git a/src/nautilus-properties-window.c b/src/nautilus-properties-window.c
new file mode 100644
index 0000000..32fd42e
--- /dev/null
+++ b/src/nautilus-properties-window.c
@@ -0,0 +1,4457 @@
+/* fm-properties-window.c - window that lets user modify file properties
+ *
+ * Copyright (C) 2000 Eazel, Inc.
+ *
+ * The Gnome Library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * The Gnome Library 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
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with the Gnome Library; see the file COPYING.LIB. If not,
+ * see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Darin Adler <darin@bentspoon.com>
+ */
+
+#include "nautilus-properties-window.h"
+
+#include <cairo.h>
+#include <eel/eel-stock-dialogs.h>
+#include <eel/eel-string.h>
+#include <eel/eel-vfs-extensions.h>
+#include <gtk/gtk.h>
+#include <gdk/gdkkeysyms.h>
+#include <glib/gi18n.h>
+#include <gio/gunixmounts.h>
+#include <nautilus-extension.h>
+#include <string.h>
+#include <sys/stat.h>
+
+#define GNOME_DESKTOP_USE_UNSTABLE_API
+#include <libgnome-desktop/gnome-desktop-thumbnail.h>
+
+#include "nautilus-application.h"
+#include "nautilus-dbus-launcher.h"
+#include "nautilus-enums.h"
+#include "nautilus-error-reporting.h"
+#include "nautilus-file-operations.h"
+#include "nautilus-file-utilities.h"
+#include "nautilus-global-preferences.h"
+#include "nautilus-icon-info.h"
+#include "nautilus-metadata.h"
+#include "nautilus-mime-actions.h"
+#include "nautilus-module.h"
+#include "nautilus-properties-model.h"
+#include "nautilus-properties-item.h"
+#include "nautilus-signaller.h"
+#include "nautilus-tag-manager.h"
+#include "nautilus-ui-utilities.h"
+
+static GHashTable *pending_lists;
+
+typedef struct
+{
+ NautilusFile *file;
+ char *owner;
+ GtkWindow *window;
+ unsigned int timeout;
+ gboolean cancelled;
+} OwnerChange;
+
+typedef struct
+{
+ NautilusFile *file;
+ char *group;
+ GtkWindow *window;
+ unsigned int timeout;
+ gboolean cancelled;
+} GroupChange;
+
+struct _NautilusPropertiesWindow
+{
+ AdwWindow parent_instance;
+
+ GList *original_files;
+ GList *target_files;
+
+ AdwWindowTitle *window_title;
+
+ GtkStack *page_stack;
+
+ /* Basic page */
+
+ GtkStack *icon_stack;
+ GtkWidget *icon_image;
+ GtkWidget *icon_button;
+ GtkWidget *icon_button_image;
+ GtkWidget *icon_chooser;
+
+ GtkWidget *star_button;
+
+ GtkLabel *name_value_label;
+ GtkWidget *type_value_label;
+ GtkLabel *type_file_system_label;
+ GtkWidget *size_value_label;
+ GtkWidget *contents_box;
+ GtkWidget *contents_value_label;
+ GtkWidget *free_space_value_label;
+
+ GtkWidget *disk_list_box;
+ GtkLevelBar *disk_space_level_bar;
+ GtkWidget *disk_space_used_value;
+ GtkWidget *disk_space_free_value;
+ GtkWidget *disk_space_capacity_value;
+
+ GtkWidget *locations_list_box;
+ GtkWidget *link_target_row;
+ GtkWidget *link_target_value_label;
+ GtkWidget *contents_spinner;
+ guint update_directory_contents_timeout_id;
+ guint update_files_timeout_id;
+ GtkWidget *parent_folder_row;
+ GtkWidget *parent_folder_value_label;
+
+ GtkWidget *trashed_list_box;
+ GtkWidget *trashed_on_value_label;
+ GtkWidget *original_folder_value_label;
+
+ GtkWidget *times_list_box;
+ GtkWidget *modified_row;
+ GtkWidget *modified_value_label;
+ GtkWidget *created_row;
+ GtkWidget *created_value_label;
+ GtkWidget *accessed_row;
+ GtkWidget *accessed_value_label;
+
+ GtkWidget *permissions_navigation_row;
+ GtkWidget *permissions_value_label;
+
+ GtkWidget *extension_models_list_box;
+
+ /* Permissions page */
+
+ GtkWidget *permissions_stack;
+
+ GtkWidget *unknown_permissions_page;
+
+ GtkWidget *bottom_prompt_seperator;
+ GtkWidget *not_the_owner_label;
+
+ AdwComboRow *owner_row;
+ AdwComboRow *owner_access_row;
+ AdwComboRow *owner_folder_access_row;
+ AdwComboRow *owner_file_access_row;
+
+ AdwComboRow *group_row;
+ AdwComboRow *group_access_row;
+ AdwComboRow *group_folder_access_row;
+ AdwComboRow *group_file_access_row;
+
+ AdwComboRow *others_access_row;
+ AdwComboRow *others_folder_access_row;
+ AdwComboRow *others_file_access_row;
+
+ AdwComboRow *execution_row;
+ GtkSwitch *execution_switch;
+
+ GtkWidget *security_context_list_box;
+ GtkWidget *security_context_value_label;
+
+ GtkWidget *change_permissions_button_box;
+ GtkWidget *change_permissions_button;
+
+ GroupChange *group_change;
+ OwnerChange *owner_change;
+
+ GList *permission_rows;
+ GList *change_permission_combos;
+ GHashTable *initial_permissions;
+ gboolean has_recursive_apply;
+
+ GList *value_fields;
+
+ GList *mime_list;
+
+ gboolean deep_count_finished;
+ GList *deep_count_files;
+ guint deep_count_spinner_timeout_id;
+
+ guint long_operation_underway;
+
+ GList *changed_files;
+
+ guint64 volume_capacity;
+ guint64 volume_free;
+ guint64 volume_used;
+};
+
+typedef enum
+{
+ NO_FILES_OR_FOLDERS = (0),
+ FILES_ONLY = (1 << 0),
+ FOLDERS_ONLY = (1 << 1),
+ FILES_AND_FOLDERS = FILES_ONLY | FOLDERS_ONLY,
+} FilterType;
+
+enum
+{
+ UNIX_PERM_SUID = S_ISUID,
+ UNIX_PERM_SGID = S_ISGID,
+ UNIX_PERM_STICKY = 01000, /* S_ISVTX not defined on all systems */
+ UNIX_PERM_USER_READ = S_IRUSR,
+ UNIX_PERM_USER_WRITE = S_IWUSR,
+ UNIX_PERM_USER_EXEC = S_IXUSR,
+ UNIX_PERM_USER_ALL = S_IRUSR | S_IWUSR | S_IXUSR,
+ UNIX_PERM_GROUP_READ = S_IRGRP,
+ UNIX_PERM_GROUP_WRITE = S_IWGRP,
+ UNIX_PERM_GROUP_EXEC = S_IXGRP,
+ UNIX_PERM_GROUP_ALL = S_IRGRP | S_IWGRP | S_IXGRP,
+ UNIX_PERM_OTHER_READ = S_IROTH,
+ UNIX_PERM_OTHER_WRITE = S_IWOTH,
+ UNIX_PERM_OTHER_EXEC = S_IXOTH,
+ UNIX_PERM_OTHER_ALL = S_IROTH | S_IWOTH | S_IXOTH
+};
+
+typedef enum
+{
+ PERMISSION_NONE = (0),
+ PERMISSION_READ = (1 << 0),
+ PERMISSION_WRITE = (1 << 1),
+ PERMISSION_EXEC = (1 << 2),
+ PERMISSION_INCONSISTENT = (1 << 3)
+} PermissionValue;
+
+typedef enum
+{
+ PERMISSION_USER,
+ PERMISSION_GROUP,
+ PERMISSION_OTHER,
+ NUM_PERMISSION_TYPE
+} PermissionType;
+
+/** Contains permissions for files and folders for each PermissionType */
+typedef struct
+{
+ NautilusPropertiesWindow *window;
+
+ PermissionValue folder_permissions[NUM_PERMISSION_TYPE];
+ PermissionValue file_permissions[NUM_PERMISSION_TYPE];
+ PermissionValue file_exec_permissions;
+ gboolean has_files;
+ gboolean has_folders;
+ gboolean can_set_all_folder_permission;
+ gboolean can_set_all_file_permission;
+ gboolean can_set_any_file_permission;
+ gboolean is_multi_file_window;
+} TargetPermissions;
+
+/* NautilusPermissionEntry - helper struct for permission AdwComboRow */
+
+#define NAUTILUS_TYPE_PERMISSION_ENTRY (nautilus_permission_entry_get_type ())
+G_DECLARE_FINAL_TYPE (NautilusPermissionEntry, nautilus_permission_entry,
+ NAUTILUS, PERMISSION_ENTRY, GObject)
+
+enum
+{
+ PROP_NAME = 1,
+ NUM_PROPERTIES
+};
+
+struct _NautilusPermissionEntry
+{
+ GObject parent;
+
+ char *name;
+ PermissionValue permission_value;
+};
+
+G_DEFINE_TYPE (NautilusPermissionEntry,
+ nautilus_permission_entry,
+ G_TYPE_OBJECT)
+
+static void
+nautilus_permission_entry_init (NautilusPermissionEntry *self)
+{
+ self->name = NULL;
+ self->permission_value = PERMISSION_NONE;
+}
+
+static void
+nautilus_permission_entry_finalize (GObject *object)
+{
+ NautilusPermissionEntry *self = NAUTILUS_PERMISSION_ENTRY (object);
+
+ g_free (self->name);
+
+ G_OBJECT_CLASS (nautilus_permission_entry_parent_class)->finalize (object);
+}
+
+static void
+nautilus_permission_entry_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ NautilusPermissionEntry *self = NAUTILUS_PERMISSION_ENTRY (object);
+
+ switch (prop_id)
+ {
+ case PROP_NAME:
+ {
+ g_value_set_string (value, self->name);
+ }
+ break;
+
+ default:
+ {
+ g_assert_not_reached ();
+ }
+ break;
+ }
+}
+
+static void
+nautilus_permission_entry_class_init (NautilusPermissionEntryClass *klass)
+{
+ GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+
+ gobject_class->finalize = nautilus_permission_entry_finalize;
+ gobject_class->get_property = nautilus_permission_entry_get_property;
+
+ g_object_class_install_property (gobject_class,
+ PROP_NAME,
+ g_param_spec_string ("name", "", "",
+ NULL,
+ G_PARAM_READABLE));
+}
+
+static gchar *
+permission_value_to_string (PermissionValue permission_value,
+ gboolean describes_folder)
+{
+ if (permission_value & PERMISSION_INCONSISTENT)
+ {
+ return "---";
+ }
+ else if (permission_value & PERMISSION_READ)
+ {
+ if (permission_value & PERMISSION_WRITE)
+ {
+ if (!describes_folder)
+ {
+ return _("Read and write");
+ }
+ else if (permission_value & PERMISSION_EXEC)
+ {
+ return _("Create and delete files");
+ }
+ else
+ {
+ return _("Read/write, no access");
+ }
+ }
+ else
+ {
+ if (!describes_folder)
+ {
+ return _("Read-only");
+ }
+ else if (permission_value & PERMISSION_EXEC)
+ {
+ return _("Access files");
+ }
+ else
+ {
+ return _("List files only");
+ }
+ }
+ }
+ else
+ {
+ if (permission_value & PERMISSION_WRITE)
+ {
+ if (!describes_folder || permission_value & PERMISSION_EXEC)
+ {
+ return _("Write-only");
+ }
+ else
+ {
+ return _("Write-only, no access");
+ }
+ }
+ else
+ {
+ if (describes_folder && permission_value & PERMISSION_EXEC)
+ {
+ return _("Access-only");
+ }
+ else
+ {
+ /* Translators: this is referred to the permissions the user has in a directory. */
+ return _("None");
+ }
+ }
+ }
+}
+
+/* end NautilusPermissionEntry */
+
+enum
+{
+ COLUMN_NAME,
+ COLUMN_VALUE,
+ COLUMN_USE_ORIGINAL,
+ COLUMN_ID,
+ NUM_COLUMNS
+};
+
+typedef struct
+{
+ GList *original_files;
+ GList *target_files;
+ GtkWidget *parent_widget;
+ GtkWindow *parent_window;
+ char *startup_id;
+ char *pending_key;
+ GHashTable *pending_files;
+ NautilusPropertiesWindowCallback callback;
+ gpointer callback_data;
+ NautilusPropertiesWindow *window;
+ gboolean cancelled;
+} StartupData;
+
+#define DIRECTORY_CONTENTS_UPDATE_INTERVAL 200 /* milliseconds */
+#define FILES_UPDATE_INTERVAL 200 /* milliseconds */
+
+/*
+ * A timeout before changes through the user/group combo box will be applied.
+ * When quickly changing owner/groups (i.e. by keyboard or scroll wheel),
+ * this ensures that the GUI doesn't end up unresponsive.
+ *
+ * Both combos react on changes by scheduling a new change and unscheduling
+ * or cancelling old pending changes.
+ */
+#define CHOWN_CHGRP_TIMEOUT 300 /* milliseconds */
+
+static void schedule_directory_contents_update (NautilusPropertiesWindow *self);
+static void directory_contents_value_field_update (NautilusPropertiesWindow *self);
+static void file_changed_callback (NautilusFile *file,
+ gpointer user_data);
+static void update_execution_row (GtkWidget *row,
+ TargetPermissions *target_perm);
+static void update_permission_row (AdwComboRow *row,
+ TargetPermissions *target_perm);
+static void value_field_update (GtkLabel *field,
+ NautilusPropertiesWindow *self);
+static void properties_window_update (NautilusPropertiesWindow *self,
+ GList *files);
+static void is_directory_ready_callback (NautilusFile *file,
+ gpointer data);
+static void cancel_group_change_callback (GroupChange *change);
+static void cancel_owner_change_callback (OwnerChange *change);
+static void update_owner_row (AdwComboRow *row,
+ TargetPermissions *target_perm);
+static void update_group_row (AdwComboRow *row,
+ TargetPermissions *target_perm);
+static void select_image_button_callback (GtkWidget *widget,
+ NautilusPropertiesWindow *self);
+static void set_icon (const char *icon_path,
+ NautilusPropertiesWindow *self);
+static void remove_pending (StartupData *data,
+ gboolean cancel_call_when_ready,
+ gboolean cancel_timed_wait);
+static void refresh_extension_model_pages (NautilusPropertiesWindow *self);
+static gboolean is_root_directory (NautilusFile *file);
+
+G_DEFINE_TYPE (NautilusPropertiesWindow, nautilus_properties_window, ADW_TYPE_WINDOW);
+
+static gboolean
+is_multi_file_window (NautilusPropertiesWindow *self)
+{
+ GList *l;
+ int count;
+
+ count = 0;
+
+ for (l = self->original_files; l != NULL; l = l->next)
+ {
+ if (!nautilus_file_is_gone (NAUTILUS_FILE (l->data)))
+ {
+ count++;
+ if (count > 1)
+ {
+ return TRUE;
+ }
+ }
+ }
+
+ return FALSE;
+}
+
+static NautilusFile *
+get_original_file (NautilusPropertiesWindow *self)
+{
+ g_return_val_if_fail (!is_multi_file_window (self), NULL);
+
+ if (self->original_files == NULL)
+ {
+ return NULL;
+ }
+
+ return NAUTILUS_FILE (self->original_files->data);
+}
+
+static NautilusFile *
+get_target_file_for_original_file (NautilusFile *file)
+{
+ NautilusFile *target_file;
+ g_autoptr (GFile) location = NULL;
+ g_autofree char *uri_to_display = NULL;
+
+ uri_to_display = nautilus_file_get_uri (file);
+ location = g_file_new_for_uri (uri_to_display);
+ target_file = nautilus_file_get (location);
+
+ return target_file;
+}
+
+static NautilusFile *
+get_target_file (NautilusPropertiesWindow *self)
+{
+ return NAUTILUS_FILE (self->target_files->data);
+}
+
+static void
+navigate_main_page (NautilusPropertiesWindow *self,
+ GParamSpec *params,
+ GtkWidget *widget)
+{
+ gtk_stack_set_visible_child_name (self->page_stack, "main");
+}
+
+static void
+navigate_permissions_page (NautilusPropertiesWindow *self,
+ GParamSpec *params,
+ GtkWidget *widget)
+{
+ gtk_stack_set_visible_child_name (self->page_stack, "permissions");
+}
+
+static void
+navigate_extension_model_page (NautilusPropertiesWindow *self,
+ GParamSpec *params,
+ AdwPreferencesRow *row)
+{
+ gtk_stack_set_visible_child (self->page_stack,
+ g_object_get_data (G_OBJECT (row),
+ "nautilus-extension-properties-page"));
+}
+
+static void
+get_image_for_properties_window (NautilusPropertiesWindow *self,
+ char **icon_name,
+ GdkPaintable **icon_paintable)
+{
+ g_autoptr (NautilusIconInfo) icon = NULL;
+ GList *l;
+ gint icon_scale;
+
+ icon_scale = gtk_widget_get_scale_factor (GTK_WIDGET (self));
+
+ for (l = self->original_files; l != NULL; l = l->next)
+ {
+ NautilusFile *file;
+ g_autoptr (NautilusIconInfo) new_icon = NULL;
+
+ file = NAUTILUS_FILE (l->data);
+
+ if (!icon)
+ {
+ icon = nautilus_file_get_icon (file, NAUTILUS_GRID_ICON_SIZE_MEDIUM, icon_scale,
+ NAUTILUS_FILE_ICON_FLAGS_USE_THUMBNAILS |
+ NAUTILUS_FILE_ICON_FLAGS_USE_MOUNT_ICON);
+ }
+ else
+ {
+ new_icon = nautilus_file_get_icon (file, NAUTILUS_GRID_ICON_SIZE_MEDIUM, icon_scale,
+ NAUTILUS_FILE_ICON_FLAGS_USE_THUMBNAILS |
+ NAUTILUS_FILE_ICON_FLAGS_USE_MOUNT_ICON);
+ if (!new_icon || new_icon != icon)
+ {
+ g_object_unref (icon);
+ icon = NULL;
+ break;
+ }
+ }
+ }
+
+ if (!icon)
+ {
+ g_autoptr (GIcon) gicon = g_themed_icon_new ("text-x-generic");
+
+ icon = nautilus_icon_info_lookup (gicon, NAUTILUS_GRID_ICON_SIZE_MEDIUM, icon_scale);
+ }
+
+ if (icon_name != NULL)
+ {
+ *icon_name = g_strdup (nautilus_icon_info_get_used_name (icon));
+ }
+
+ if (icon_paintable != NULL)
+ {
+ *icon_paintable = nautilus_icon_info_get_paintable (icon);
+ }
+}
+
+
+static void
+update_properties_window_icon (NautilusPropertiesWindow *self)
+{
+ g_autoptr (GdkPaintable) paintable = NULL;
+ g_autofree char *name = NULL;
+ gint pixel_size;
+
+ get_image_for_properties_window (self, &name, &paintable);
+
+ if (name != NULL)
+ {
+ gtk_window_set_icon_name (GTK_WINDOW (self), name);
+ }
+
+ pixel_size = MAX (gdk_paintable_get_intrinsic_width (paintable),
+ gdk_paintable_get_intrinsic_width (paintable));
+
+ gtk_image_set_from_paintable (GTK_IMAGE (self->icon_image), paintable);
+ gtk_image_set_from_paintable (GTK_IMAGE (self->icon_button_image), paintable);
+ gtk_image_set_pixel_size (GTK_IMAGE (self->icon_image), pixel_size);
+ gtk_image_set_pixel_size (GTK_IMAGE (self->icon_button_image), pixel_size);
+}
+
+/* utility to test if a uri refers to a local image */
+static gboolean
+uri_is_local_image (const char *uri)
+{
+ g_autoptr (GdkPixbuf) pixbuf = NULL;
+ g_autofree char *image_path = NULL;
+
+ image_path = g_filename_from_uri (uri, NULL, NULL);
+ if (image_path == NULL)
+ {
+ return FALSE;
+ }
+
+ pixbuf = gdk_pixbuf_new_from_file (image_path, NULL);
+
+ if (pixbuf == NULL)
+ {
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static void
+reset_icon (NautilusPropertiesWindow *self)
+{
+ GList *l;
+
+ for (l = self->original_files; l != NULL; l = l->next)
+ {
+ NautilusFile *file;
+
+ file = NAUTILUS_FILE (l->data);
+
+ nautilus_file_set_metadata (file,
+ NAUTILUS_METADATA_KEY_CUSTOM_ICON,
+ NULL, NULL);
+ }
+}
+
+static void
+nautilus_properties_window_drag_drop_cb (GtkDropTarget *target,
+ const GValue *value,
+ gdouble x,
+ gdouble y,
+ gpointer user_data)
+{
+ GSList *file_list;
+ gboolean exactly_one;
+ GtkImage *image;
+ GtkWindow *window;
+
+ image = GTK_IMAGE (user_data);
+ window = GTK_WINDOW (gtk_widget_get_root (GTK_WIDGET (image)));
+
+ if (!G_VALUE_HOLDS (value, GDK_TYPE_FILE_LIST))
+ {
+ return;
+ }
+
+ file_list = g_value_get_boxed (value);
+ exactly_one = file_list != NULL && g_slist_next (file_list) == NULL;
+
+ if (!exactly_one)
+ {
+ show_dialog (_("You cannot assign more than one custom icon at a time!"),
+ _("Please drop just one image to set a custom icon."),
+ window,
+ GTK_MESSAGE_ERROR);
+ }
+ else
+ {
+ g_autofree gchar *uri = g_file_get_uri (file_list->data);
+
+ if (uri_is_local_image (uri))
+ {
+ set_icon (uri, NAUTILUS_PROPERTIES_WINDOW (window));
+ }
+ else
+ {
+ if (!g_file_is_native (file_list->data))
+ {
+ show_dialog (_("The file that you dropped is not local."),
+ _("You can only use local images as custom icons."),
+ window,
+ GTK_MESSAGE_ERROR);
+ }
+ else
+ {
+ show_dialog (_("The file that you dropped is not an image."),
+ _("You can only use local images as custom icons."),
+ window,
+ GTK_MESSAGE_ERROR);
+ }
+ }
+ }
+}
+
+static void
+star_clicked (NautilusPropertiesWindow *self)
+{
+ NautilusTagManager *tag_manager = nautilus_tag_manager_get ();
+ NautilusFile *file = get_original_file (self);
+ g_autofree gchar *uri = nautilus_file_get_uri (file);
+
+ if (nautilus_tag_manager_file_is_starred (tag_manager, uri))
+ {
+ nautilus_tag_manager_unstar_files (tag_manager, G_OBJECT (self),
+ &(GList){ file, NULL }, NULL, NULL);
+ }
+ else
+ {
+ nautilus_tag_manager_star_files (tag_manager, G_OBJECT (self),
+ &(GList){ file, NULL }, NULL, NULL);
+ }
+}
+
+static void
+update_star (NautilusPropertiesWindow *self,
+ NautilusTagManager *tag_manager)
+{
+ gboolean is_starred;
+ g_autofree gchar *file_uri = NULL;
+
+ file_uri = nautilus_file_get_uri (get_target_file (self));
+ is_starred = nautilus_tag_manager_file_is_starred (tag_manager, file_uri);
+
+ gtk_button_set_icon_name (GTK_BUTTON (self->star_button),
+ is_starred ? "starred-symbolic" : "non-starred-symbolic");
+ /* Translators: This is a verb for tagging or untagging a file with a star. */
+ gtk_widget_set_tooltip_text (self->star_button, is_starred ? _("Unstar") : _("Star"));
+}
+
+static void
+on_starred_changed (NautilusTagManager *tag_manager,
+ GList *changed_files,
+ gpointer user_data)
+{
+ NautilusPropertiesWindow *self = user_data;
+ NautilusFile *file = get_target_file (self);
+
+ if (g_list_find (changed_files, file))
+ {
+ update_star (self, tag_manager);
+ }
+}
+
+static void
+setup_star_button (NautilusPropertiesWindow *self)
+{
+ NautilusTagManager *tag_manager = nautilus_tag_manager_get ();
+ NautilusFile *file = get_target_file (self);
+ g_autoptr (GFile) parent_location = nautilus_file_get_parent_location (file);
+
+ if (parent_location == NULL)
+ {
+ return;
+ }
+
+ if (nautilus_tag_manager_can_star_contents (tag_manager, parent_location))
+ {
+ gtk_widget_show (self->star_button);
+ update_star (self, tag_manager);
+ g_signal_connect_object (tag_manager, "starred-changed",
+ G_CALLBACK (on_starred_changed), self, 0);
+ }
+}
+
+static void
+setup_image_widget (NautilusPropertiesWindow *self,
+ gboolean is_customizable)
+{
+ update_properties_window_icon (self);
+
+ if (is_customizable)
+ {
+ GtkDropTarget *target;
+
+ /* prepare the image to receive dropped objects to assign custom images */
+ target = gtk_drop_target_new (GDK_TYPE_FILE_LIST, GDK_ACTION_COPY);
+ gtk_widget_add_controller (self->icon_button, GTK_EVENT_CONTROLLER (target));
+ g_signal_connect (target, "drop",
+ G_CALLBACK (nautilus_properties_window_drag_drop_cb), self->icon_button_image);
+
+ g_signal_connect (self->icon_button, "clicked",
+ G_CALLBACK (select_image_button_callback), self);
+ gtk_stack_set_visible_child (self->icon_stack, self->icon_button);
+ }
+ else
+ {
+ gtk_stack_set_visible_child (self->icon_stack, self->icon_image);
+ }
+}
+
+static void
+update_name_field (NautilusPropertiesWindow *self)
+{
+ g_autoptr (GString) name_str = g_string_new ("");
+ g_autofree gchar *os_name = NULL;
+ gchar *name_value;
+ guint file_counter = 0;
+
+ for (GList *l = self->target_files; l != NULL; l = l->next)
+ {
+ NautilusFile *file = NAUTILUS_FILE (l->data);
+
+ if (!nautilus_file_is_gone (file))
+ {
+ g_autofree gchar *file_name = NULL;
+
+ file_counter += 1;
+ if (file_counter > 1)
+ {
+ g_string_append (name_str, ", ");
+ }
+
+ file_name = nautilus_file_get_display_name (file);
+ g_string_append (name_str, file_name);
+ }
+ }
+
+ if (!is_multi_file_window (self) && is_root_directory (get_original_file (self)))
+ {
+ os_name = g_get_os_info (G_OS_INFO_KEY_NAME);
+ name_value = (os_name != NULL) ? os_name : _("Operating System");
+ }
+ else
+ {
+ name_value = name_str->str;
+ }
+
+ gtk_label_set_text (self->name_value_label, name_value);
+}
+
+/**
+ * Returns the attribute value if all files in file_list have identical
+ * attributes, "unknown" if no files exist and NULL otherwise.
+ */
+static char *
+file_list_get_string_attribute (GList *file_list,
+ const char *attribute_name)
+{
+ g_autofree char *first_attr = NULL;
+
+ for (GList *l = file_list; l != NULL; l = l->next)
+ {
+ NautilusFile *file = NAUTILUS_FILE (l->data);
+
+ if (nautilus_file_is_gone (file))
+ {
+ continue;
+ }
+
+ if (first_attr == NULL)
+ {
+ first_attr = nautilus_file_get_string_attribute_with_default (file, attribute_name);
+ }
+ else
+ {
+ g_autofree char *attr = NULL;
+ attr = nautilus_file_get_string_attribute_with_default (file, attribute_name);
+ if (!g_str_equal (attr, first_attr))
+ {
+ /* Not all files have the same value for attribute_name. */
+ return NULL;
+ }
+ }
+ }
+
+ if (first_attr != NULL)
+ {
+ return g_steal_pointer (&first_attr);
+ }
+ else
+ {
+ return g_strdup (_("unknown"));
+ }
+}
+
+static GtkWidget *
+create_extension_group_row (NautilusPropertiesItem *item,
+ NautilusPropertiesWindow *self)
+{
+ GtkWidget *row = adw_action_row_new ();
+ GtkWidget *box = gtk_box_new (GTK_ORIENTATION_VERTICAL, 3);
+ GtkWidget *name_label = gtk_label_new (NULL);
+ GtkWidget *value_label = gtk_label_new (NULL);
+
+ gtk_list_box_row_set_selectable (GTK_LIST_BOX_ROW (row), FALSE);
+ gtk_list_box_row_set_activatable (GTK_LIST_BOX_ROW (row), FALSE);
+ adw_action_row_add_prefix (ADW_ACTION_ROW (row), box);
+
+ gtk_widget_set_margin_top (box, 7);
+ gtk_widget_set_margin_bottom (box, 7);
+ gtk_box_append (GTK_BOX (box), name_label);
+ gtk_box_append (GTK_BOX (box), value_label);
+
+ g_object_bind_property (item, "name", name_label, "label", G_BINDING_SYNC_CREATE);
+ gtk_widget_add_css_class (name_label, "caption");
+ gtk_widget_add_css_class (name_label, "dim-label");
+ gtk_widget_set_halign (name_label, GTK_ALIGN_START);
+ gtk_label_set_ellipsize (GTK_LABEL (name_label), PANGO_ELLIPSIZE_END);
+
+ g_object_bind_property (item, "value", value_label, "label", G_BINDING_SYNC_CREATE);
+ gtk_widget_set_halign (value_label, GTK_ALIGN_START);
+ gtk_label_set_wrap (GTK_LABEL (value_label), TRUE);
+ gtk_label_set_wrap_mode (GTK_LABEL (value_label), PANGO_WRAP_WORD_CHAR);
+ gtk_label_set_selectable (GTK_LABEL (value_label), TRUE);
+
+ return row;
+}
+
+static GtkWidget *
+add_extension_model_page (NautilusPropertiesModel *model,
+ NautilusPropertiesWindow *self)
+{
+ GListModel *list_model = nautilus_properties_model_get_model (model);
+ GtkWidget *row;
+ GtkWidget *title;
+ GtkWidget *header_bar;
+ GtkWidget *list_box;
+ GtkWidget *clamp;
+ GtkWidget *scrolled_window;
+ GtkWidget *up_button;
+ GtkWidget *box;
+
+ row = adw_action_row_new ();
+ g_object_bind_property (model, "title", row, "title", G_BINDING_SYNC_CREATE);
+ gtk_list_box_row_set_activatable (GTK_LIST_BOX_ROW (row), TRUE);
+ gtk_list_box_row_set_selectable (GTK_LIST_BOX_ROW (row), FALSE);
+ adw_action_row_add_suffix (ADW_ACTION_ROW (row),
+ gtk_image_new_from_icon_name ("go-next-symbolic"));
+ g_signal_connect_swapped (row, "activated",
+ G_CALLBACK (navigate_extension_model_page), self);
+
+ title = adw_window_title_new (NULL, NULL);
+ g_object_bind_property (model, "title", title, "title", G_BINDING_SYNC_CREATE);
+
+ up_button = gtk_button_new_from_icon_name ("go-previous-symbolic");
+ g_signal_connect_swapped (up_button, "clicked", G_CALLBACK (navigate_main_page), self);
+
+ header_bar = gtk_header_bar_new ();
+ gtk_header_bar_set_title_widget (GTK_HEADER_BAR (header_bar), title);
+ gtk_header_bar_pack_start (GTK_HEADER_BAR (header_bar), up_button);
+
+ list_box = gtk_list_box_new ();
+ gtk_widget_add_css_class (list_box, "boxed-list");
+ gtk_widget_set_valign (list_box, GTK_ALIGN_START);
+ gtk_list_box_bind_model (GTK_LIST_BOX (list_box), list_model,
+ (GtkListBoxCreateWidgetFunc) create_extension_group_row,
+ self,
+ NULL);
+
+ clamp = adw_clamp_new ();
+ adw_clamp_set_child (ADW_CLAMP (clamp), list_box);
+ gtk_widget_set_margin_top (clamp, 18);
+ gtk_widget_set_margin_bottom (clamp, 18);
+ gtk_widget_set_margin_start (clamp, 18);
+ gtk_widget_set_margin_end (clamp, 18);
+
+ scrolled_window = gtk_scrolled_window_new ();
+ gtk_scrolled_window_set_child (GTK_SCROLLED_WINDOW (scrolled_window), clamp);
+ gtk_widget_set_vexpand (scrolled_window, TRUE);
+
+ box = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0);
+ gtk_box_append (GTK_BOX (box), header_bar);
+ gtk_box_append (GTK_BOX (box), scrolled_window);
+ gtk_widget_add_css_class (scrolled_window, "background");
+
+ gtk_stack_add_named (self->page_stack,
+ box,
+ NULL);
+
+ g_object_set_data (G_OBJECT (row), "nautilus-extension-properties-page", box);
+
+ return row;
+}
+
+static void
+remove_from_dialog (NautilusPropertiesWindow *self,
+ NautilusFile *file)
+{
+ int index;
+ GList *original_link;
+ GList *target_link;
+ g_autoptr (NautilusFile) original_file = NULL;
+ g_autoptr (NautilusFile) target_file = NULL;
+
+ index = g_list_index (self->target_files, file);
+ if (index == -1)
+ {
+ index = g_list_index (self->original_files, file);
+ g_return_if_fail (index != -1);
+ }
+
+ original_link = g_list_nth (self->original_files, index);
+ target_link = g_list_nth (self->target_files, index);
+
+ g_return_if_fail (original_link && target_link);
+
+ original_file = NAUTILUS_FILE (original_link->data);
+ target_file = NAUTILUS_FILE (target_link->data);
+
+ self->original_files = g_list_delete_link (self->original_files, original_link);
+ self->target_files = g_list_delete_link (self->target_files, target_link);
+
+ g_hash_table_remove (self->initial_permissions, target_file);
+
+ g_signal_handlers_disconnect_by_func (original_file,
+ G_CALLBACK (file_changed_callback),
+ self);
+ g_signal_handlers_disconnect_by_func (target_file,
+ G_CALLBACK (file_changed_callback),
+ self);
+
+ nautilus_file_monitor_remove (original_file, &self->original_files);
+ nautilus_file_monitor_remove (target_file, &self->target_files);
+}
+
+static gboolean
+mime_list_equal (GList *a,
+ GList *b)
+{
+ while (a && b)
+ {
+ if (strcmp (a->data, b->data))
+ {
+ return FALSE;
+ }
+ a = a->next;
+ b = b->next;
+ }
+
+ return (a == b);
+}
+
+static GList *
+get_mime_list (NautilusPropertiesWindow *self)
+{
+ return g_list_copy_deep (self->target_files,
+ (GCopyFunc) nautilus_file_get_mime_type,
+ NULL);
+}
+
+static gboolean
+start_spinner_callback (NautilusPropertiesWindow *self)
+{
+ gtk_widget_show (self->contents_spinner);
+ gtk_spinner_start (GTK_SPINNER (self->contents_spinner));
+ self->deep_count_spinner_timeout_id = 0;
+
+ return FALSE;
+}
+
+static void
+schedule_start_spinner (NautilusPropertiesWindow *self)
+{
+ if (self->deep_count_spinner_timeout_id == 0)
+ {
+ self->deep_count_spinner_timeout_id
+ = g_timeout_add_seconds (1,
+ (GSourceFunc) start_spinner_callback,
+ self);
+ }
+}
+
+static void
+stop_spinner (NautilusPropertiesWindow *self)
+{
+ gtk_spinner_stop (GTK_SPINNER (self->contents_spinner));
+ gtk_widget_hide (self->contents_spinner);
+ g_clear_handle_id (&self->deep_count_spinner_timeout_id, g_source_remove);
+}
+
+static void
+stop_deep_count_for_file (NautilusPropertiesWindow *self,
+ NautilusFile *file)
+{
+ if (g_list_find (self->deep_count_files, file))
+ {
+ g_signal_handlers_disconnect_by_func (file,
+ G_CALLBACK (schedule_directory_contents_update),
+ self);
+ nautilus_file_unref (file);
+ self->deep_count_files = g_list_remove (self->deep_count_files, file);
+ }
+}
+
+static void
+start_deep_count_for_file (NautilusFile *file,
+ NautilusPropertiesWindow *self)
+{
+ if (!nautilus_file_is_directory (file))
+ {
+ return;
+ }
+
+ if (!g_list_find (self->deep_count_files, file))
+ {
+ nautilus_file_ref (file);
+ self->deep_count_files = g_list_prepend (self->deep_count_files, file);
+
+ nautilus_file_recompute_deep_counts (file);
+ if (!self->deep_count_finished)
+ {
+ g_signal_connect_object (file,
+ "updated-deep-count-in-progress",
+ G_CALLBACK (schedule_directory_contents_update),
+ self, G_CONNECT_SWAPPED);
+ schedule_start_spinner (self);
+ }
+ }
+}
+
+static guint32 vfs_perms[3][3] =
+{
+ {UNIX_PERM_USER_READ, UNIX_PERM_USER_WRITE, UNIX_PERM_USER_EXEC},
+ {UNIX_PERM_GROUP_READ, UNIX_PERM_GROUP_WRITE, UNIX_PERM_GROUP_EXEC},
+ {UNIX_PERM_OTHER_READ, UNIX_PERM_OTHER_WRITE, UNIX_PERM_OTHER_EXEC},
+};
+
+static guint32
+permission_to_vfs (PermissionType type,
+ PermissionValue perm)
+{
+ guint32 vfs_perm;
+ g_assert (type >= 0 && type < 3);
+
+ vfs_perm = 0;
+ if (perm & PERMISSION_READ)
+ {
+ vfs_perm |= vfs_perms[type][0];
+ }
+ if (perm & PERMISSION_WRITE)
+ {
+ vfs_perm |= vfs_perms[type][1];
+ }
+ if (perm & PERMISSION_EXEC)
+ {
+ vfs_perm |= vfs_perms[type][2];
+ }
+
+ return vfs_perm;
+}
+
+static PermissionValue
+permission_from_vfs (PermissionType type,
+ guint32 vfs_perm)
+{
+ PermissionValue perm = PERMISSION_NONE;
+
+ g_assert (type >= 0 && type < 3);
+
+ if (vfs_perm & vfs_perms[type][0])
+ {
+ perm |= PERMISSION_READ;
+ }
+ if (vfs_perm & vfs_perms[type][1])
+ {
+ perm |= PERMISSION_WRITE;
+ }
+ if (vfs_perm & vfs_perms[type][2])
+ {
+ perm |= PERMISSION_EXEC;
+ }
+
+ return perm;
+}
+
+static PermissionValue
+exec_permission_from_vfs (guint32 vfs_perm)
+{
+ guint32 perm_user = vfs_perm & UNIX_PERM_USER_EXEC;
+ guint32 perm_group = vfs_perm & UNIX_PERM_GROUP_EXEC;
+ guint32 perm_other = vfs_perm & UNIX_PERM_OTHER_EXEC;
+
+ if (perm_user && perm_group && perm_other)
+ {
+ return PERMISSION_EXEC;
+ }
+ else if (perm_user || perm_group || perm_other)
+ {
+ return PERMISSION_INCONSISTENT;
+ }
+ else
+ {
+ return PERMISSION_NONE;
+ }
+}
+
+static TargetPermissions *
+get_target_permissions (NautilusPropertiesWindow *self)
+{
+ TargetPermissions *p = g_new0 (TargetPermissions, 1);
+ p->window = self;
+ p->can_set_all_folder_permission = TRUE;
+ p->can_set_all_file_permission = TRUE;
+
+ for (GList *entry = self->target_files; entry != NULL; entry = entry->next)
+ {
+ guint32 vfs_permissions;
+ gboolean can_set_permissions;
+ NautilusFile *file = NAUTILUS_FILE (entry->data);
+
+ if (nautilus_file_is_gone (file) || !nautilus_file_can_get_permissions (file))
+ {
+ continue;
+ }
+
+ vfs_permissions = nautilus_file_get_permissions (file);
+ can_set_permissions = nautilus_file_can_set_permissions (file);
+
+ if (nautilus_file_is_directory (file))
+ {
+ /* Gather permissions for each type (owner, group, other) */
+ for (PermissionType type = PERMISSION_USER; type < NUM_PERMISSION_TYPE; type += 1)
+ {
+ PermissionValue permissions = permission_from_vfs (type, vfs_permissions)
+ & (PERMISSION_READ | PERMISSION_WRITE | PERMISSION_EXEC);
+ if (!p->has_folders)
+ {
+ /* first found folder, initialize with its permissions */
+ p->folder_permissions[type] = permissions;
+ }
+ else if (permissions != p->folder_permissions[type])
+ {
+ p->folder_permissions[type] = PERMISSION_INCONSISTENT;
+ }
+ }
+
+ p->can_set_all_folder_permission &= can_set_permissions;
+ p->has_folders = TRUE;
+ }
+ else
+ {
+ PermissionValue exec_permissions = exec_permission_from_vfs (vfs_permissions);
+
+ if (!p->has_files)
+ {
+ p->file_exec_permissions = exec_permissions;
+ }
+ else if (exec_permissions != p->file_exec_permissions)
+ {
+ p->file_exec_permissions = PERMISSION_INCONSISTENT;
+ }
+ for (PermissionType type = PERMISSION_USER ; type < NUM_PERMISSION_TYPE; type += 1)
+ {
+ PermissionValue permissions = permission_from_vfs (type, vfs_permissions)
+ & (PERMISSION_READ | PERMISSION_WRITE);
+
+ if (!p->has_files)
+ {
+ /* first found file, initialize with its permissions */
+ p->file_permissions[type] = permissions;
+ }
+ else if (permissions != p->file_permissions[type])
+ {
+ p->file_permissions[type] = PERMISSION_INCONSISTENT;
+ }
+ }
+
+ p->can_set_all_file_permission &= can_set_permissions;
+ p->can_set_any_file_permission |= can_set_permissions;
+ p->has_files = TRUE;
+ }
+ }
+
+ p->is_multi_file_window = is_multi_file_window (self);
+
+ return p;
+}
+
+static void
+update_permissions_navigation_row (NautilusPropertiesWindow *self,
+ TargetPermissions *target_perm)
+{
+ if (!target_perm->is_multi_file_window)
+ {
+ uid_t user_id = geteuid ();
+ gid_t group_id = getegid ();
+ PermissionType permission_type = PERMISSION_OTHER;
+ const gchar *text;
+
+ if (user_id == nautilus_file_get_uid (get_original_file (self)))
+ {
+ permission_type = PERMISSION_USER;
+ }
+ else if (group_id == nautilus_file_get_gid (get_original_file (self)))
+ {
+ permission_type = PERMISSION_GROUP;
+ }
+
+ if (nautilus_file_is_directory (get_original_file (self)))
+ {
+ text = permission_value_to_string (target_perm->folder_permissions[permission_type], TRUE);
+ }
+ else
+ {
+ text = permission_value_to_string (target_perm->file_permissions[permission_type], FALSE);
+ }
+
+ gtk_label_set_text (GTK_LABEL (self->permissions_value_label), text);
+ }
+}
+
+static void
+properties_window_update (NautilusPropertiesWindow *self,
+ GList *files)
+{
+ GList *mime_list;
+ NautilusFile *changed_file;
+ gboolean dirty_original = FALSE;
+ gboolean dirty_target = FALSE;
+
+ if (files == NULL)
+ {
+ dirty_original = TRUE;
+ dirty_target = TRUE;
+ }
+
+ for (GList *tmp = files; tmp != NULL; tmp = tmp->next)
+ {
+ changed_file = NAUTILUS_FILE (tmp->data);
+
+ if (changed_file && nautilus_file_is_gone (changed_file))
+ {
+ /* Remove the file from the property dialog */
+ remove_from_dialog (self, changed_file);
+ changed_file = NULL;
+
+ if (self->original_files == NULL)
+ {
+ return;
+ }
+ }
+ if (changed_file == NULL ||
+ g_list_find (self->original_files, changed_file))
+ {
+ dirty_original = TRUE;
+ }
+ if (changed_file == NULL ||
+ g_list_find (self->target_files, changed_file))
+ {
+ dirty_target = TRUE;
+ }
+ }
+
+ if (dirty_original)
+ {
+ update_properties_window_icon (self);
+ update_name_field (self);
+
+ /* If any of the value fields start to depend on the original
+ * value, value_field_updates should be added here */
+ }
+
+ if (dirty_target)
+ {
+ g_autofree TargetPermissions *target_perm = get_target_permissions (self);
+
+ update_permissions_navigation_row (self, target_perm);
+ update_owner_row (self->owner_row, target_perm);
+ update_group_row (self->group_row, target_perm);
+ update_execution_row (GTK_WIDGET (self->execution_row), target_perm);
+ g_list_foreach (self->permission_rows,
+ (GFunc) update_permission_row,
+ target_perm);
+ g_list_foreach (self->value_fields,
+ (GFunc) value_field_update,
+ self);
+ }
+
+ mime_list = get_mime_list (self);
+
+ if (self->mime_list == NULL)
+ {
+ self->mime_list = mime_list;
+ }
+ else
+ {
+ if (!mime_list_equal (self->mime_list, mime_list))
+ {
+ refresh_extension_model_pages (self);
+ }
+
+ g_list_free_full (self->mime_list, g_free);
+ self->mime_list = mime_list;
+ }
+}
+
+static gboolean
+update_files_callback (gpointer data)
+{
+ NautilusPropertiesWindow *self;
+
+ self = NAUTILUS_PROPERTIES_WINDOW (data);
+
+ self->update_files_timeout_id = 0;
+
+ properties_window_update (self, self->changed_files);
+
+ if (self->original_files == NULL)
+ {
+ /* Close the window if no files are left */
+ gtk_window_destroy (GTK_WINDOW (self));
+ }
+ else
+ {
+ nautilus_file_list_free (self->changed_files);
+ self->changed_files = NULL;
+ }
+
+ return FALSE;
+}
+
+static void
+schedule_files_update (NautilusPropertiesWindow *self)
+{
+ g_assert (NAUTILUS_IS_PROPERTIES_WINDOW (self));
+
+ if (self->update_files_timeout_id == 0)
+ {
+ self->update_files_timeout_id
+ = g_timeout_add (FILES_UPDATE_INTERVAL,
+ update_files_callback,
+ self);
+ }
+}
+
+static gboolean
+location_show_original (NautilusPropertiesWindow *self)
+{
+ NautilusFile *file;
+
+ /* there is no way a recent item will be mixed with
+ * other items so just pick the first file to check */
+ file = NAUTILUS_FILE (g_list_nth_data (self->original_files, 0));
+ return (file != NULL && !nautilus_file_is_in_recent (file));
+}
+
+static void
+value_field_update (GtkLabel *label,
+ NautilusPropertiesWindow *self)
+{
+ GList *file_list;
+ const char *attribute_name;
+ g_autofree char *attribute_value = NULL;
+ gboolean is_where;
+
+ g_assert (GTK_IS_LABEL (label));
+
+ attribute_name = g_object_get_data (G_OBJECT (label), "file_attribute");
+
+ is_where = (g_strcmp0 (attribute_name, "where") == 0);
+ if (is_where && location_show_original (self))
+ {
+ file_list = self->original_files;
+ }
+ else
+ {
+ file_list = self->target_files;
+ }
+
+ attribute_value = file_list_get_string_attribute (file_list,
+ attribute_name);
+ if (g_str_equal (attribute_name, "detailed_type"))
+ {
+ g_autofree char *mime_type = NULL;
+ gchar *cap_label;
+
+ mime_type = file_list_get_string_attribute (file_list, "mime_type");
+ gtk_widget_set_tooltip_text (GTK_WIDGET (label), mime_type);
+
+ cap_label = eel_str_capitalize (attribute_value);
+ if (cap_label != NULL)
+ {
+ g_free (attribute_value);
+
+ attribute_value = cap_label;
+ }
+ }
+ else if (g_str_equal (attribute_name, "size"))
+ {
+ g_autofree char *size_detail = NULL;
+
+ size_detail = file_list_get_string_attribute (file_list, "size_detail");
+
+ gtk_widget_set_tooltip_text (GTK_WIDGET (label), size_detail);
+ }
+
+ gtk_label_set_text (label, attribute_value);
+}
+
+static guint
+hash_string_list (GList *list)
+{
+ guint hash_value = 0;
+
+ for (GList *node = list; node != NULL; node = node->next)
+ {
+ hash_value ^= g_str_hash ((gconstpointer) node->data);
+ }
+
+ return hash_value;
+}
+
+static gsize
+get_first_word_length (const gchar *str)
+{
+ const gchar *space_pos = g_strstr_len (str, -1, " ");
+ return space_pos ? space_pos - str : strlen (str);
+}
+
+static void
+update_combo_row_dropdown (AdwComboRow *row,
+ GList *entries)
+{
+ /* check if dropdown already exist and is up to date by comparing with stored hash. */
+ guint current_hash = hash_string_list (entries);
+ guint stored_hash = GPOINTER_TO_UINT (g_object_get_data (G_OBJECT (row), "dropdown-hash"));
+
+ if (stored_hash != current_hash)
+ {
+ /* Recreate the drop down. */
+ g_autoptr (GtkStringList) new_model = gtk_string_list_new (NULL);
+
+ for (GList *node = entries; node != NULL; node = node->next)
+ {
+ const char *entry = (const char *) node->data;
+ gtk_string_list_append (new_model, entry);
+ }
+
+ adw_combo_row_set_model (row, G_LIST_MODEL (new_model));
+
+ g_object_set_data (G_OBJECT (row), "dropdown-hash", GUINT_TO_POINTER (current_hash));
+ }
+}
+
+typedef gboolean CompareOwnershipRowFunc (GListModel *list,
+ guint position,
+ const char *str);
+
+static void
+select_ownership_row_entry (AdwComboRow *row,
+ const char *entry,
+ CompareOwnershipRowFunc compare_func)
+{
+ GListModel *list = adw_combo_row_get_model (row);
+ guint index_to_select = GTK_INVALID_LIST_POSITION;
+ guint n_entries;
+
+ /* check if entry is already selected */
+ gint selected_pos = adw_combo_row_get_selected (row);
+
+ if (selected_pos >= 0
+ && compare_func (list, selected_pos, entry))
+ {
+ /* entry already selected */
+ return;
+ }
+
+ /* check if entry exists in model list */
+ n_entries = g_list_model_get_n_items (list);
+
+ for (guint position = 0; position < n_entries; position += 1)
+ {
+ if (compare_func (list, position, entry))
+ {
+ /* found entry in list, select it */
+ index_to_select = position;
+ break;
+ }
+ }
+
+ if (index_to_select == GTK_INVALID_LIST_POSITION)
+ {
+ /* entry not in list, add */
+ gtk_string_list_append (GTK_STRING_LIST (list), entry);
+ index_to_select = n_entries;
+ }
+
+ adw_combo_row_set_selected (row, index_to_select);
+}
+
+static void
+ownership_row_set_single_entry (AdwComboRow *row,
+ const char *entry,
+ CompareOwnershipRowFunc compare_func)
+{
+ GListModel *list = adw_combo_row_get_model (row);
+ gint selected_pos = adw_combo_row_get_selected (row);
+
+ /* check entry not already displayed */
+ if (selected_pos < 0
+ || g_list_model_get_n_items (list) > 1
+ || !compare_func (list, selected_pos, entry))
+ {
+ g_autoptr (GtkStringList) new_model = gtk_string_list_new (NULL);
+
+ /* set current entry as only entry */
+ gtk_string_list_append (new_model, entry);
+
+ adw_combo_row_set_model (row, G_LIST_MODEL (new_model));
+ adw_combo_row_set_selected (row, 0);
+ }
+}
+
+static void
+group_change_free (GroupChange *change)
+{
+ nautilus_file_unref (change->file);
+ g_free (change->group);
+ g_object_unref (change->window);
+
+ g_free (change);
+}
+
+static void
+group_change_callback (NautilusFile *file,
+ GFile *res_loc,
+ GError *error,
+ GroupChange *change)
+{
+ NautilusPropertiesWindow *self;
+
+ g_assert (NAUTILUS_IS_PROPERTIES_WINDOW (change->window));
+ g_assert (NAUTILUS_IS_FILE (change->file));
+ g_assert (change->group != NULL);
+
+ if (!change->cancelled)
+ {
+ /* Report the error if it's an error. */
+ eel_timed_wait_stop ((EelCancelCallback) cancel_group_change_callback, change);
+ nautilus_report_error_setting_group (change->file, error, change->window);
+ }
+
+ self = NAUTILUS_PROPERTIES_WINDOW (change->window);
+ if (self->group_change == change)
+ {
+ self->group_change = NULL;
+ }
+
+ group_change_free (change);
+}
+
+static void
+cancel_group_change_callback (GroupChange *change)
+{
+ g_assert (NAUTILUS_IS_FILE (change->file));
+ g_assert (change->group != NULL);
+
+ change->cancelled = TRUE;
+ nautilus_file_cancel (change->file, (NautilusFileOperationCallback) group_change_callback, change);
+}
+
+static gboolean
+schedule_group_change_timeout (GroupChange *change)
+{
+ g_assert (NAUTILUS_IS_PROPERTIES_WINDOW (change->window));
+ g_assert (NAUTILUS_IS_FILE (change->file));
+ g_assert (change->group != NULL);
+
+ change->timeout = 0;
+
+ eel_timed_wait_start
+ ((EelCancelCallback) cancel_group_change_callback,
+ change,
+ _("Cancel Group Change?"),
+ change->window);
+
+ nautilus_file_set_group
+ (change->file, change->group,
+ (NautilusFileOperationCallback) group_change_callback, change);
+
+ return FALSE;
+}
+
+static void
+schedule_group_change (NautilusPropertiesWindow *self,
+ NautilusFile *file,
+ const char *group)
+{
+ GroupChange *change;
+
+ g_assert (NAUTILUS_IS_PROPERTIES_WINDOW (self));
+ g_assert (self->group_change == NULL);
+ g_assert (NAUTILUS_IS_FILE (file));
+
+ change = g_new0 (GroupChange, 1);
+
+ change->file = nautilus_file_ref (file);
+ change->group = g_strdup (group);
+ change->window = GTK_WINDOW (g_object_ref (self));
+ change->timeout =
+ g_timeout_add (CHOWN_CHGRP_TIMEOUT,
+ (GSourceFunc) schedule_group_change_timeout,
+ change);
+
+ self->group_change = change;
+}
+
+static void
+unschedule_or_cancel_group_change (NautilusPropertiesWindow *self)
+{
+ GroupChange *change;
+
+ g_assert (NAUTILUS_IS_PROPERTIES_WINDOW (self));
+
+ change = self->group_change;
+
+ if (change != NULL)
+ {
+ if (change->timeout == 0)
+ {
+ /* The operation was started, cancel it and let the operation callback free the change */
+ cancel_group_change_callback (change);
+ eel_timed_wait_stop ((EelCancelCallback) cancel_group_change_callback, change);
+ }
+ else
+ {
+ g_source_remove (change->timeout);
+ group_change_free (change);
+ }
+
+ self->group_change = NULL;
+ }
+}
+
+/** Apply group owner change on user selection. */
+static void
+changed_group_callback (AdwComboRow *row,
+ GParamSpec *pspec,
+ NautilusPropertiesWindow *self)
+{
+ guint selected_pos = adw_combo_row_get_selected (row);
+
+ g_assert (NAUTILUS_IS_PROPERTIES_WINDOW (self));
+
+ if (selected_pos >= 0)
+ {
+ NautilusFile *file = get_target_file (self);
+ GListModel *list = adw_combo_row_get_model (row);
+ const gchar *new_group_name = gtk_string_list_get_string (GTK_STRING_LIST (list), selected_pos);
+ g_autofree char *current_group_name = nautilus_file_get_group_name (file);
+
+ g_assert (new_group_name);
+ g_assert (current_group_name);
+
+ if (strcmp (new_group_name, current_group_name) != 0)
+ {
+ /* Try to change file group. If this fails, complain to user. */
+ unschedule_or_cancel_group_change (self);
+ schedule_group_change (self, file, new_group_name);
+ }
+ }
+}
+
+static void
+owner_change_free (OwnerChange *change)
+{
+ nautilus_file_unref (change->file);
+ g_free (change->owner);
+ g_object_unref (change->window);
+
+ g_free (change);
+}
+
+static void
+owner_change_callback (NautilusFile *file,
+ GFile *result_location,
+ GError *error,
+ OwnerChange *change)
+{
+ NautilusPropertiesWindow *self;
+
+ g_assert (NAUTILUS_IS_PROPERTIES_WINDOW (change->window));
+ g_assert (NAUTILUS_IS_FILE (change->file));
+ g_assert (change->owner != NULL);
+
+ if (!change->cancelled)
+ {
+ /* Report the error if it's an error. */
+ eel_timed_wait_stop ((EelCancelCallback) cancel_owner_change_callback, change);
+ nautilus_report_error_setting_owner (file, error, change->window);
+ }
+
+ self = NAUTILUS_PROPERTIES_WINDOW (change->window);
+ if (self->owner_change == change)
+ {
+ self->owner_change = NULL;
+ }
+
+ owner_change_free (change);
+}
+
+static void
+cancel_owner_change_callback (OwnerChange *change)
+{
+ g_assert (NAUTILUS_IS_FILE (change->file));
+ g_assert (change->owner != NULL);
+
+ change->cancelled = TRUE;
+ nautilus_file_cancel (change->file, (NautilusFileOperationCallback) owner_change_callback, change);
+}
+
+static gboolean
+schedule_owner_change_timeout (OwnerChange *change)
+{
+ g_assert (NAUTILUS_IS_PROPERTIES_WINDOW (change->window));
+ g_assert (NAUTILUS_IS_FILE (change->file));
+ g_assert (change->owner != NULL);
+
+ change->timeout = 0;
+
+ eel_timed_wait_start
+ ((EelCancelCallback) cancel_owner_change_callback,
+ change,
+ _("Cancel Owner Change?"),
+ change->window);
+
+ nautilus_file_set_owner
+ (change->file, change->owner,
+ (NautilusFileOperationCallback) owner_change_callback, change);
+
+ return FALSE;
+}
+
+static void
+schedule_owner_change (NautilusPropertiesWindow *self,
+ NautilusFile *file,
+ const char *owner)
+{
+ OwnerChange *change;
+
+ g_assert (NAUTILUS_IS_PROPERTIES_WINDOW (self));
+ g_assert (self->owner_change == NULL);
+ g_assert (NAUTILUS_IS_FILE (file));
+
+ change = g_new0 (OwnerChange, 1);
+
+ change->file = nautilus_file_ref (file);
+ change->owner = g_strdup (owner);
+ change->window = GTK_WINDOW (g_object_ref (self));
+ change->timeout =
+ g_timeout_add (CHOWN_CHGRP_TIMEOUT,
+ (GSourceFunc) schedule_owner_change_timeout,
+ change);
+
+ self->owner_change = change;
+}
+
+static void
+unschedule_or_cancel_owner_change (NautilusPropertiesWindow *self)
+{
+ OwnerChange *change;
+
+ g_assert (NAUTILUS_IS_PROPERTIES_WINDOW (self));
+
+ change = self->owner_change;
+
+ if (change != NULL)
+ {
+ g_assert (NAUTILUS_IS_FILE (change->file));
+
+ if (change->timeout == 0)
+ {
+ /* The operation was started, cancel it and let the operation callback free the change */
+ cancel_owner_change_callback (change);
+ eel_timed_wait_stop ((EelCancelCallback) cancel_owner_change_callback, change);
+ }
+ else
+ {
+ g_source_remove (change->timeout);
+ owner_change_free (change);
+ }
+
+ self->owner_change = NULL;
+ }
+}
+
+static void
+changed_owner_callback (AdwComboRow *row,
+ GParamSpec *pspec,
+ NautilusPropertiesWindow *self)
+{
+ guint selected_pos = adw_combo_row_get_selected (row);
+ g_assert (NAUTILUS_IS_PROPERTIES_WINDOW (self));
+
+ if (selected_pos >= 0)
+ {
+ NautilusFile *file = get_target_file (self);
+
+ GListModel *list = adw_combo_row_get_model (row);
+ const gchar *selected_owner_str = gtk_string_list_get_string (GTK_STRING_LIST (list), selected_pos);
+ gsize owner_name_length = get_first_word_length (selected_owner_str);
+ g_autofree gchar *new_owner_name = g_strndup (selected_owner_str, owner_name_length);
+ g_autofree char *current_owner_name = nautilus_file_get_owner_name (file);
+
+ g_assert (NAUTILUS_IS_FILE (file));
+
+ if (strcmp (new_owner_name, current_owner_name) != 0)
+ {
+ /* Try to change file owner. If this fails, complain to user. */
+ unschedule_or_cancel_owner_change (self);
+ schedule_owner_change (self, file, new_owner_name);
+ }
+ }
+}
+
+static gboolean
+string_list_item_starts_with_word (GListModel *list,
+ guint position,
+ const char *word)
+{
+ const gchar *entry_str = gtk_string_list_get_string (GTK_STRING_LIST (list), position);
+
+ return g_str_has_prefix (entry_str, word)
+ && strlen (word) == get_first_word_length (entry_str);
+}
+
+static gboolean
+string_list_item_equals_string (GListModel *list,
+ guint position,
+ const char *string)
+{
+ const gchar *entry_str = gtk_string_list_get_string (GTK_STRING_LIST (list), position);
+
+ return strcmp (entry_str, string) == 0;
+}
+
+/* Select correct owner if file permissions have changed. */
+static void
+update_owner_row (AdwComboRow *row,
+ TargetPermissions *target_perm)
+{
+ NautilusPropertiesWindow *self = target_perm->window;
+ gboolean provide_dropdown = (!target_perm->is_multi_file_window
+ && nautilus_file_can_set_owner (get_target_file (self)));
+ gboolean had_dropdown = gtk_widget_is_sensitive (GTK_WIDGET (row));
+
+ gtk_widget_set_sensitive (GTK_WIDGET (row), provide_dropdown);
+
+ /* check if should provide dropdown */
+ if (provide_dropdown)
+ {
+ NautilusFile *file = get_target_file (self);
+ g_autofree char *owner_name = nautilus_file_get_owner_name (file);
+ GList *users = nautilus_get_user_names ();
+
+ update_combo_row_dropdown (row, users);
+
+ /* display current owner */
+ select_ownership_row_entry (row, owner_name, string_list_item_starts_with_word);
+
+ if (!had_dropdown)
+ {
+ /* Update file when selection changes. */
+ g_signal_connect (row, "notify::selected",
+ G_CALLBACK (changed_owner_callback),
+ self);
+ }
+ }
+ else
+ {
+ g_autofree char *owner_name = file_list_get_string_attribute (self->target_files,
+ "owner");
+ if (owner_name == NULL)
+ {
+ owner_name = g_strdup (_("Multiple"));
+ }
+
+ g_signal_handlers_disconnect_by_func (row, G_CALLBACK (changed_owner_callback), self);
+
+ ownership_row_set_single_entry (row, owner_name, string_list_item_starts_with_word);
+ }
+}
+
+/* Select correct group if file permissions have changed. */
+static void
+update_group_row (AdwComboRow *row,
+ TargetPermissions *target_perm)
+{
+ NautilusPropertiesWindow *self = target_perm->window;
+ gboolean provide_dropdown = (!target_perm->is_multi_file_window
+ && nautilus_file_can_set_group (get_target_file (self)));
+ gboolean had_dropdown = gtk_widget_is_sensitive (GTK_WIDGET (row));
+
+ gtk_widget_set_sensitive (GTK_WIDGET (row), provide_dropdown);
+
+ if (provide_dropdown)
+ {
+ NautilusFile *file = get_target_file (self);
+ g_autofree char *group_name = nautilus_file_get_group_name (file);
+ GList *groups = nautilus_file_get_settable_group_names (file);
+ update_combo_row_dropdown (row, groups);
+
+ /* display current group */
+ select_ownership_row_entry (row, group_name, string_list_item_equals_string);
+
+ if (!had_dropdown)
+ {
+ /* Update file when selection changes. */
+ g_signal_connect (row, "notify::selected",
+ G_CALLBACK (changed_group_callback),
+ self);
+ }
+ }
+ else
+ {
+ g_autofree char *group_name = file_list_get_string_attribute (self->target_files,
+ "group");
+ if (group_name == NULL)
+ {
+ group_name = g_strdup (_("Multiple"));
+ }
+
+ g_signal_handlers_disconnect_by_func (row, G_CALLBACK (changed_group_callback), self);
+
+ ownership_row_set_single_entry (row, group_name, string_list_item_equals_string);
+ }
+}
+
+static void
+setup_ownership_row (NautilusPropertiesWindow *self,
+ AdwComboRow *row)
+{
+ adw_combo_row_set_model (row, G_LIST_MODEL (gtk_string_list_new (NULL)));
+
+ /* Intial setup of list model is handled via update function, called via properties_window_update. */
+}
+
+static gboolean
+file_has_prefix (NautilusFile *file,
+ GList *prefix_candidates)
+{
+ GList *p;
+ g_autoptr (GFile) location = NULL;
+
+ location = nautilus_file_get_location (file);
+
+ for (p = prefix_candidates; p != NULL; p = p->next)
+ {
+ g_autoptr (GFile) candidate_location = NULL;
+
+ if (file == p->data)
+ {
+ continue;
+ }
+
+ candidate_location = nautilus_file_get_location (NAUTILUS_FILE (p->data));
+ if (g_file_has_prefix (location, candidate_location))
+ {
+ return TRUE;
+ }
+ }
+
+ return FALSE;
+}
+
+static void
+directory_contents_value_field_update (NautilusPropertiesWindow *self)
+{
+ NautilusRequestStatus file_status;
+ g_autofree char *text = NULL;
+ guint directory_count;
+ guint file_count;
+ guint total_count;
+ guint unreadable_directory_count;
+ goffset total_size;
+ NautilusFile *file;
+ GList *l;
+ guint file_unreadable;
+ goffset file_size;
+ gboolean deep_count_active;
+
+ g_assert (NAUTILUS_IS_PROPERTIES_WINDOW (self));
+
+ total_count = 0;
+ total_size = 0;
+ unreadable_directory_count = FALSE;
+
+ for (l = self->target_files; l; l = l->next)
+ {
+ file = NAUTILUS_FILE (l->data);
+
+ if (file_has_prefix (file, self->target_files))
+ {
+ /* don't count nested files twice */
+ continue;
+ }
+
+ if (nautilus_file_is_directory (file))
+ {
+ file_status = nautilus_file_get_deep_counts (file,
+ &directory_count,
+ &file_count,
+ &file_unreadable,
+ &file_size,
+ TRUE);
+ total_count += (file_count + directory_count);
+ total_size += file_size;
+
+ if (file_unreadable)
+ {
+ unreadable_directory_count = TRUE;
+ }
+
+ if (file_status == NAUTILUS_REQUEST_DONE)
+ {
+ stop_deep_count_for_file (self, file);
+ }
+ }
+ else
+ {
+ ++total_count;
+ total_size += nautilus_file_get_size (file);
+ }
+ }
+
+ deep_count_active = (self->deep_count_files != NULL);
+ /* If we've already displayed the total once, don't do another visible
+ * count-up if the deep_count happens to get invalidated.
+ * But still display the new total, since it might have changed.
+ */
+ if (self->deep_count_finished && deep_count_active)
+ {
+ return;
+ }
+
+ text = NULL;
+
+ if (total_count == 0)
+ {
+ if (!deep_count_active)
+ {
+ if (unreadable_directory_count == 0)
+ {
+ text = g_strdup (_("Empty folder"));
+ }
+ else
+ {
+ text = g_strdup (_("Contents unreadable"));
+ }
+ }
+ else
+ {
+ text = g_strdup ("…");
+ }
+ }
+ else
+ {
+ g_autofree char *size_str = NULL;
+ size_str = g_format_size (total_size);
+ text = g_strdup_printf (ngettext ("%'d item, with size %s",
+ "%'d items, totalling %s",
+ total_count),
+ total_count, size_str);
+
+ if (unreadable_directory_count != 0)
+ {
+ g_autofree char *temp = g_steal_pointer (&text);
+
+ text = g_strconcat (temp, "\n",
+ _("(some contents unreadable)"),
+ NULL);
+ }
+ }
+
+ gtk_label_set_text (GTK_LABEL (self->contents_value_label),
+ text);
+
+ if (!deep_count_active)
+ {
+ self->deep_count_finished = TRUE;
+ stop_spinner (self);
+ }
+}
+
+static gboolean
+update_directory_contents_callback (gpointer data)
+{
+ NautilusPropertiesWindow *self;
+
+ self = NAUTILUS_PROPERTIES_WINDOW (data);
+
+ self->update_directory_contents_timeout_id = 0;
+ directory_contents_value_field_update (self);
+
+ return FALSE;
+}
+
+static void
+schedule_directory_contents_update (NautilusPropertiesWindow *self)
+{
+ g_assert (NAUTILUS_IS_PROPERTIES_WINDOW (self));
+
+ if (self->update_directory_contents_timeout_id == 0)
+ {
+ self->update_directory_contents_timeout_id
+ = g_timeout_add (DIRECTORY_CONTENTS_UPDATE_INTERVAL,
+ update_directory_contents_callback,
+ self);
+ }
+}
+
+static void
+setup_contents_field (NautilusPropertiesWindow *self)
+{
+ g_list_foreach (self->target_files,
+ (GFunc) start_deep_count_for_file,
+ self);
+
+ /* Fill in the initial value. */
+ directory_contents_value_field_update (self);
+}
+
+static gboolean
+is_root_directory (NautilusFile *file)
+{
+ g_autoptr (GFile) location = NULL;
+ gboolean result;
+
+ location = nautilus_file_get_location (file);
+ result = nautilus_is_root_directory (location);
+
+ return result;
+}
+
+static gboolean
+is_network_directory (NautilusFile *file)
+{
+ g_autofree char *file_uri = NULL;
+
+ file_uri = nautilus_file_get_uri (file);
+
+ return strcmp (file_uri, "network:///") == 0;
+}
+
+static gboolean
+is_burn_directory (NautilusFile *file)
+{
+ g_autofree char *file_uri = NULL;
+
+ file_uri = nautilus_file_get_uri (file);
+
+ return strcmp (file_uri, "burn:///") == 0;
+}
+
+
+static gboolean
+is_volume_properties (NautilusPropertiesWindow *self)
+{
+ NautilusFile *file;
+ gboolean success = FALSE;
+
+ if (is_multi_file_window (self))
+ {
+ return FALSE;
+ }
+
+ file = get_original_file (self);
+
+ if (file == NULL)
+ {
+ return FALSE;
+ }
+
+ if (is_root_directory (file) && nautilus_application_is_sandboxed ())
+ {
+ return FALSE;
+ }
+
+ if (nautilus_file_can_unmount (file))
+ {
+ return TRUE;
+ }
+
+ success = is_root_directory (file);
+
+#ifdef TODO_GIO
+ /* Look at is_mountpoint for activation uri */
+#endif
+
+ return success;
+}
+
+static gboolean
+should_show_custom_icon_buttons (NautilusPropertiesWindow *self)
+{
+ if (is_multi_file_window (self) || is_volume_properties (self) ||
+ is_root_directory (get_original_file (self)))
+ {
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static gboolean
+is_single_file_type (NautilusPropertiesWindow *self)
+{
+ if (is_multi_file_window (self))
+ {
+ g_autofree gchar *mime_type = NULL;
+ GList *l = self->original_files;
+
+ mime_type = nautilus_file_get_mime_type (NAUTILUS_FILE (l->data));
+ for (l = l->next; l != NULL; l = l->next)
+ {
+ g_autofree gchar *next_mime_type = NULL;
+
+ if (nautilus_file_is_gone (NAUTILUS_FILE (l->data)))
+ {
+ continue;
+ }
+
+ next_mime_type = nautilus_file_get_mime_type (NAUTILUS_FILE (l->data));
+ if (g_strcmp0 (next_mime_type, mime_type) != 0)
+ {
+ return FALSE;
+ }
+ }
+ }
+
+ return TRUE;
+}
+
+static gboolean
+should_show_file_type (NautilusPropertiesWindow *self)
+{
+ if (!is_single_file_type (self))
+ {
+ return FALSE;
+ }
+
+ if (!is_multi_file_window (self)
+ && (nautilus_file_is_in_trash (get_target_file (self)) ||
+ nautilus_file_is_directory (get_original_file (self)) ||
+ is_network_directory (get_target_file (self)) ||
+ is_burn_directory (get_target_file (self)) ||
+ is_volume_properties (self)))
+ {
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static gboolean
+should_show_location_info (NautilusPropertiesWindow *self)
+{
+ GList *l;
+
+ for (l = self->original_files; l != NULL; l = l->next)
+ {
+ if (nautilus_file_is_in_trash (NAUTILUS_FILE (l->data)) ||
+ is_root_directory (NAUTILUS_FILE (l->data)) ||
+ is_network_directory (NAUTILUS_FILE (l->data)) ||
+ is_burn_directory (NAUTILUS_FILE (l->data)))
+ {
+ return FALSE;
+ }
+ }
+
+ return TRUE;
+}
+
+static gboolean
+should_show_trashed_info (NautilusPropertiesWindow *self)
+{
+ GList *l;
+
+ for (l = self->original_files; l != NULL; l = l->next)
+ {
+ if (!nautilus_file_is_in_trash (NAUTILUS_FILE (l->data)))
+ {
+ return FALSE;
+ }
+ }
+
+ return TRUE;
+}
+
+static gboolean
+should_show_accessed_date (NautilusPropertiesWindow *self)
+{
+ /* Accessed date for directory seems useless. If we some
+ * day decide that it is useful, we should separately
+ * consider whether it's useful for "trash:".
+ */
+ if (nautilus_file_list_are_all_folders (self->target_files)
+ || is_multi_file_window (self))
+ {
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static gboolean
+should_show_modified_date (NautilusPropertiesWindow *self)
+{
+ return !is_multi_file_window (self);
+}
+
+static gboolean
+should_show_created_date (NautilusPropertiesWindow *self)
+{
+ return !is_multi_file_window (self);
+}
+
+static gboolean
+should_show_link_target (NautilusPropertiesWindow *self)
+{
+ if (!is_multi_file_window (self)
+ && nautilus_file_is_symbolic_link (get_target_file (self)))
+ {
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static gboolean
+should_show_free_space (NautilusPropertiesWindow *self)
+{
+ if (!is_multi_file_window (self)
+ && (nautilus_file_is_in_trash (get_target_file (self)) ||
+ is_network_directory (get_target_file (self)) ||
+ nautilus_file_is_in_recent (get_target_file (self)) ||
+ is_burn_directory (get_target_file (self)) ||
+ is_volume_properties (self)))
+ {
+ return FALSE;
+ }
+
+ if (nautilus_file_list_are_all_folders (self->target_files))
+ {
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static gboolean
+should_show_volume_usage (NautilusPropertiesWindow *self)
+{
+ return is_volume_properties (self);
+}
+
+static void
+setup_volume_information (NautilusPropertiesWindow *self)
+{
+ NautilusFile *file;
+ g_autofree gchar *capacity = NULL;
+ g_autofree gchar *used = NULL;
+ g_autofree gchar *free = NULL;
+ const char *fs_type;
+ g_autofree gchar *uri = NULL;
+ g_autoptr (GFile) location = NULL;
+ g_autoptr (GFileInfo) info = NULL;
+
+ capacity = g_format_size (self->volume_capacity);
+ free = g_format_size (self->volume_free);
+ used = g_format_size (self->volume_used);
+
+ file = get_original_file (self);
+
+ uri = nautilus_file_get_activation_uri (file);
+
+ gtk_label_set_text (GTK_LABEL (self->disk_space_used_value), used);
+ gtk_label_set_text (GTK_LABEL (self->disk_space_free_value), free);
+ gtk_label_set_text (GTK_LABEL (self->disk_space_capacity_value), capacity);
+
+ location = g_file_new_for_uri (uri);
+ info = g_file_query_filesystem_info (location, G_FILE_ATTRIBUTE_FILESYSTEM_TYPE,
+ NULL, NULL);
+ if (info)
+ {
+ fs_type = g_file_info_get_attribute_string (info, G_FILE_ATTRIBUTE_FILESYSTEM_TYPE);
+
+ /* We shouldn't be using filesystem::type, it's not meant for UI.
+ * https://gitlab.gnome.org/GNOME/nautilus/-/issues/98
+ *
+ * Until we fix that issue, workaround this common outrageous case. */
+ if (g_strcmp0 (fs_type, "msdos") == 0)
+ {
+ fs_type = "FAT";
+ }
+
+ if (fs_type != NULL)
+ {
+ /* Translators: %s will be filled with a filesystem type, such as 'ext4' or 'msdos'. */
+ g_autofree gchar *fs_label = g_strdup_printf (_("%s Filesystem"), fs_type);
+ gchar *cap_label = eel_str_capitalize (fs_label);
+ if (cap_label != NULL)
+ {
+ g_free (fs_label);
+ fs_label = cap_label;
+ }
+
+ gtk_label_set_text (self->type_file_system_label, fs_label);
+ gtk_widget_show (GTK_WIDGET (self->type_file_system_label));
+ }
+ }
+
+ gtk_level_bar_set_value (self->disk_space_level_bar, (double) self->volume_used / (double) self->volume_capacity);
+ /* display color changing based on filled level */
+ gtk_level_bar_add_offset_value (self->disk_space_level_bar, GTK_LEVEL_BAR_OFFSET_FULL, 0.0);
+}
+
+static void
+setup_volume_usage_widget (NautilusPropertiesWindow *self)
+{
+ NautilusFile *file;
+ g_autofree gchar *uri = NULL;
+ g_autoptr (GFile) location = NULL;
+ g_autoptr (GFileInfo) info = NULL;
+
+ file = get_original_file (self);
+
+ uri = nautilus_file_get_activation_uri (file);
+
+ location = g_file_new_for_uri (uri);
+ info = g_file_query_filesystem_info (location, "filesystem::*", NULL, NULL);
+
+ if (info)
+ {
+ self->volume_capacity = g_file_info_get_attribute_uint64 (info, G_FILE_ATTRIBUTE_FILESYSTEM_SIZE);
+ self->volume_free = g_file_info_get_attribute_uint64 (info, G_FILE_ATTRIBUTE_FILESYSTEM_FREE);
+ if (g_file_info_has_attribute (info, G_FILE_ATTRIBUTE_FILESYSTEM_USED))
+ {
+ self->volume_used = g_file_info_get_attribute_uint64 (info, G_FILE_ATTRIBUTE_FILESYSTEM_USED);
+ }
+ else
+ {
+ self->volume_used = self->volume_capacity - self->volume_free;
+ }
+ }
+ else
+ {
+ self->volume_capacity = 0;
+ self->volume_free = 0;
+ self->volume_used = 0;
+ }
+
+ if (self->volume_capacity > 0)
+ {
+ setup_volume_information (self);
+ }
+}
+
+static void
+open_parent_folder (NautilusPropertiesWindow *self)
+{
+ g_autoptr (GFile) parent_location = NULL;
+
+ parent_location = nautilus_file_get_parent_location (get_target_file (self));
+ g_return_if_fail (parent_location != NULL);
+
+ nautilus_application_open_location_full (NAUTILUS_APPLICATION (g_application_get_default ()),
+ parent_location,
+ NAUTILUS_OPEN_FLAG_NEW_WINDOW,
+ &(GList){get_original_file (self), NULL},
+ NULL, NULL);
+}
+
+static void
+open_link_target (NautilusPropertiesWindow *self)
+{
+ g_autofree gchar *link_target_uri = NULL;
+ g_autoptr (GFile) link_target_location = NULL;
+ g_autoptr (NautilusFile) link_target_file = NULL;
+ g_autoptr (GFile) parent_location = NULL;
+
+ link_target_uri = nautilus_file_get_symbolic_link_target_uri (get_target_file (self));
+ g_return_if_fail (link_target_uri != NULL);
+ link_target_location = g_file_new_for_uri (link_target_uri);
+ link_target_file = nautilus_file_get (link_target_location);
+ parent_location = nautilus_file_get_parent_location (link_target_file);
+ g_return_if_fail (parent_location != NULL);
+
+ nautilus_application_open_location_full (NAUTILUS_APPLICATION (g_application_get_default ()),
+ parent_location,
+ NAUTILUS_OPEN_FLAG_NEW_WINDOW,
+ &(GList){link_target_file, NULL},
+ NULL, NULL);
+}
+
+static void
+open_in_disks (NautilusPropertiesWindow *self)
+{
+ NautilusDBusLauncher *launcher = nautilus_dbus_launcher_get ();
+ g_autoptr (GMount) mount = NULL;
+ g_autoptr (GVolume) volume = NULL;
+ g_autofree gchar *device_identifier = NULL;
+ GVariant *parameters;
+
+ mount = nautilus_file_get_mount (get_original_file (self));
+ volume = (mount != NULL) ? g_mount_get_volume (mount) : NULL;
+
+ if (volume != NULL)
+ {
+ device_identifier = g_volume_get_identifier (volume,
+ G_VOLUME_IDENTIFIER_KIND_UNIX_DEVICE);
+ }
+ else
+ {
+ g_autoptr (GFile) location = NULL;
+ g_autofree gchar *path = NULL;
+ g_autoptr (GUnixMountEntry) mount_entry = NULL;
+
+ location = nautilus_file_get_location (get_original_file (self));
+ path = g_file_get_path (location);
+ mount_entry = (path != NULL) ? g_unix_mount_at (path, NULL) : NULL;
+ if (mount_entry != NULL)
+ {
+ device_identifier = g_strdup (g_unix_mount_get_device_path (mount_entry));
+ }
+ }
+
+ if (device_identifier != NULL)
+ {
+ parameters = g_variant_new_parsed ("(objectpath '/org/gnome/DiskUtility', "
+ "@aay [], {'options': <{'block-device': <%s>}> })",
+ device_identifier);
+ }
+ else
+ {
+ parameters = g_variant_new_parsed ("(objectpath '/org/gnome/DiskUtility', @aay [], @a{sv} {})");
+ }
+
+ nautilus_dbus_launcher_call (launcher,
+ NAUTILUS_DBUS_LAUNCHER_DISKS,
+ "CommandLine", parameters,
+ GTK_WINDOW (self));
+}
+
+static void
+add_updatable_label (NautilusPropertiesWindow *self,
+ GtkWidget *label,
+ const char *file_attribute)
+{
+ g_object_set_data_full (G_OBJECT (label), "file_attribute",
+ g_strdup (file_attribute), g_free);
+
+ self->value_fields = g_list_prepend (self->value_fields, label);
+}
+
+static void
+setup_basic_page (NautilusPropertiesWindow *self)
+{
+ gboolean should_show_locations_list_box = FALSE;
+
+ /* Icon pixmap */
+
+ setup_image_widget (self, should_show_custom_icon_buttons (self));
+
+ self->icon_chooser = NULL;
+
+ if (!is_multi_file_window (self))
+ {
+ setup_star_button (self);
+ }
+
+ update_name_field (self);
+
+ if (should_show_volume_usage (self))
+ {
+ gtk_widget_show (self->disk_list_box);
+ setup_volume_usage_widget (self);
+ }
+
+ if (should_show_file_type (self))
+ {
+ gtk_widget_show (self->type_value_label);
+ add_updatable_label (self, self->type_value_label, "detailed_type");
+ }
+
+ if (should_show_link_target (self))
+ {
+ gtk_widget_show (self->link_target_row);
+ add_updatable_label (self, self->link_target_value_label, "link_target");
+
+ should_show_locations_list_box = TRUE;
+ }
+
+ if (is_multi_file_window (self) ||
+ nautilus_file_is_directory (get_target_file (self)))
+ {
+ /* We have a more efficient way to measure used space in volumes. */
+ if (!is_volume_properties (self))
+ {
+ gtk_widget_show (self->contents_box);
+ setup_contents_field (self);
+ }
+ }
+ else
+ {
+ gtk_widget_show (self->size_value_label);
+ add_updatable_label (self, self->size_value_label, "size");
+ }
+
+ if (should_show_location_info (self))
+ {
+ gtk_widget_show (self->parent_folder_row);
+ add_updatable_label (self, self->parent_folder_value_label, "where");
+
+ should_show_locations_list_box = TRUE;
+ }
+
+ if (should_show_trashed_info (self))
+ {
+ gtk_widget_show (self->trashed_list_box);
+ add_updatable_label (self, self->original_folder_value_label, "trash_orig_path");
+ add_updatable_label (self, self->trashed_on_value_label, "trashed_on_full");
+ }
+
+ if (should_show_modified_date (self))
+ {
+ gtk_widget_show (self->times_list_box);
+ gtk_widget_show (self->modified_row);
+ add_updatable_label (self, self->modified_value_label, "date_modified_full");
+ }
+
+ if (should_show_created_date (self))
+ {
+ gtk_widget_show (self->created_row);
+ gtk_widget_show (self->times_list_box);
+ add_updatable_label (self, self->created_value_label, "date_created_full");
+ }
+
+ if (should_show_accessed_date (self))
+ {
+ gtk_widget_show (self->times_list_box);
+ gtk_widget_show (self->accessed_row);
+ add_updatable_label (self, self->accessed_value_label, "date_accessed_full");
+ }
+
+ if (should_show_free_space (self))
+ {
+ /* We have a more efficient way to measure free space in volumes. */
+ if (!is_volume_properties (self))
+ {
+ gtk_widget_show (self->free_space_value_label);
+ add_updatable_label (self, self->free_space_value_label, "free_space");
+ }
+ }
+
+ if (should_show_locations_list_box)
+ {
+ gtk_widget_show (self->locations_list_box);
+ }
+}
+
+static FilterType
+files_get_filter_type (NautilusPropertiesWindow *self)
+{
+ FilterType filter_type = NO_FILES_OR_FOLDERS;
+
+ for (GList *l = self->target_files; l != NULL && filter_type != FILES_AND_FOLDERS; l = l->next)
+ {
+ NautilusFile *file = NAUTILUS_FILE (l->data);
+ if (nautilus_file_is_directory (file))
+ {
+ filter_type |= FOLDERS_ONLY;
+ }
+ else
+ {
+ filter_type |= FILES_ONLY;
+ }
+ }
+
+ return filter_type;
+}
+
+static gboolean
+file_matches_filter_type (NautilusFile *file,
+ FilterType filter_type)
+{
+ gboolean is_directory = nautilus_file_is_directory (file);
+
+ switch (filter_type)
+ {
+ case FILES_AND_FOLDERS:
+ {
+ return TRUE;
+ }
+
+ case FILES_ONLY:
+ {
+ return !is_directory;
+ }
+
+ case FOLDERS_ONLY:
+ {
+ return is_directory;
+ }
+
+ default:
+ {
+ return FALSE;
+ }
+ }
+}
+
+static gboolean
+files_has_changable_permissions_directory (NautilusPropertiesWindow *self)
+{
+ GList *l;
+ gboolean changable = FALSE;
+
+ for (l = self->target_files; l != NULL; l = l->next)
+ {
+ NautilusFile *file;
+ file = NAUTILUS_FILE (l->data);
+ if (nautilus_file_is_directory (file) &&
+ nautilus_file_can_get_permissions (file) &&
+ nautilus_file_can_set_permissions (file))
+ {
+ changable = TRUE;
+ }
+ else
+ {
+ changable = FALSE;
+ break;
+ }
+ }
+
+ return changable;
+}
+
+static void
+start_long_operation (NautilusPropertiesWindow *self)
+{
+ if (self->long_operation_underway == 0)
+ {
+ /* start long operation */
+ gtk_widget_set_cursor_from_name (GTK_WIDGET (self), "wait");
+ }
+ self->long_operation_underway++;
+}
+
+static void
+end_long_operation (NautilusPropertiesWindow *self)
+{
+ if (gtk_native_get_surface (GTK_NATIVE (self)) != NULL &&
+ self->long_operation_underway == 1)
+ {
+ /* finished !! */
+ gtk_widget_set_cursor (GTK_WIDGET (self), NULL);
+ }
+ self->long_operation_underway--;
+}
+
+static void
+permission_change_callback (NautilusFile *file,
+ GFile *res_loc,
+ GError *error,
+ gpointer callback_data)
+{
+ g_autoptr (NautilusPropertiesWindow) self = NAUTILUS_PROPERTIES_WINDOW (callback_data);
+ g_assert (self != NULL);
+
+ end_long_operation (self);
+
+ /* Report the error if it's an error. */
+ nautilus_report_error_setting_permissions (file, error, GTK_WINDOW (self));
+}
+
+static void
+update_permissions (NautilusPropertiesWindow *self,
+ guint32 vfs_new_perm,
+ guint32 vfs_mask,
+ FilterType filter_type,
+ gboolean use_original)
+{
+ for (GList *l = self->target_files; l != NULL; l = l->next)
+ {
+ NautilusFile *file = NAUTILUS_FILE (l->data);
+ guint32 permissions;
+
+ if (!nautilus_file_can_get_permissions (file))
+ {
+ continue;
+ }
+
+ if (!nautilus_file_can_get_permissions (file) || !file_matches_filter_type (file, filter_type))
+ {
+ continue;
+ }
+
+ permissions = nautilus_file_get_permissions (file);
+ if (use_original)
+ {
+ gpointer ptr;
+ if (g_hash_table_lookup_extended (self->initial_permissions,
+ file, NULL, &ptr))
+ {
+ permissions = (permissions & ~vfs_mask) | (GPOINTER_TO_INT (ptr) & vfs_mask);
+ }
+ }
+ else
+ {
+ permissions = (permissions & ~vfs_mask) | vfs_new_perm;
+ }
+
+ start_long_operation (self);
+ g_object_ref (self);
+ nautilus_file_set_permissions
+ (file, permissions,
+ permission_change_callback,
+ self);
+ }
+}
+
+static void
+execution_bit_changed (NautilusPropertiesWindow *self,
+ GParamSpec *params,
+ GtkWidget *widget)
+{
+ const guint32 permission_mask = UNIX_PERM_USER_EXEC | UNIX_PERM_GROUP_EXEC | UNIX_PERM_OTHER_EXEC;
+ const FilterType filter_type = FILES_ONLY;
+
+ /* if activated from switch, switch state is already toggled, thus invert value via XOR. */
+ gboolean active = gtk_switch_get_state (self->execution_switch) ^ GTK_IS_SWITCH (widget);
+ gboolean set_executable = !active;
+
+ update_permissions (self,
+ set_executable ? permission_mask : 0,
+ permission_mask,
+ filter_type,
+ FALSE);
+}
+
+static gboolean
+should_show_exectution_switch (NautilusPropertiesWindow *self)
+{
+ g_autofree gchar *mime_type = NULL;
+
+ if (is_multi_file_window (self))
+ {
+ return FALSE;
+ }
+
+ mime_type = nautilus_file_get_mime_type (get_target_file (self));
+ return g_content_type_can_be_executable (mime_type);
+}
+
+static void
+update_execution_row (GtkWidget *row,
+ TargetPermissions *target_perm)
+{
+ NautilusPropertiesWindow *self = target_perm->window;
+
+ if (!should_show_exectution_switch (self))
+ {
+ gtk_widget_hide (GTK_WIDGET (self->execution_row));
+ }
+ else
+ {
+ g_signal_handlers_block_by_func (self->execution_switch,
+ G_CALLBACK (execution_bit_changed),
+ self);
+
+ gtk_switch_set_state (self->execution_switch,
+ target_perm->file_exec_permissions == PERMISSION_EXEC);
+
+ g_signal_handlers_unblock_by_func (self->execution_switch,
+ G_CALLBACK (execution_bit_changed),
+ self);
+
+ gtk_widget_set_sensitive (row,
+ target_perm->can_set_any_file_permission);
+
+ gtk_widget_show (GTK_WIDGET (self->execution_row));
+ }
+}
+
+static void
+on_permission_row_change (AdwComboRow *row,
+ GParamSpec *pspec,
+ NautilusPropertiesWindow *self)
+{
+ GListModel *list = adw_combo_row_get_model (row);
+ guint position = adw_combo_row_get_selected (row);
+ g_autoptr (NautilusPermissionEntry) entry = NULL;
+ FilterType filter_type;
+ gboolean use_original;
+ PermissionType type;
+ PermissionValue mask;
+ guint32 vfs_new_perm, vfs_mask;
+
+ g_assert (NAUTILUS_IS_PROPERTIES_WINDOW (self));
+
+ if (position == GTK_INVALID_LIST_POSITION)
+ {
+ return;
+ }
+
+ filter_type = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (row), "filter-type"));
+ type = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (row), "permission-type"));
+
+ mask = PERMISSION_READ | PERMISSION_WRITE | ((filter_type == FOLDERS_ONLY) * PERMISSION_EXEC);
+ vfs_mask = permission_to_vfs (type, mask);
+
+ entry = g_list_model_get_item (list, position);
+ vfs_new_perm = permission_to_vfs (type, entry->permission_value);
+ use_original = entry->permission_value & PERMISSION_INCONSISTENT;
+
+ update_permissions (self, vfs_new_perm, vfs_mask,
+ filter_type, use_original);
+}
+
+static void
+list_store_append_nautilus_permission_entry (GListStore *list,
+ PermissionValue permission_value,
+ gboolean describes_folder)
+{
+ g_autoptr (NautilusPermissionEntry) entry = g_object_new (NAUTILUS_TYPE_PERMISSION_ENTRY, NULL);
+
+ entry->name = g_strdup (permission_value_to_string (permission_value, describes_folder));
+ entry->permission_value = permission_value;
+
+ g_list_store_append (list, entry);
+}
+
+static gint
+get_permission_value_list_position (GListModel *list,
+ PermissionValue wanted_permissions)
+{
+ const guint n_entries = g_list_model_get_n_items (list);
+
+ for (guint position = 0; position < n_entries; position += 1)
+ {
+ g_autoptr (NautilusPermissionEntry) entry = g_list_model_get_item (list, position);
+ if (entry->permission_value == wanted_permissions)
+ {
+ return position;
+ }
+ }
+
+ return -1;
+}
+
+static void
+update_permission_row (AdwComboRow *row,
+ TargetPermissions *target_perm)
+{
+ NautilusPropertiesWindow *self = target_perm->window;
+ PermissionType type;
+ PermissionValue permissions_to_show;
+ FilterType filter_type;
+ gboolean is_folder;
+ GListModel *model;
+ gint position;
+
+ model = adw_combo_row_get_model (row);
+
+ filter_type = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (row), "filter-type"));
+ is_folder = (FOLDERS_ONLY == filter_type);
+ type = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (row), "permission-type"));
+
+ permissions_to_show = is_folder ? target_perm->folder_permissions[type] :
+ target_perm->file_permissions[type] & ~PERMISSION_EXEC;
+
+ g_signal_handlers_block_by_func (G_OBJECT (row),
+ G_CALLBACK (on_permission_row_change),
+ self);
+
+ position = get_permission_value_list_position (model, permissions_to_show);
+
+ if (position == GTK_INVALID_LIST_POSITION)
+ {
+ /* configured permissions not listed, create new entry */
+ position = g_list_model_get_n_items (model);
+ list_store_append_nautilus_permission_entry (G_LIST_STORE (model), permissions_to_show, is_folder);
+ }
+
+ adw_combo_row_set_selected (row, position);
+
+ /* Also enable if no files found (for recursive
+ * file changes when only selecting folders) */
+ gtk_widget_set_sensitive (GTK_WIDGET (row), is_folder ?
+ target_perm->can_set_all_folder_permission :
+ target_perm->can_set_all_file_permission);
+
+ g_signal_handlers_unblock_by_func (G_OBJECT (row),
+ G_CALLBACK (on_permission_row_change),
+ self);
+}
+
+static void
+setup_permissions_combo_box (GtkComboBox *combo,
+ PermissionType type,
+ FilterType filter_type)
+{
+ g_autoptr (GtkListStore) store = NULL;
+ GtkCellRenderer *cell;
+ GtkTreeIter iter;
+
+ store = gtk_list_store_new (NUM_COLUMNS, G_TYPE_STRING, G_TYPE_INT, G_TYPE_BOOLEAN, G_TYPE_STRING);
+ gtk_combo_box_set_model (combo, GTK_TREE_MODEL (store));
+ gtk_combo_box_set_id_column (GTK_COMBO_BOX (combo), COLUMN_ID);
+
+ g_object_set_data (G_OBJECT (combo), "filter-type", GINT_TO_POINTER (filter_type));
+ g_object_set_data (G_OBJECT (combo), "permission-type", GINT_TO_POINTER (type));
+
+ if (filter_type == FOLDERS_ONLY)
+ {
+ if (type != PERMISSION_USER)
+ {
+ gtk_list_store_append (store, &iter);
+ gtk_list_store_set (store, &iter,
+ /* Translators: this is referred to the permissions
+ * the user has in a directory.
+ */
+ COLUMN_NAME, _("None"),
+ COLUMN_VALUE, PERMISSION_NONE,
+ COLUMN_ID, "none",
+ -1);
+ }
+ gtk_list_store_append (store, &iter);
+ gtk_list_store_set (store, &iter,
+ COLUMN_NAME, _("List files only"),
+ COLUMN_VALUE, PERMISSION_READ,
+ COLUMN_ID, "r",
+ -1);
+ gtk_list_store_append (store, &iter);
+ gtk_list_store_set (store, &iter,
+ COLUMN_NAME, _("Access files"),
+ COLUMN_VALUE, PERMISSION_READ | PERMISSION_EXEC,
+ COLUMN_ID, "rx",
+ -1);
+ gtk_list_store_append (store, &iter);
+ gtk_list_store_set (store, &iter,
+ COLUMN_NAME, _("Create and delete files"),
+ COLUMN_VALUE, PERMISSION_READ | PERMISSION_EXEC | PERMISSION_WRITE,
+ COLUMN_ID, "rwx",
+ -1);
+ }
+ else
+ {
+ if (type != PERMISSION_USER)
+ {
+ gtk_list_store_append (store, &iter);
+ gtk_list_store_set (store, &iter,
+ COLUMN_NAME, _("None"),
+ COLUMN_VALUE, PERMISSION_NONE,
+ COLUMN_ID, "none",
+ -1);
+ }
+ gtk_list_store_append (store, &iter);
+ gtk_list_store_set (store, &iter,
+ COLUMN_NAME, _("Read-only"),
+ COLUMN_VALUE, PERMISSION_READ,
+ COLUMN_ID, "r",
+ -1);
+ gtk_list_store_append (store, &iter);
+ gtk_list_store_set (store, &iter,
+ COLUMN_NAME, _("Read and write"),
+ COLUMN_VALUE, PERMISSION_READ | PERMISSION_WRITE,
+ COLUMN_ID, "rw",
+ -1);
+ }
+
+ cell = gtk_cell_renderer_text_new ();
+ gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (combo), cell, TRUE);
+ gtk_cell_layout_set_attributes (GTK_CELL_LAYOUT (combo), cell,
+ "text", COLUMN_NAME,
+ NULL);
+}
+
+static gboolean
+all_can_get_permissions (GList *file_list)
+{
+ GList *l;
+ for (l = file_list; l != NULL; l = l->next)
+ {
+ NautilusFile *file;
+
+ file = NAUTILUS_FILE (l->data);
+
+ if (!nautilus_file_can_get_permissions (file))
+ {
+ return FALSE;
+ }
+ }
+
+ return TRUE;
+}
+
+static gboolean
+all_can_set_permissions (GList *file_list)
+{
+ GList *l;
+ for (l = file_list; l != NULL; l = l->next)
+ {
+ NautilusFile *file;
+
+ file = NAUTILUS_FILE (l->data);
+
+ if (!nautilus_file_can_set_permissions (file))
+ {
+ return FALSE;
+ }
+ }
+
+ return TRUE;
+}
+
+static GHashTable *
+get_initial_permissions (GList *file_list)
+{
+ GHashTable *ret;
+ GList *l;
+
+ ret = g_hash_table_new (g_direct_hash,
+ g_direct_equal);
+
+ for (l = file_list; l != NULL; l = l->next)
+ {
+ guint32 permissions;
+ NautilusFile *file;
+
+ file = NAUTILUS_FILE (l->data);
+
+ permissions = nautilus_file_get_permissions (file);
+ g_hash_table_insert (ret, file,
+ GINT_TO_POINTER (permissions));
+ }
+
+ return ret;
+}
+
+static GListModel *
+create_permission_list_model (PermissionType type,
+ FilterType filter_type)
+{
+ GListStore *store = g_list_store_new (NAUTILUS_TYPE_PERMISSION_ENTRY);
+
+ if (type != PERMISSION_USER)
+ {
+ list_store_append_nautilus_permission_entry (store, PERMISSION_NONE, /* unused */ FALSE);
+ }
+
+ if (filter_type == FOLDERS_ONLY)
+ {
+ list_store_append_nautilus_permission_entry (store, PERMISSION_READ, TRUE);
+ list_store_append_nautilus_permission_entry (store, PERMISSION_READ | PERMISSION_EXEC, TRUE);
+ list_store_append_nautilus_permission_entry (store, PERMISSION_READ | PERMISSION_EXEC | PERMISSION_WRITE, TRUE);
+ }
+ else
+ {
+ list_store_append_nautilus_permission_entry (store, PERMISSION_READ, FALSE);
+ list_store_append_nautilus_permission_entry (store, PERMISSION_READ | PERMISSION_WRITE, FALSE);
+ }
+
+ return G_LIST_MODEL (store);
+}
+
+static void
+create_permissions_row (NautilusPropertiesWindow *self,
+ AdwComboRow *row,
+ PermissionType permission_type,
+ FilterType filter_type)
+{
+ g_autoptr (GtkExpression) expression = NULL;
+ g_autoptr (GListModel) model = NULL;
+
+ expression = gtk_property_expression_new (NAUTILUS_TYPE_PERMISSION_ENTRY, NULL, "name");
+ adw_combo_row_set_expression (row, expression);
+
+ gtk_widget_show (GTK_WIDGET (row));
+
+ g_object_set_data (G_OBJECT (row), "permission-type", GINT_TO_POINTER (permission_type));
+ g_object_set_data (G_OBJECT (row), "filter-type", GINT_TO_POINTER (filter_type));
+ model = create_permission_list_model (permission_type, filter_type);
+ adw_combo_row_set_model (row, model);
+
+ self->permission_rows = g_list_prepend (self->permission_rows, row);
+ g_signal_connect (row, "notify::selected", G_CALLBACK (on_permission_row_change), self);
+}
+
+static void
+create_simple_permissions (NautilusPropertiesWindow *self)
+{
+ FilterType filter_type = files_get_filter_type (self);
+
+ g_assert (filter_type != NO_FILES_OR_FOLDERS);
+
+ setup_ownership_row (self, self->owner_row);
+ setup_ownership_row (self, self->group_row);
+
+ if (filter_type == FILES_AND_FOLDERS)
+ {
+ /* owner */
+ create_permissions_row (self, self->owner_folder_access_row,
+ PERMISSION_USER, FOLDERS_ONLY);
+ create_permissions_row (self, self->owner_file_access_row,
+ PERMISSION_USER, FILES_ONLY);
+ /* group */
+ create_permissions_row (self, self->group_folder_access_row,
+ PERMISSION_GROUP, FOLDERS_ONLY);
+ create_permissions_row (self, self->group_file_access_row,
+ PERMISSION_GROUP, FILES_ONLY);
+ /* others */
+ create_permissions_row (self, self->others_folder_access_row,
+ PERMISSION_OTHER, FOLDERS_ONLY);
+ create_permissions_row (self, self->others_file_access_row,
+ PERMISSION_OTHER, FILES_ONLY);
+ }
+ else
+ {
+ create_permissions_row (self, self->owner_access_row,
+ PERMISSION_USER, filter_type);
+ create_permissions_row (self, self->group_access_row,
+ PERMISSION_GROUP, filter_type);
+ create_permissions_row (self, self->others_access_row,
+ PERMISSION_OTHER, filter_type);
+ }
+
+ /* Connect execution bit switch, independent of whether it will be visible or not. */
+ g_signal_connect_swapped (self->execution_row, "activated",
+ G_CALLBACK (execution_bit_changed),
+ self);
+ g_signal_connect_swapped (self->execution_switch, "notify::active",
+ G_CALLBACK (execution_bit_changed),
+ self);
+}
+
+static void
+set_recursive_permissions_done (gboolean success,
+ gpointer callback_data)
+{
+ g_autoptr (NautilusPropertiesWindow) self = NAUTILUS_PROPERTIES_WINDOW (callback_data);
+ end_long_operation (self);
+}
+
+static void
+on_change_permissions_response (GtkDialog *dialog,
+ int response,
+ NautilusPropertiesWindow *self)
+{
+ guint32 file_permission, file_permission_mask;
+ guint32 dir_permission, dir_permission_mask;
+ guint32 vfs_mask, vfs_new_perm;
+ GtkWidget *combo;
+ gboolean use_original;
+ FilterType filter_type;
+ GList *l;
+ GtkTreeModel *model;
+ GtkTreeIter iter;
+ PermissionType type;
+ int new_perm, mask;
+
+ if (response != GTK_RESPONSE_OK)
+ {
+ g_clear_pointer (&self->change_permission_combos, g_list_free);
+ gtk_window_destroy (GTK_WINDOW (dialog));
+ return;
+ }
+
+ file_permission = 0;
+ file_permission_mask = 0;
+ dir_permission = 0;
+ dir_permission_mask = 0;
+
+ /* Simple mode, minus exec checkbox */
+ for (l = self->change_permission_combos; l != NULL; l = l->next)
+ {
+ combo = l->data;
+
+ if (!gtk_combo_box_get_active_iter (GTK_COMBO_BOX (combo), &iter))
+ {
+ continue;
+ }
+
+ type = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (combo), "permission-type"));
+ filter_type = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (combo), "filter-type"));
+
+ model = gtk_combo_box_get_model (GTK_COMBO_BOX (combo));
+ gtk_tree_model_get (model, &iter,
+ COLUMN_VALUE, &new_perm,
+ COLUMN_USE_ORIGINAL, &use_original, -1);
+ if (use_original)
+ {
+ continue;
+ }
+ vfs_new_perm = permission_to_vfs (type, new_perm);
+
+ mask = PERMISSION_READ | PERMISSION_WRITE;
+ if (filter_type == FOLDERS_ONLY)
+ {
+ mask |= PERMISSION_EXEC;
+ }
+ vfs_mask = permission_to_vfs (type, mask);
+
+ if (filter_type == FOLDERS_ONLY)
+ {
+ dir_permission_mask |= vfs_mask;
+ dir_permission |= vfs_new_perm;
+ }
+ else
+ {
+ file_permission_mask |= vfs_mask;
+ file_permission |= vfs_new_perm;
+ }
+ }
+
+ for (l = self->target_files; l != NULL; l = l->next)
+ {
+ NautilusFile *file;
+
+ file = NAUTILUS_FILE (l->data);
+
+ if (nautilus_file_is_directory (file) &&
+ nautilus_file_can_set_permissions (file))
+ {
+ g_autofree gchar *uri = NULL;
+
+ uri = nautilus_file_get_uri (file);
+ start_long_operation (self);
+ g_object_ref (self);
+ nautilus_file_set_permissions_recursive (uri,
+ file_permission,
+ file_permission_mask,
+ dir_permission,
+ dir_permission_mask,
+ set_recursive_permissions_done,
+ self);
+ }
+ }
+ g_clear_pointer (&self->change_permission_combos, g_list_free);
+ gtk_window_destroy (GTK_WINDOW (dialog));
+}
+
+static void
+set_active_from_umask (GtkComboBox *combo,
+ PermissionType type,
+ FilterType filter_type)
+{
+ mode_t initial;
+ mode_t mask;
+ mode_t p;
+ const char *id;
+
+ if (filter_type == FOLDERS_ONLY)
+ {
+ initial = (S_IRWXU | S_IRWXG | S_IRWXO);
+ }
+ else
+ {
+ initial = (S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH);
+ }
+
+ umask (mask = umask (0));
+
+ p = ~mask & initial;
+
+ if (type == PERMISSION_USER)
+ {
+ p &= ~(S_IRWXG | S_IRWXO);
+ if ((p & S_IRWXU) == S_IRWXU)
+ {
+ id = "rwx";
+ }
+ else if ((p & (S_IRUSR | S_IWUSR)) == (S_IRUSR | S_IWUSR))
+ {
+ id = "rw";
+ }
+ else if ((p & (S_IRUSR | S_IXUSR)) == (S_IRUSR | S_IXUSR))
+ {
+ id = "rx";
+ }
+ else if ((p & S_IRUSR) == S_IRUSR)
+ {
+ id = "r";
+ }
+ else
+ {
+ id = "none";
+ }
+ }
+ else if (type == PERMISSION_GROUP)
+ {
+ p &= ~(S_IRWXU | S_IRWXO);
+ if ((p & S_IRWXG) == S_IRWXG)
+ {
+ id = "rwx";
+ }
+ else if ((p & (S_IRGRP | S_IWGRP)) == (S_IRGRP | S_IWGRP))
+ {
+ id = "rw";
+ }
+ else if ((p & (S_IRGRP | S_IXGRP)) == (S_IRGRP | S_IXGRP))
+ {
+ id = "rx";
+ }
+ else if ((p & S_IRGRP) == S_IRGRP)
+ {
+ id = "r";
+ }
+ else
+ {
+ id = "none";
+ }
+ }
+ else
+ {
+ p &= ~(S_IRWXU | S_IRWXG);
+ if ((p & S_IRWXO) == S_IRWXO)
+ {
+ id = "rwx";
+ }
+ else if ((p & (S_IROTH | S_IWOTH)) == (S_IROTH | S_IWOTH))
+ {
+ id = "rw";
+ }
+ else if ((p & (S_IROTH | S_IXOTH)) == (S_IROTH | S_IXOTH))
+ {
+ id = "rx";
+ }
+ else if ((p & S_IROTH) == S_IROTH)
+ {
+ id = "r";
+ }
+ else
+ {
+ id = "none";
+ }
+ }
+
+ gtk_combo_box_set_active_id (combo, id);
+}
+
+static void
+on_change_permissions_clicked (GtkWidget *button,
+ NautilusPropertiesWindow *self)
+{
+ GtkWidget *dialog;
+ GtkComboBox *combo;
+ g_autoptr (GtkBuilder) change_permissions_builder = NULL;
+
+ change_permissions_builder = gtk_builder_new_from_resource ("/org/gnome/nautilus/ui/nautilus-file-properties-change-permissions.ui");
+
+ dialog = GTK_WIDGET (gtk_builder_get_object (change_permissions_builder, "change_permissions_dialog"));
+ gtk_window_set_transient_for (GTK_WINDOW (dialog), GTK_WINDOW (self));
+
+ /* Owner Permissions */
+ combo = GTK_COMBO_BOX (gtk_builder_get_object (change_permissions_builder, "file_owner_combo"));
+ setup_permissions_combo_box (combo, PERMISSION_USER, FILES_ONLY);
+ self->change_permission_combos = g_list_prepend (self->change_permission_combos,
+ combo);
+ set_active_from_umask (combo, PERMISSION_USER, FILES_ONLY);
+
+ combo = GTK_COMBO_BOX (gtk_builder_get_object (change_permissions_builder, "folder_owner_combo"));
+ setup_permissions_combo_box (combo, PERMISSION_USER, FOLDERS_ONLY);
+ self->change_permission_combos = g_list_prepend (self->change_permission_combos,
+ combo);
+ set_active_from_umask (combo, PERMISSION_USER, FOLDERS_ONLY);
+
+ /* Group Permissions */
+ combo = GTK_COMBO_BOX (gtk_builder_get_object (change_permissions_builder, "file_group_combo"));
+ setup_permissions_combo_box (combo, PERMISSION_GROUP, FILES_ONLY);
+ self->change_permission_combos = g_list_prepend (self->change_permission_combos,
+ combo);
+ set_active_from_umask (combo, PERMISSION_GROUP, FILES_ONLY);
+
+ combo = GTK_COMBO_BOX (gtk_builder_get_object (change_permissions_builder, "folder_group_combo"));
+ setup_permissions_combo_box (combo, PERMISSION_GROUP, FOLDERS_ONLY);
+ self->change_permission_combos = g_list_prepend (self->change_permission_combos,
+ combo);
+ set_active_from_umask (combo, PERMISSION_GROUP, FOLDERS_ONLY);
+
+ /* Others Permissions */
+ combo = GTK_COMBO_BOX (gtk_builder_get_object (change_permissions_builder, "file_other_combo"));
+ setup_permissions_combo_box (combo, PERMISSION_OTHER, FILES_ONLY);
+ self->change_permission_combos = g_list_prepend (self->change_permission_combos,
+ combo);
+ set_active_from_umask (combo, PERMISSION_OTHER, FILES_ONLY);
+
+ combo = GTK_COMBO_BOX (gtk_builder_get_object (change_permissions_builder, "folder_other_combo"));
+ setup_permissions_combo_box (combo, PERMISSION_OTHER, FOLDERS_ONLY);
+ self->change_permission_combos = g_list_prepend (self->change_permission_combos,
+ combo);
+ set_active_from_umask (combo, PERMISSION_OTHER, FOLDERS_ONLY);
+
+ g_signal_connect (dialog, "response", G_CALLBACK (on_change_permissions_response), self);
+ gtk_widget_show (dialog);
+}
+
+static void
+setup_permissions_page (NautilusPropertiesWindow *self)
+{
+ GList *file_list;
+
+ file_list = self->original_files;
+
+ self->initial_permissions = NULL;
+
+ if (all_can_get_permissions (file_list) && all_can_get_permissions (self->target_files))
+ {
+ self->initial_permissions = get_initial_permissions (self->target_files);
+ self->has_recursive_apply = files_has_changable_permissions_directory (self);
+
+ if (!all_can_set_permissions (file_list))
+ {
+ gtk_widget_show (self->not_the_owner_label);
+ gtk_widget_show (self->bottom_prompt_seperator);
+ }
+
+ gtk_stack_set_visible_child_name (GTK_STACK (self->permissions_stack), "permissions-box");
+ create_simple_permissions (self);
+
+#ifdef HAVE_SELINUX
+ gtk_widget_show (self->security_context_list_box);
+
+ /* Stash a copy of the file attribute name in this field for the callback's sake. */
+ g_object_set_data_full (G_OBJECT (self->security_context_value_label), "file_attribute",
+ g_strdup ("selinux_context"), g_free);
+
+ self->value_fields = g_list_prepend (self->value_fields,
+ self->security_context_value_label);
+#endif
+
+ if (self->has_recursive_apply)
+ {
+ gtk_widget_show (self->change_permissions_button_box);
+ g_signal_connect (self->change_permissions_button, "clicked",
+ G_CALLBACK (on_change_permissions_clicked),
+ self);
+ }
+ }
+ else
+ {
+ /*
+ * This if block only gets executed if its a single file window,
+ * in which case the label text needs to be different from the
+ * default label text. The default label text for a multifile
+ * window is set in nautilus-properties-window.ui so no else block.
+ */
+ if (!is_multi_file_window (self))
+ {
+ g_autofree gchar *file_name = NULL;
+ g_autofree gchar *prompt_text = NULL;
+
+ file_name = nautilus_file_get_display_name (get_target_file (self));
+ prompt_text = g_strdup_printf (_("The permissions of “%s” could not be determined."), file_name);
+ adw_status_page_set_description (ADW_STATUS_PAGE (self->unknown_permissions_page), prompt_text);
+ }
+
+ gtk_stack_set_visible_child_name (GTK_STACK (self->permissions_stack), "permission-indeterminable");
+ }
+}
+
+static void
+refresh_extension_model_pages (NautilusPropertiesWindow *self)
+{
+ g_autoptr (GListStore) extensions_list = g_list_store_new (NAUTILUS_TYPE_PROPERTIES_MODEL);
+ g_autolist (NautilusPropertiesModel) all_models = NULL;
+ g_autolist (GObject) providers =
+ nautilus_module_get_extensions_for_type (NAUTILUS_TYPE_PROPERTIES_MODEL_PROVIDER);
+
+ for (GList *l = providers; l != NULL; l = l->next)
+ {
+ GList *models = nautilus_properties_model_provider_get_models (l->data, self->original_files);
+
+ all_models = g_list_concat (all_models, models);
+ }
+
+ for (GList *l = all_models; l != NULL; l = l->next)
+ {
+ g_list_store_append (extensions_list, NAUTILUS_PROPERTIES_MODEL (l->data));
+ }
+
+ gtk_widget_set_visible (self->extension_models_list_box,
+ g_list_model_get_n_items (G_LIST_MODEL (extensions_list)) > 0);
+
+ gtk_list_box_bind_model (GTK_LIST_BOX (self->extension_models_list_box),
+ G_LIST_MODEL (extensions_list),
+ (GtkListBoxCreateWidgetFunc) add_extension_model_page,
+ self,
+ NULL);
+}
+
+static gboolean
+should_show_permissions (NautilusPropertiesWindow *self)
+{
+ GList *l;
+
+ /* Don't show permissions for Trash and Computer since they're not
+ * really file system objects.
+ */
+ for (l = self->original_files; l != NULL; l = l->next)
+ {
+ if (nautilus_file_is_in_trash (NAUTILUS_FILE (l->data)) ||
+ nautilus_file_is_in_recent (NAUTILUS_FILE (l->data)))
+ {
+ return FALSE;
+ }
+ }
+
+ return TRUE;
+}
+
+static char *
+get_pending_key (GList *file_list)
+{
+ GList *uris = NULL;
+ GList *l;
+ GString *key;
+ char *ret;
+
+ uris = NULL;
+ for (l = file_list; l != NULL; l = l->next)
+ {
+ uris = g_list_prepend (uris, nautilus_file_get_uri (NAUTILUS_FILE (l->data)));
+ }
+ uris = g_list_sort (uris, (GCompareFunc) strcmp);
+
+ key = g_string_new ("");
+ for (l = uris; l != NULL; l = l->next)
+ {
+ g_string_append (key, l->data);
+ g_string_append (key, ";");
+ }
+
+ g_list_free_full (uris, g_free);
+
+ ret = key->str;
+ g_string_free (key, FALSE);
+
+ return ret;
+}
+
+static StartupData *
+startup_data_new (GList *original_files,
+ GList *target_files,
+ const char *pending_key,
+ GtkWidget *parent_widget,
+ GtkWindow *parent_window,
+ const char *startup_id,
+ NautilusPropertiesWindowCallback callback,
+ gpointer callback_data,
+ NautilusPropertiesWindow *window)
+{
+ StartupData *data;
+ GList *l;
+
+ data = g_new0 (StartupData, 1);
+ data->original_files = nautilus_file_list_copy (original_files);
+ data->target_files = nautilus_file_list_copy (target_files);
+ data->parent_widget = parent_widget;
+ data->parent_window = parent_window;
+ data->startup_id = g_strdup (startup_id);
+ data->pending_key = g_strdup (pending_key);
+ data->pending_files = g_hash_table_new (g_direct_hash,
+ g_direct_equal);
+ data->callback = callback;
+ data->callback_data = callback_data;
+ data->window = window;
+
+ for (l = data->target_files; l != NULL; l = l->next)
+ {
+ g_hash_table_insert (data->pending_files, l->data, l->data);
+ }
+
+ return data;
+}
+
+static void
+startup_data_free (StartupData *data)
+{
+ nautilus_file_list_free (data->original_files);
+ nautilus_file_list_free (data->target_files);
+ g_hash_table_destroy (data->pending_files);
+ g_free (data->pending_key);
+ g_free (data->startup_id);
+ g_free (data);
+}
+
+static void
+file_changed_callback (NautilusFile *file,
+ gpointer user_data)
+{
+ NautilusPropertiesWindow *self = NAUTILUS_PROPERTIES_WINDOW (user_data);
+
+ if (!g_list_find (self->changed_files, file))
+ {
+ nautilus_file_ref (file);
+ self->changed_files = g_list_prepend (self->changed_files, file);
+ schedule_files_update (self);
+ }
+}
+
+static NautilusPropertiesWindow *
+create_properties_window (StartupData *startup_data)
+{
+ NautilusPropertiesWindow *window;
+ GList *l;
+
+ window = NAUTILUS_PROPERTIES_WINDOW (g_object_new (NAUTILUS_TYPE_PROPERTIES_WINDOW,
+ NULL));
+
+ window->original_files = nautilus_file_list_copy (startup_data->original_files);
+
+ window->target_files = nautilus_file_list_copy (startup_data->target_files);
+
+ if (startup_data->parent_widget)
+ {
+ gtk_window_set_display (GTK_WINDOW (window),
+ gtk_widget_get_display (startup_data->parent_widget));
+ }
+
+ if (startup_data->parent_window)
+ {
+ gtk_window_set_transient_for (GTK_WINDOW (window), startup_data->parent_window);
+ }
+
+ if (startup_data->startup_id)
+ {
+ gtk_window_set_startup_id (GTK_WINDOW (window), startup_data->startup_id);
+ }
+
+ /* Start monitoring the file attributes we display. Note that some
+ * of the attributes are for the original file, and some for the
+ * target files.
+ */
+
+ for (l = window->original_files; l != NULL; l = l->next)
+ {
+ NautilusFile *file;
+ NautilusFileAttributes attributes;
+
+ file = NAUTILUS_FILE (l->data);
+
+ attributes =
+ NAUTILUS_FILE_ATTRIBUTES_FOR_ICON |
+ NAUTILUS_FILE_ATTRIBUTE_INFO;
+
+ nautilus_file_monitor_add (file,
+ &window->original_files,
+ attributes);
+ }
+
+ for (l = window->target_files; l != NULL; l = l->next)
+ {
+ NautilusFile *file;
+ NautilusFileAttributes attributes;
+
+ file = NAUTILUS_FILE (l->data);
+
+ attributes = 0;
+ if (nautilus_file_is_directory (file))
+ {
+ attributes |= NAUTILUS_FILE_ATTRIBUTE_DEEP_COUNTS;
+ }
+
+ attributes |= NAUTILUS_FILE_ATTRIBUTE_INFO;
+ nautilus_file_monitor_add (file, &window->target_files, attributes);
+ }
+
+ for (l = window->target_files; l != NULL; l = l->next)
+ {
+ g_signal_connect_object (NAUTILUS_FILE (l->data),
+ "changed",
+ G_CALLBACK (file_changed_callback),
+ G_OBJECT (window),
+ 0);
+ }
+
+ for (l = window->original_files; l != NULL; l = l->next)
+ {
+ g_signal_connect_object (NAUTILUS_FILE (l->data),
+ "changed",
+ G_CALLBACK (file_changed_callback),
+ G_OBJECT (window),
+ 0);
+ }
+
+ /* Create the pages. */
+ setup_basic_page (window);
+
+ if (should_show_permissions (window))
+ {
+ setup_permissions_page (window);
+ gtk_widget_show (window->permissions_navigation_row);
+ }
+
+ if (should_show_exectution_switch (window))
+ {
+ gtk_widget_show (GTK_WIDGET (window->execution_row));
+ }
+
+ /* Add available extension models pages */
+ refresh_extension_model_pages (window);
+
+ /* Update from initial state */
+ properties_window_update (window, NULL);
+
+ return window;
+}
+
+static GList *
+get_target_file_list (GList *original_files)
+{
+ return g_list_copy_deep (original_files,
+ (GCopyFunc) get_target_file_for_original_file,
+ NULL);
+}
+
+static void
+properties_window_finish (StartupData *data)
+{
+ gboolean cancel_timed_wait;
+
+ if (data->parent_widget != NULL)
+ {
+ g_signal_handlers_disconnect_by_data (data->parent_widget,
+ data);
+ }
+ if (data->window != NULL)
+ {
+ g_signal_handlers_disconnect_by_data (data->window,
+ data);
+ }
+
+ cancel_timed_wait = (data->window == NULL && !data->cancelled);
+ remove_pending (data, TRUE, cancel_timed_wait);
+
+ startup_data_free (data);
+}
+
+static void
+cancel_create_properties_window_callback (gpointer callback_data)
+{
+ StartupData *data;
+
+ data = callback_data;
+ data->cancelled = TRUE;
+
+ properties_window_finish (data);
+}
+
+static void
+parent_widget_destroyed_callback (GtkWidget *widget,
+ gpointer callback_data)
+{
+ g_assert (widget == ((StartupData *) callback_data)->parent_widget);
+
+ properties_window_finish ((StartupData *) callback_data);
+}
+
+static void
+cancel_call_when_ready_callback (gpointer key,
+ gpointer value,
+ gpointer user_data)
+{
+ nautilus_file_cancel_call_when_ready
+ (NAUTILUS_FILE (key),
+ is_directory_ready_callback,
+ user_data);
+}
+
+static void
+remove_pending (StartupData *startup_data,
+ gboolean cancel_call_when_ready,
+ gboolean cancel_timed_wait)
+{
+ if (cancel_call_when_ready)
+ {
+ g_hash_table_foreach (startup_data->pending_files,
+ cancel_call_when_ready_callback,
+ startup_data);
+ }
+ if (cancel_timed_wait)
+ {
+ eel_timed_wait_stop
+ (cancel_create_properties_window_callback, startup_data);
+ }
+ if (startup_data->pending_key != NULL)
+ {
+ g_hash_table_remove (pending_lists, startup_data->pending_key);
+ }
+}
+
+static gboolean
+widget_on_destroy (GtkWidget *widget,
+ gpointer user_data)
+{
+ StartupData *data = (StartupData *) user_data;
+
+
+ if (data->callback != NULL)
+ {
+ data->callback (data->callback_data);
+ }
+
+ properties_window_finish (data);
+
+ return GDK_EVENT_PROPAGATE;
+}
+
+static void
+is_directory_ready_callback (NautilusFile *file,
+ gpointer data)
+{
+ StartupData *startup_data;
+
+ startup_data = data;
+
+ g_hash_table_remove (startup_data->pending_files, file);
+
+ if (g_hash_table_size (startup_data->pending_files) == 0)
+ {
+ NautilusPropertiesWindow *new_window;
+
+ new_window = create_properties_window (startup_data);
+
+ startup_data->window = new_window;
+
+ remove_pending (startup_data, FALSE, TRUE);
+
+ gtk_window_present (GTK_WINDOW (new_window));
+ g_signal_connect (GTK_WIDGET (new_window), "destroy",
+ G_CALLBACK (widget_on_destroy), startup_data);
+
+ /* We wish the label to be selectable, but not selected by default. */
+ gtk_label_select_region (GTK_LABEL (new_window->name_value_label), -1, -1);
+ }
+}
+
+void
+nautilus_properties_window_present (GList *original_files,
+ GtkWidget *parent_widget,
+ const gchar *startup_id,
+ NautilusPropertiesWindowCallback callback,
+ gpointer callback_data)
+{
+ GList *l, *next;
+ GtkWindow *parent_window;
+ StartupData *startup_data;
+ g_autolist (NautilusFile) target_files = NULL;
+ g_autofree char *pending_key = NULL;
+
+ g_return_if_fail (original_files != NULL);
+ g_return_if_fail (parent_widget == NULL || GTK_IS_WIDGET (parent_widget));
+
+ if (pending_lists == NULL)
+ {
+ pending_lists = g_hash_table_new (g_str_hash, g_str_equal);
+ }
+
+ pending_key = get_pending_key (original_files);
+
+ /* Look to see if we're already waiting for a window for this file. */
+ if (g_hash_table_lookup (pending_lists, pending_key) != NULL)
+ {
+ /* FIXME: No callback is done if this happen. In practice, it's a quite
+ * corner case
+ */
+ return;
+ }
+
+ target_files = get_target_file_list (original_files);
+
+ if (parent_widget)
+ {
+ parent_window = GTK_WINDOW (gtk_widget_get_ancestor (parent_widget, GTK_TYPE_WINDOW));
+ }
+ else
+ {
+ parent_window = NULL;
+ }
+
+ startup_data = startup_data_new (original_files,
+ target_files,
+ pending_key,
+ parent_widget,
+ parent_window,
+ startup_id,
+ callback,
+ callback_data,
+ NULL);
+
+ /* Wait until we can tell whether it's a directory before showing, since
+ * some one-time layout decisions depend on that info.
+ */
+
+ g_hash_table_insert (pending_lists, startup_data->pending_key, startup_data->pending_key);
+ if (parent_widget)
+ {
+ g_signal_connect (parent_widget, "destroy",
+ G_CALLBACK (parent_widget_destroyed_callback), startup_data);
+ }
+
+ eel_timed_wait_start
+ (cancel_create_properties_window_callback,
+ startup_data,
+ _("Creating Properties window."),
+ parent_window == NULL ? NULL : GTK_WINDOW (parent_window));
+
+ for (l = startup_data->target_files; l != NULL; l = next)
+ {
+ next = l->next;
+ nautilus_file_call_when_ready
+ (NAUTILUS_FILE (l->data),
+ NAUTILUS_FILE_ATTRIBUTE_INFO,
+ is_directory_ready_callback,
+ startup_data);
+ }
+}
+
+static void
+real_dispose (GObject *object)
+{
+ NautilusPropertiesWindow *self;
+
+ self = NAUTILUS_PROPERTIES_WINDOW (object);
+
+ unschedule_or_cancel_group_change (self);
+ unschedule_or_cancel_owner_change (self);
+
+ g_list_foreach (self->original_files,
+ (GFunc) nautilus_file_monitor_remove,
+ &self->original_files);
+ g_clear_list (&self->original_files, (GDestroyNotify) nautilus_file_unref);
+
+ g_list_foreach (self->target_files,
+ (GFunc) nautilus_file_monitor_remove,
+ &self->target_files);
+ g_clear_list (&self->target_files, (GDestroyNotify) nautilus_file_unref);
+
+ g_clear_list (&self->changed_files, (GDestroyNotify) nautilus_file_unref);
+
+ g_clear_handle_id (&self->deep_count_spinner_timeout_id, g_source_remove);
+
+ while (self->deep_count_files)
+ {
+ stop_deep_count_for_file (self, self->deep_count_files->data);
+ }
+
+ g_clear_list (&self->permission_rows, NULL);
+
+ g_clear_list (&self->change_permission_combos, NULL);
+
+ g_clear_pointer (&self->initial_permissions, g_hash_table_destroy);
+
+ g_clear_list (&self->value_fields, NULL);
+
+ g_clear_handle_id (&self->update_directory_contents_timeout_id, g_source_remove);
+ g_clear_handle_id (&self->update_files_timeout_id, g_source_remove);
+
+ G_OBJECT_CLASS (nautilus_properties_window_parent_class)->dispose (object);
+}
+
+static void
+real_finalize (GObject *object)
+{
+ NautilusPropertiesWindow *self;
+
+ self = NAUTILUS_PROPERTIES_WINDOW (object);
+
+ g_list_free_full (self->mime_list, g_free);
+
+ G_OBJECT_CLASS (nautilus_properties_window_parent_class)->finalize (object);
+}
+
+/* icon selection callback to set the image of the file object to the selected file */
+static void
+set_icon (const char *icon_uri,
+ NautilusPropertiesWindow *self)
+{
+ NautilusFile *file;
+ g_autofree gchar *icon_path = NULL;
+
+ g_assert (icon_uri != NULL);
+ g_assert (NAUTILUS_IS_PROPERTIES_WINDOW (self));
+
+ icon_path = g_filename_from_uri (icon_uri, NULL, NULL);
+ /* we don't allow remote URIs */
+ if (icon_path != NULL)
+ {
+ GList *l;
+
+ for (l = self->original_files; l != NULL; l = l->next)
+ {
+ g_autofree gchar *file_uri = NULL;
+ g_autoptr (GFile) file_location = NULL;
+ g_autoptr (GFile) icon_location = NULL;
+ g_autofree gchar *real_icon_uri = NULL;
+
+ file = NAUTILUS_FILE (l->data);
+ file_uri = nautilus_file_get_uri (file);
+ file_location = nautilus_file_get_location (file);
+ icon_location = g_file_new_for_uri (icon_uri);
+
+ /* ’Tis a little bit of a misnomer. Actually a path. */
+ real_icon_uri = g_file_get_relative_path (icon_location,
+ file_location);
+
+ if (real_icon_uri == NULL)
+ {
+ real_icon_uri = g_strdup (icon_uri);
+ }
+
+ nautilus_file_set_metadata (file, NAUTILUS_METADATA_KEY_CUSTOM_ICON, NULL, real_icon_uri);
+ }
+ }
+}
+
+static void
+custom_icon_file_chooser_response_cb (GtkDialog *dialog,
+ gint response,
+ NautilusPropertiesWindow *self)
+{
+ switch (response)
+ {
+ case GTK_RESPONSE_NO:
+ {
+ reset_icon (self);
+ }
+ break;
+
+ case GTK_RESPONSE_OK:
+ {
+ g_autoptr (GFile) location = NULL;
+ g_autofree gchar *uri = NULL;
+
+ location = gtk_file_chooser_get_file (GTK_FILE_CHOOSER (dialog));
+ if (location != NULL)
+ {
+ uri = g_file_get_uri (location);
+ set_icon (uri, self);
+ }
+ else
+ {
+ reset_icon (self);
+ }
+ }
+ break;
+
+ default:
+ {
+ }
+ break;
+ }
+
+ gtk_widget_hide (GTK_WIDGET (dialog));
+}
+
+static void
+select_image_button_callback (GtkWidget *widget,
+ NautilusPropertiesWindow *self)
+{
+ GtkWidget *dialog;
+ GtkFileFilter *filter;
+ GList *l;
+ NautilusFile *file;
+ gboolean revert_is_sensitive;
+
+ g_assert (NAUTILUS_IS_PROPERTIES_WINDOW (self));
+
+ dialog = self->icon_chooser;
+
+ if (dialog == NULL)
+ {
+ g_autoptr (GFile) pictures_location = NULL;
+ dialog = gtk_file_chooser_dialog_new (_("Select Custom Icon"), GTK_WINDOW (self),
+ GTK_FILE_CHOOSER_ACTION_OPEN,
+ _("_Revert"), GTK_RESPONSE_NO,
+ _("_Cancel"), GTK_RESPONSE_CANCEL,
+ _("_Open"), GTK_RESPONSE_OK,
+ NULL);
+ pictures_location = g_file_new_for_path (g_get_user_special_dir (G_USER_DIRECTORY_PICTURES));
+ gtk_file_chooser_add_shortcut_folder (GTK_FILE_CHOOSER (dialog),
+ pictures_location,
+ NULL);
+ gtk_window_set_destroy_with_parent (GTK_WINDOW (dialog), TRUE);
+ gtk_window_set_modal (GTK_WINDOW (dialog), TRUE);
+
+ filter = gtk_file_filter_new ();
+ gtk_file_filter_add_pixbuf_formats (filter);
+ gtk_file_chooser_set_filter (GTK_FILE_CHOOSER (dialog), filter);
+
+ self->icon_chooser = dialog;
+
+ g_object_add_weak_pointer (G_OBJECT (dialog),
+ (gpointer *) &self->icon_chooser);
+ }
+
+ /* it's likely that the user wants to pick an icon that is inside a local directory */
+ if (g_list_length (self->original_files) == 1)
+ {
+ file = NAUTILUS_FILE (self->original_files->data);
+
+ if (nautilus_file_is_directory (file))
+ {
+ g_autoptr (GFile) image_location = NULL;
+
+ image_location = nautilus_file_get_location (file);
+
+ if (image_location != NULL)
+ {
+ gtk_file_chooser_set_current_folder (GTK_FILE_CHOOSER (dialog),
+ image_location,
+ NULL);
+ }
+ }
+ }
+
+ revert_is_sensitive = FALSE;
+ for (l = self->original_files; l != NULL; l = l->next)
+ {
+ g_autofree gchar *image_path = NULL;
+
+ file = NAUTILUS_FILE (l->data);
+ image_path = nautilus_file_get_metadata (file, NAUTILUS_METADATA_KEY_CUSTOM_ICON, NULL);
+ revert_is_sensitive = (image_path != NULL);
+
+ if (revert_is_sensitive)
+ {
+ break;
+ }
+ }
+ gtk_dialog_set_response_sensitive (GTK_DIALOG (dialog), GTK_RESPONSE_NO, revert_is_sensitive);
+
+ g_signal_connect (dialog, "response",
+ G_CALLBACK (custom_icon_file_chooser_response_cb), self);
+ gtk_widget_show (dialog);
+}
+
+static void
+nautilus_properties_window_class_init (NautilusPropertiesWindowClass *klass)
+{
+ GtkWidgetClass *widget_class;
+ GObjectClass *oclass;
+
+ widget_class = GTK_WIDGET_CLASS (klass);
+ oclass = G_OBJECT_CLASS (klass);
+ oclass->dispose = real_dispose;
+ oclass->finalize = real_finalize;
+
+ gtk_widget_class_add_binding (widget_class,
+ GDK_KEY_Escape, 0,
+ (GtkShortcutFunc) gtk_window_close, NULL);
+
+ gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/nautilus/ui/nautilus-properties-window.ui");
+
+ gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, page_stack);
+ gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, icon_stack);
+ gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, icon_image);
+ gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, icon_button);
+ gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, icon_button_image);
+ gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, star_button);
+ gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, name_value_label);
+ gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, type_value_label);
+ gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, type_file_system_label);
+ gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, size_value_label);
+ gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, contents_box);
+ gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, contents_value_label);
+ gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, contents_spinner);
+ gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, bottom_prompt_seperator);
+ gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, disk_list_box);
+ gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, disk_space_level_bar);
+ gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, disk_space_used_value);
+ gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, disk_space_free_value);
+ gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, disk_space_capacity_value);
+ gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, locations_list_box);
+ gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, link_target_row);
+ gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, link_target_value_label);
+ gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, parent_folder_row);
+ gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, parent_folder_value_label);
+ gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, trashed_list_box);
+ gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, trashed_on_value_label);
+ gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, original_folder_value_label);
+ gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, times_list_box);
+ gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, modified_row);
+ gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, modified_value_label);
+ gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, created_row);
+ gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, created_value_label);
+ gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, accessed_row);
+ gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, accessed_value_label);
+ gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, permissions_navigation_row);
+ gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, permissions_value_label);
+ gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, extension_models_list_box);
+ gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, free_space_value_label);
+ gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, permissions_stack);
+ gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, not_the_owner_label);
+ gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, unknown_permissions_page);
+ gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, owner_row);
+ gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, owner_access_row);
+ gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, owner_folder_access_row);
+ gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, owner_file_access_row);
+ gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, group_row);
+ gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, group_access_row);
+ gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, group_folder_access_row);
+ gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, group_file_access_row);
+ gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, others_access_row);
+ gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, others_folder_access_row);
+ gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, others_file_access_row);
+ gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, execution_row);
+ gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, execution_switch);
+ gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, security_context_list_box);
+ gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, security_context_value_label);
+ gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, change_permissions_button_box);
+ gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, change_permissions_button);
+
+ gtk_widget_class_bind_template_callback (widget_class, star_clicked);
+ gtk_widget_class_bind_template_callback (widget_class, open_in_disks);
+ gtk_widget_class_bind_template_callback (widget_class, open_parent_folder);
+ gtk_widget_class_bind_template_callback (widget_class, open_link_target);
+ gtk_widget_class_bind_template_callback (widget_class, navigate_main_page);
+ gtk_widget_class_bind_template_callback (widget_class, navigate_permissions_page);
+}
+
+static void
+nautilus_properties_window_init (NautilusPropertiesWindow *self)
+{
+ gtk_widget_init_template (GTK_WIDGET (self));
+}
diff --git a/src/nautilus-properties-window.h b/src/nautilus-properties-window.h
new file mode 100644
index 0000000..4b769bd
--- /dev/null
+++ b/src/nautilus-properties-window.h
@@ -0,0 +1,41 @@
+
+/* fm-properties-window.h - interface for window that lets user modify
+ icon properties
+
+ Copyright (C) 2000 Eazel, Inc.
+
+ The Gnome Library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Library General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version.
+
+ The Gnome Library 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
+ Library General Public License for more details.
+
+ You should have received a copy of the GNU Library General Public
+ License along with the Gnome Library; see the file COPYING.LIB. If not,
+ see <http://www.gnu.org/licenses/>.
+
+ Authors: Darin Adler <darin@bentspoon.com>
+*/
+
+#pragma once
+
+#include <gtk/gtk.h>
+#include <libadwaita-1/adwaita.h>
+
+#define NAUTILUS_TYPE_PROPERTIES_WINDOW (nautilus_properties_window_get_type ())
+
+G_DECLARE_FINAL_TYPE (NautilusPropertiesWindow, nautilus_properties_window,
+ NAUTILUS, PROPERTIES_WINDOW,
+ AdwWindow)
+
+typedef void (* NautilusPropertiesWindowCallback) (gpointer callback_data);
+
+void nautilus_properties_window_present (GList *files,
+ GtkWidget *parent_widget,
+ const gchar *startup_id,
+ NautilusPropertiesWindowCallback callback,
+ gpointer callback_data);
diff --git a/src/nautilus-query-editor.c b/src/nautilus-query-editor.c
new file mode 100644
index 0000000..a7fe84f
--- /dev/null
+++ b/src/nautilus-query-editor.c
@@ -0,0 +1,844 @@
+/*
+ * Copyright (C) 2005 Red Hat, Inc.
+ *
+ * Nautilus is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * Nautilus is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; see the file COPYING. If not,
+ * see <http://www.gnu.org/licenses/>.
+ *
+ * Author: Alexander Larsson <alexl@redhat.com>
+ * Georges Basile Stavracas Neto <gbsneto@gnome.org>
+ *
+ */
+
+#include "nautilus-query-editor.h"
+
+#include <gdk/gdkkeysyms.h>
+#include <gio/gio.h>
+#include <glib/gi18n.h>
+#include <gtk/gtk.h>
+#include <string.h>
+
+#include "nautilus-file.h"
+#include "nautilus-file-utilities.h"
+#include "nautilus-global-preferences.h"
+#include "nautilus-search-directory.h"
+#include "nautilus-search-popover.h"
+#include "nautilus-mime-actions.h"
+#include "nautilus-ui-utilities.h"
+
+struct _NautilusQueryEditor
+{
+ GtkWidget parent_instance;
+
+ GtkWidget *tags_box;
+ GtkWidget *text;
+ GtkWidget *clear_icon;
+ GtkWidget *popover;
+ GtkWidget *dropdown_button;
+
+ GtkWidget *mime_types_tag;
+ GtkWidget *date_range_tag;
+
+ guint search_changed_timeout_id;
+ gboolean change_frozen;
+
+ GFile *location;
+
+ NautilusQuery *query;
+};
+
+enum
+{
+ ACTIVATED,
+ FOCUS_VIEW,
+ CHANGED,
+ CANCEL,
+ LAST_SIGNAL
+};
+
+enum
+{
+ PROP_0,
+ PROP_LOCATION,
+ PROP_QUERY,
+ LAST_PROP
+};
+
+static guint signals[LAST_SIGNAL];
+
+static void entry_activate_cb (GtkWidget *entry,
+ NautilusQueryEditor *editor);
+static void entry_changed_cb (GtkWidget *entry,
+ NautilusQueryEditor *editor);
+static void nautilus_query_editor_changed (NautilusQueryEditor *editor);
+
+G_DEFINE_TYPE (NautilusQueryEditor, nautilus_query_editor, GTK_TYPE_WIDGET);
+
+/* A hunt-and-peck typist types at 25-35 words per minute, which means 342 to 480ms between strokes.
+ * An average touch typist types at 50-70 wpm, which means 171 to 240ms "under ideal conditions".
+ * A 150ms default search triggering delay is too short even for fast typists in general,
+ * so wait 400ms after typing, to improve performance by not spamming search engines: */
+#define SEARCH_CHANGED_TIMEOUT 400
+
+static void
+update_fts_sensitivity (NautilusQueryEditor *editor)
+{
+ gboolean fts_sensitive = TRUE;
+
+ if (editor->location)
+ {
+ g_autoptr (NautilusFile) file = NULL;
+ g_autofree gchar *uri = NULL;
+
+ file = nautilus_file_get (editor->location);
+ uri = g_file_get_uri (editor->location);
+
+ fts_sensitive = !nautilus_file_is_other_locations (file) &&
+ !g_str_has_prefix (uri, "network://") &&
+ !(nautilus_file_is_remote (file) &&
+ location_settings_search_get_recursive_for_location (editor->location) == NAUTILUS_QUERY_RECURSIVE_NEVER);
+ nautilus_search_popover_set_fts_sensitive (NAUTILUS_SEARCH_POPOVER (editor->popover),
+ fts_sensitive);
+ }
+}
+
+static void
+recursive_search_preferences_changed (GSettings *settings,
+ gchar *key,
+ NautilusQueryEditor *editor)
+{
+ NautilusQueryRecursive recursive;
+
+ if (!editor->query)
+ {
+ return;
+ }
+
+ recursive = location_settings_search_get_recursive ();
+ if (recursive != nautilus_query_get_recursive (editor->query))
+ {
+ nautilus_query_set_recursive (editor->query, recursive);
+ nautilus_query_editor_changed (editor);
+ }
+
+ update_fts_sensitivity (editor);
+}
+
+
+static void
+nautilus_query_editor_dispose (GObject *object)
+{
+ NautilusQueryEditor *editor;
+
+ editor = NAUTILUS_QUERY_EDITOR (object);
+
+ g_clear_handle_id (&editor->search_changed_timeout_id, g_source_remove);
+
+ gtk_widget_unparent (gtk_widget_get_first_child (GTK_WIDGET (editor)));
+ g_clear_pointer (&editor->tags_box, gtk_widget_unparent);
+ g_clear_pointer (&editor->text, gtk_widget_unparent);
+ g_clear_pointer (&editor->dropdown_button, gtk_widget_unparent);
+ g_clear_pointer (&editor->clear_icon, gtk_widget_unparent);
+
+ g_clear_object (&editor->location);
+ g_clear_object (&editor->query);
+
+ g_signal_handlers_disconnect_by_func (nautilus_preferences,
+ recursive_search_preferences_changed,
+ object);
+
+ G_OBJECT_CLASS (nautilus_query_editor_parent_class)->dispose (object);
+}
+
+static gboolean
+nautilus_query_editor_grab_focus (GtkWidget *widget)
+{
+ NautilusQueryEditor *editor;
+
+ editor = NAUTILUS_QUERY_EDITOR (widget);
+
+ if (gtk_widget_get_visible (widget) && !gtk_widget_is_focus (editor->text))
+ {
+ return gtk_text_grab_focus_without_selecting (GTK_TEXT (editor->text));
+ }
+
+ return FALSE;
+}
+
+static void
+nautilus_query_editor_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ NautilusQueryEditor *editor;
+
+ editor = NAUTILUS_QUERY_EDITOR (object);
+
+ switch (prop_id)
+ {
+ case PROP_LOCATION:
+ {
+ g_value_set_object (value, editor->location);
+ }
+ break;
+
+ case PROP_QUERY:
+ {
+ g_value_set_object (value, editor->query);
+ }
+ break;
+
+ default:
+ {
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+ }
+}
+
+static void
+nautilus_query_editor_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ NautilusQueryEditor *self;
+
+ self = NAUTILUS_QUERY_EDITOR (object);
+
+ switch (prop_id)
+ {
+ case PROP_LOCATION:
+ {
+ nautilus_query_editor_set_location (self, g_value_get_object (value));
+ }
+ break;
+
+ case PROP_QUERY:
+ {
+ nautilus_query_editor_set_query (self, g_value_get_object (value));
+ }
+ break;
+
+ default:
+ {
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+ }
+}
+
+static void
+nautilus_query_editor_finalize (GObject *object)
+{
+ G_OBJECT_CLASS (nautilus_query_editor_parent_class)->finalize (object);
+}
+
+static void
+nautilus_query_editor_class_init (NautilusQueryEditorClass *class)
+{
+ GObjectClass *gobject_class;
+ GtkWidgetClass *widget_class;
+ g_autoptr (GtkShortcut) shortcut = NULL;
+
+ gobject_class = G_OBJECT_CLASS (class);
+ gobject_class->finalize = nautilus_query_editor_finalize;
+ gobject_class->dispose = nautilus_query_editor_dispose;
+ gobject_class->get_property = nautilus_query_editor_get_property;
+ gobject_class->set_property = nautilus_query_editor_set_property;
+
+ widget_class = GTK_WIDGET_CLASS (class);
+ widget_class->grab_focus = nautilus_query_editor_grab_focus;
+
+ signals[CHANGED] =
+ g_signal_new ("changed",
+ G_TYPE_FROM_CLASS (class),
+ G_SIGNAL_RUN_LAST,
+ 0,
+ NULL, NULL,
+ g_cclosure_marshal_generic,
+ G_TYPE_NONE, 2, NAUTILUS_TYPE_QUERY, G_TYPE_BOOLEAN);
+
+ signals[CANCEL] =
+ g_signal_new ("cancel",
+ G_TYPE_FROM_CLASS (class),
+ G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
+ 0,
+ NULL, NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+
+ signals[ACTIVATED] =
+ g_signal_new ("activated",
+ G_TYPE_FROM_CLASS (class),
+ G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
+ 0,
+ NULL, NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+
+ signals[FOCUS_VIEW] =
+ g_signal_new ("focus-view",
+ G_TYPE_FROM_CLASS (class),
+ G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
+ 0,
+ NULL, NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+
+ shortcut = gtk_shortcut_new (gtk_keyval_trigger_new (GDK_KEY_Down, 0),
+ gtk_signal_action_new ("focus-view"));
+ gtk_widget_class_add_shortcut (widget_class, shortcut);
+
+ gtk_widget_class_add_binding_signal (widget_class,
+ GDK_KEY_Escape, 0, "cancel",
+ NULL);
+
+ /**
+ * NautilusQueryEditor::location:
+ *
+ * The current location of the query editor.
+ */
+ g_object_class_install_property (gobject_class,
+ PROP_LOCATION,
+ g_param_spec_object ("location",
+ "Location of the search",
+ "The current location of the editor",
+ G_TYPE_FILE,
+ G_PARAM_READWRITE));
+
+ /**
+ * NautilusQueryEditor::query:
+ *
+ * The current query of the query editor. It it always synchronized
+ * with the filter popover's query.
+ */
+ g_object_class_install_property (gobject_class,
+ PROP_QUERY,
+ g_param_spec_object ("query",
+ "Query of the search",
+ "The query that the editor is handling",
+ NAUTILUS_TYPE_QUERY,
+ G_PARAM_READWRITE));
+
+ gtk_widget_class_set_layout_manager_type (widget_class, GTK_TYPE_BOX_LAYOUT);
+ gtk_widget_class_set_css_name (widget_class, "entry");
+ gtk_widget_class_set_accessible_role (widget_class, GTK_ACCESSIBLE_ROLE_SEARCH_BOX);
+}
+
+GFile *
+nautilus_query_editor_get_location (NautilusQueryEditor *editor)
+{
+ g_return_val_if_fail (NAUTILUS_IS_QUERY_EDITOR (editor), NULL);
+
+ return g_object_ref (editor->location);
+}
+
+static void
+create_query (NautilusQueryEditor *editor)
+{
+ NautilusQuery *query;
+ g_autoptr (NautilusFile) file = NULL;
+ gboolean fts_enabled;
+
+ g_return_if_fail (editor->query == NULL);
+
+ fts_enabled = nautilus_search_popover_get_fts_enabled (NAUTILUS_SEARCH_POPOVER (editor->popover));
+
+ if (editor->location == NULL)
+ {
+ return;
+ }
+
+ file = nautilus_file_get (editor->location);
+ query = nautilus_query_new ();
+
+ nautilus_query_set_search_content (query, fts_enabled);
+
+ nautilus_query_set_text (query, gtk_editable_get_text (GTK_EDITABLE (editor->text)));
+ nautilus_query_set_location (query, editor->location);
+
+ /* We only set the query using the global setting for recursivity here,
+ * it's up to the search engine to check weather it can proceed with
+ * deep search in the current directory or not. */
+ nautilus_query_set_recursive (query, location_settings_search_get_recursive ());
+
+ nautilus_query_editor_set_query (editor, query);
+}
+
+static void
+entry_activate_cb (GtkWidget *entry,
+ NautilusQueryEditor *editor)
+{
+ g_signal_emit (editor, signals[ACTIVATED], 0);
+}
+
+static gboolean
+entry_changed_internal (NautilusQueryEditor *editor)
+{
+ const gchar *text = gtk_editable_get_text (GTK_EDITABLE (editor->text));
+ gboolean is_empty = (text == NULL && *text == '\0');
+
+ editor->search_changed_timeout_id = 0;
+
+ gtk_widget_set_child_visible (editor->clear_icon, !is_empty);
+
+ if (editor->query == NULL)
+ {
+ create_query (editor);
+ }
+ else
+ {
+ g_autofree gchar *stripped_text = g_strstrip (g_strdup (text));
+ nautilus_query_set_text (editor->query, stripped_text);
+ }
+
+ nautilus_query_editor_changed (editor);
+
+ return G_SOURCE_REMOVE;
+}
+
+static void
+entry_changed_cb (GtkWidget *entry,
+ NautilusQueryEditor *editor)
+{
+ if (editor->change_frozen)
+ {
+ return;
+ }
+
+ g_clear_handle_id (&editor->search_changed_timeout_id, g_source_remove);
+ editor->search_changed_timeout_id = g_timeout_add (SEARCH_CHANGED_TIMEOUT,
+ G_SOURCE_FUNC (entry_changed_internal),
+ editor);
+}
+
+static GtkWidget *
+create_tag (NautilusQueryEditor *self,
+ const gchar *text,
+ GCallback reset_callback)
+{
+ GtkWidget *tag;
+ GtkWidget *label;
+ GtkWidget *button;
+
+ tag = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 6);
+ gtk_widget_set_margin_end (tag, 6);
+ gtk_widget_set_name (tag, "NautilusQueryEditorTag");
+
+ label = gtk_label_new (text);
+ gtk_widget_add_css_class (label, "caption-heading");
+ gtk_widget_set_margin_start (label, 12);
+ gtk_box_append (GTK_BOX (tag), label);
+
+ button = gtk_button_new ();
+ gtk_button_set_icon_name (GTK_BUTTON (button), "window-close-symbolic");
+ gtk_widget_add_css_class (button, "flat");
+ gtk_widget_add_css_class (button, "circular");
+ g_signal_connect_object (button, "clicked",
+ reset_callback, self->popover, G_CONNECT_SWAPPED);
+ gtk_box_append (GTK_BOX (tag), button);
+
+ return tag;
+}
+
+static void
+search_popover_date_range_changed_cb (NautilusSearchPopover *popover,
+ GPtrArray *date_range,
+ gpointer user_data)
+{
+ NautilusQueryEditor *editor;
+
+ editor = NAUTILUS_QUERY_EDITOR (user_data);
+
+ if (editor->query == NULL)
+ {
+ create_query (editor);
+ }
+
+ if (editor->date_range_tag != NULL)
+ {
+ gtk_box_remove (GTK_BOX (editor->tags_box), editor->date_range_tag);
+ editor->date_range_tag = NULL;
+ }
+
+ if (date_range)
+ {
+ g_autofree gchar *text_for_date_range = NULL;
+
+ text_for_date_range = get_text_for_date_range (date_range, TRUE);
+ editor->date_range_tag = create_tag (editor,
+ text_for_date_range,
+ G_CALLBACK (nautilus_search_popover_reset_date_range));
+ gtk_box_append (GTK_BOX (editor->tags_box), editor->date_range_tag);
+ }
+
+ nautilus_query_set_date_range (editor->query, date_range);
+
+ nautilus_query_editor_changed (editor);
+ gtk_widget_set_visible (editor->tags_box,
+ (gtk_widget_get_first_child (editor->tags_box) != NULL));
+}
+
+static void
+search_popover_mime_type_changed_cb (NautilusSearchPopover *popover,
+ gint mimetype_group,
+ const gchar *mimetype,
+ gpointer user_data)
+{
+ NautilusQueryEditor *editor;
+ g_autoptr (GPtrArray) mimetypes = NULL;
+
+ editor = NAUTILUS_QUERY_EDITOR (user_data);
+
+ if (editor->query == NULL)
+ {
+ create_query (editor);
+ }
+
+ if (editor->mime_types_tag != NULL)
+ {
+ gtk_box_remove (GTK_BOX (editor->tags_box), editor->mime_types_tag);
+ editor->mime_types_tag = NULL;
+ }
+
+ /* group 0 is anything */
+ if (mimetype_group == 0)
+ {
+ mimetypes = nautilus_mime_types_group_get_mimetypes (mimetype_group);
+ }
+ else if (mimetype_group > 0)
+ {
+ mimetypes = nautilus_mime_types_group_get_mimetypes (mimetype_group);
+ editor->mime_types_tag = create_tag (editor,
+ nautilus_mime_types_group_get_name (mimetype_group),
+ G_CALLBACK (nautilus_search_popover_reset_mime_types));
+ gtk_box_append (GTK_BOX (editor->tags_box), editor->mime_types_tag);
+ }
+ else
+ {
+ g_autofree gchar *display_name = NULL;
+
+ mimetypes = g_ptr_array_new_full (1, g_free);
+ g_ptr_array_add (mimetypes, g_strdup (mimetype));
+
+ display_name = g_content_type_get_description (mimetype);
+ editor->mime_types_tag = create_tag (editor,
+ display_name,
+ G_CALLBACK (nautilus_search_popover_reset_mime_types));
+ gtk_box_append (GTK_BOX (editor->tags_box), editor->mime_types_tag);
+ }
+ nautilus_query_set_mime_types (editor->query, mimetypes);
+
+ nautilus_query_editor_changed (editor);
+ gtk_widget_set_visible (editor->tags_box,
+ (gtk_widget_get_first_child (editor->tags_box) != NULL));
+}
+
+static void
+search_popover_time_type_changed_cb (NautilusSearchPopover *popover,
+ NautilusQuerySearchType data,
+ gpointer user_data)
+{
+ NautilusQueryEditor *editor;
+
+ editor = NAUTILUS_QUERY_EDITOR (user_data);
+
+ if (editor->query == NULL)
+ {
+ create_query (editor);
+ }
+
+ nautilus_query_set_search_type (editor->query, data);
+
+ nautilus_query_editor_changed (editor);
+}
+
+static void
+search_popover_fts_changed_cb (GObject *popover,
+ GParamSpec *pspec,
+ gpointer user_data)
+{
+ NautilusQueryEditor *editor;
+
+ editor = NAUTILUS_QUERY_EDITOR (user_data);
+
+ if (editor->query == NULL)
+ {
+ create_query (editor);
+ }
+
+ nautilus_query_set_search_content (editor->query,
+ nautilus_search_popover_get_fts_enabled (NAUTILUS_SEARCH_POPOVER (popover)));
+
+ nautilus_query_editor_changed (editor);
+}
+
+static void
+on_clear_icon_pressed (GtkGestureClick *gesture,
+ int n_press,
+ double x,
+ double y,
+ NautilusQueryEditor *self)
+{
+ gtk_gesture_set_state (GTK_GESTURE (gesture), GTK_EVENT_SEQUENCE_CLAIMED);
+}
+
+static void
+on_clear_icon_released (GtkGestureClick *gesture,
+ int n_press,
+ double x,
+ double y,
+ NautilusQueryEditor *self)
+{
+ gtk_editable_set_text (GTK_EDITABLE (self->text), "");
+}
+
+static void
+nautilus_query_editor_init (NautilusQueryEditor *editor)
+{
+ gboolean rtl = (gtk_widget_get_direction (GTK_WIDGET (editor)) == GTK_TEXT_DIR_RTL);
+ GtkWidget *image;
+ GtkEventController *controller;
+
+ gtk_widget_set_name (GTK_WIDGET (editor), "NautilusQueryEditor");
+ gtk_widget_add_css_class (GTK_WIDGET (editor), "search");
+
+ g_signal_connect (nautilus_preferences,
+ "changed::recursive-search",
+ G_CALLBACK (recursive_search_preferences_changed),
+ editor);
+
+ /* create the search entry */
+ image = gtk_image_new_from_icon_name ("system-search-symbolic");
+ g_object_set (image, "accessible-role", GTK_ACCESSIBLE_ROLE_PRESENTATION, NULL);
+ gtk_widget_set_margin_start (image, 4);
+ gtk_widget_set_margin_end (image, 6);
+ gtk_widget_set_parent (image, GTK_WIDGET (editor));
+
+ editor->tags_box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
+ gtk_widget_hide (editor->tags_box);
+ gtk_widget_set_parent (editor->tags_box, GTK_WIDGET (editor));
+
+ editor->text = gtk_text_new ();
+ gtk_widget_set_hexpand (editor->text, TRUE);
+ gtk_widget_set_parent (editor->text, GTK_WIDGET (editor));
+
+ editor->clear_icon = gtk_image_new_from_icon_name (rtl ? "edit-clear-rtl-symbolic" :
+ "edit-clear-symbolic");
+ g_object_set (editor->clear_icon, "accessible-role", GTK_ACCESSIBLE_ROLE_PRESENTATION, NULL);
+ gtk_widget_set_tooltip_text (editor->clear_icon, _("Clear entry"));
+ gtk_widget_set_child_visible (editor->clear_icon, FALSE);
+ gtk_widget_set_parent (editor->clear_icon, GTK_WIDGET (editor));
+
+ controller = GTK_EVENT_CONTROLLER (gtk_gesture_click_new ());
+ g_signal_connect (controller, "pressed", G_CALLBACK (on_clear_icon_pressed), editor);
+ g_signal_connect (controller, "released", G_CALLBACK (on_clear_icon_released), editor);
+ gtk_widget_add_controller (editor->clear_icon, controller);
+
+ /* setup the search popover */
+ editor->popover = nautilus_search_popover_new ();
+
+ g_signal_connect (editor->popover, "show",
+ G_CALLBACK (gtk_widget_grab_focus), NULL);
+ g_signal_connect_swapped (editor->popover, "closed",
+ G_CALLBACK (gtk_widget_grab_focus), editor);
+
+ g_object_bind_property (editor, "query",
+ editor->popover, "query",
+ G_BINDING_DEFAULT);
+
+ /* setup the filter menu button */
+ editor->dropdown_button = gtk_menu_button_new ();
+ gtk_menu_button_set_icon_name (GTK_MENU_BUTTON (editor->dropdown_button), "funnel-symbolic");
+ gtk_menu_button_set_popover (GTK_MENU_BUTTON (editor->dropdown_button), editor->popover);
+ gtk_widget_set_parent (editor->dropdown_button, GTK_WIDGET (editor));
+ gtk_widget_add_css_class (editor->dropdown_button, "circular");
+
+ g_signal_connect (editor->text, "activate",
+ G_CALLBACK (entry_activate_cb), editor);
+ g_signal_connect (editor->text, "changed",
+ G_CALLBACK (entry_changed_cb), editor);
+ g_signal_connect (editor->popover, "date-range",
+ G_CALLBACK (search_popover_date_range_changed_cb), editor);
+ g_signal_connect (editor->popover, "mime-type",
+ G_CALLBACK (search_popover_mime_type_changed_cb), editor);
+ g_signal_connect (editor->popover, "time-type",
+ G_CALLBACK (search_popover_time_type_changed_cb), editor);
+ g_signal_connect (editor->popover, "notify::fts-enabled",
+ G_CALLBACK (search_popover_fts_changed_cb), editor);
+}
+
+static void
+nautilus_query_editor_changed (NautilusQueryEditor *editor)
+{
+ if (editor->change_frozen)
+ {
+ return;
+ }
+
+ g_signal_emit (editor, signals[CHANGED], 0, editor->query, TRUE);
+}
+
+NautilusQuery *
+nautilus_query_editor_get_query (NautilusQueryEditor *editor)
+{
+ g_return_val_if_fail (NAUTILUS_IS_QUERY_EDITOR (editor), NULL);
+
+ if (editor->text == NULL)
+ {
+ return NULL;
+ }
+
+ return editor->query;
+}
+
+GtkWidget *
+nautilus_query_editor_new (void)
+{
+ return GTK_WIDGET (g_object_new (NAUTILUS_TYPE_QUERY_EDITOR, NULL));
+}
+
+void
+nautilus_query_editor_set_location (NautilusQueryEditor *editor,
+ GFile *location)
+{
+ g_autoptr (NautilusDirectory) directory = NULL;
+ NautilusDirectory *base_model;
+ gboolean should_notify;
+
+ g_return_if_fail (NAUTILUS_IS_QUERY_EDITOR (editor));
+
+ /* The client could set us a location that is actually a search directory,
+ * like what happens with the slot when updating the query editor location.
+ * However here we want the real location used as a model for the search,
+ * not the search directory invented uri. */
+ directory = nautilus_directory_get (location);
+ if (NAUTILUS_IS_SEARCH_DIRECTORY (directory))
+ {
+ g_autoptr (GFile) real_location = NULL;
+
+ base_model = nautilus_search_directory_get_base_model (NAUTILUS_SEARCH_DIRECTORY (directory));
+ real_location = nautilus_directory_get_location (base_model);
+
+ should_notify = g_set_object (&editor->location, real_location);
+ }
+ else
+ {
+ should_notify = g_set_object (&editor->location, location);
+ }
+
+ if (editor->query == NULL)
+ {
+ create_query (editor);
+ }
+ nautilus_query_set_location (editor->query, editor->location);
+
+ update_fts_sensitivity (editor);
+
+ if (should_notify)
+ {
+ g_object_notify (G_OBJECT (editor), "location");
+ }
+}
+
+void
+nautilus_query_editor_set_query (NautilusQueryEditor *self,
+ NautilusQuery *query)
+{
+ g_autofree char *text = NULL;
+ g_autofree char *current_text = NULL;
+
+ g_return_if_fail (NAUTILUS_IS_QUERY_EDITOR (self));
+
+ if (query != NULL)
+ {
+ text = nautilus_query_get_text (query);
+ }
+
+ if (!text)
+ {
+ text = g_strdup ("");
+ }
+
+ self->change_frozen = TRUE;
+
+ current_text = g_strdup (gtk_editable_get_text (GTK_EDITABLE (self->text)));
+ current_text = g_strstrip (current_text);
+ if (!g_str_equal (current_text, text))
+ {
+ gtk_editable_set_text (GTK_EDITABLE (self->text), text);
+ gtk_editable_set_position (GTK_EDITABLE (self->text), -1);
+ }
+
+ if (g_set_object (&self->query, query))
+ {
+ g_object_notify (G_OBJECT (self), "query");
+ }
+
+ self->change_frozen = FALSE;
+}
+
+void
+nautilus_query_editor_set_text (NautilusQueryEditor *self,
+ const gchar *text)
+{
+ g_return_if_fail (NAUTILUS_IS_QUERY_EDITOR (self));
+ g_return_if_fail (text != NULL);
+
+ /* The handler of the entry will take care of everything */
+ gtk_editable_set_text (GTK_EDITABLE (self->text), text);
+}
+
+static gboolean
+nautilus_gtk_search_entry_is_keynav_event (guint keyval,
+ GdkModifierType state)
+{
+ if (keyval == GDK_KEY_Tab || keyval == GDK_KEY_KP_Tab ||
+ keyval == GDK_KEY_Up || keyval == GDK_KEY_KP_Up ||
+ keyval == GDK_KEY_Down || keyval == GDK_KEY_KP_Down ||
+ keyval == GDK_KEY_Left || keyval == GDK_KEY_KP_Left ||
+ keyval == GDK_KEY_Right || keyval == GDK_KEY_KP_Right ||
+ keyval == GDK_KEY_Home || keyval == GDK_KEY_KP_Home ||
+ keyval == GDK_KEY_End || keyval == GDK_KEY_KP_End ||
+ keyval == GDK_KEY_Page_Up || keyval == GDK_KEY_KP_Page_Up ||
+ keyval == GDK_KEY_Page_Down || keyval == GDK_KEY_KP_Page_Down ||
+ ((state & (GDK_CONTROL_MASK | GDK_ALT_MASK)) != 0))
+ {
+ return TRUE;
+ }
+
+ /* Other navigation events should get automatically
+ * ignored as they will not change the content of the entry
+ */
+ return FALSE;
+}
+
+gboolean
+nautilus_query_editor_handle_event (NautilusQueryEditor *self,
+ GtkEventControllerKey *controller,
+ guint keyval,
+ GdkModifierType state)
+{
+ g_return_val_if_fail (NAUTILUS_IS_QUERY_EDITOR (self), GDK_EVENT_PROPAGATE);
+ g_return_val_if_fail (controller != NULL, GDK_EVENT_PROPAGATE);
+
+ /* Conditions are copied straight from GTK. */
+ if (nautilus_gtk_search_entry_is_keynav_event (keyval, state) ||
+ keyval == GDK_KEY_space ||
+ keyval == GDK_KEY_Menu)
+ {
+ return GDK_EVENT_PROPAGATE;
+ }
+
+ return gtk_event_controller_key_forward (controller, self->text);
+}
diff --git a/src/nautilus-query-editor.h b/src/nautilus-query-editor.h
new file mode 100644
index 0000000..e071f94
--- /dev/null
+++ b/src/nautilus-query-editor.h
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2005 Red Hat, Inc.
+ *
+ * Nautilus is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * Nautilus is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; see the file COPYING. If not,
+ * see <http://www.gnu.org/licenses/>.
+ *
+ * Author: Alexander Larsson <alexl@redhat.com>
+ *
+ */
+
+#pragma once
+
+#include <gtk/gtk.h>
+
+#include "nautilus-types.h"
+
+#define NAUTILUS_TYPE_QUERY_EDITOR nautilus_query_editor_get_type()
+
+G_DECLARE_FINAL_TYPE (NautilusQueryEditor, nautilus_query_editor, NAUTILUS, QUERY_EDITOR, GtkWidget)
+
+GtkWidget *nautilus_query_editor_new (void);
+
+/**
+ * nautilus_query_editor_get_query:
+ *
+ * @editor: A #NautilusQueryEditor instance.
+ *
+ * Returns: (nullable) (transfer full): The #NautilusQuery for the editor.
+ */
+NautilusQuery *nautilus_query_editor_get_query (NautilusQueryEditor *editor);
+/**
+ * nautilus_query_editor_set_query:
+ *
+ * @editor: A #NautilusQueryEditor instance.
+ * @query: (nullable) (transfer full): The #NautilusQuery for the search.
+ */
+void nautilus_query_editor_set_query (NautilusQueryEditor *editor,
+ NautilusQuery *query);
+/**
+ * nautilus_query_editor_get_location:
+ *
+ * @editor: A #NautilusQueryEditor instance.
+ *
+ * Returns: (nullable) (transfer full): The location of the current search.
+ */
+GFile *nautilus_query_editor_get_location (NautilusQueryEditor *editor);
+/**
+ * nautilus_query_editor_set_location:
+ *
+ * @editor: A #NautilusQueryEditor instance.
+ * @location: (nullable) (transfer full): The location in which the search will take place.
+ */
+void nautilus_query_editor_set_location (NautilusQueryEditor *editor,
+ GFile *location);
+/**
+ * nautilus_query_editor_set_text:
+ *
+ * @editor: A #NautilusQueryEditor instance.
+ * @text: (not nullable) (transfer none): The search text.
+ */
+void nautilus_query_editor_set_text (NautilusQueryEditor *editor,
+ const gchar *text);
+
+gboolean
+nautilus_query_editor_handle_event (NautilusQueryEditor *self,
+ GtkEventControllerKey *controller,
+ guint keyval,
+ GdkModifierType state);
diff --git a/src/nautilus-query.c b/src/nautilus-query.c
new file mode 100644
index 0000000..e3b64c9
--- /dev/null
+++ b/src/nautilus-query.c
@@ -0,0 +1,692 @@
+/*
+ * Copyright (C) 2005 Novell, Inc.
+ *
+ * Nautilus is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * Nautilus is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; see the file COPYING. If not,
+ * see <http://www.gnu.org/licenses/>.
+ *
+ * Author: Anders Carlsson <andersca@imendio.com>
+ *
+ */
+
+#include "nautilus-query.h"
+
+#include <eel/eel-glib-extensions.h>
+#include <glib/gi18n.h>
+
+#include "nautilus-enum-types.h"
+#include "nautilus-file-utilities.h"
+#include "nautilus-global-preferences.h"
+
+#define RANK_SCALE_FACTOR 100
+#define MIN_RANK 10.0
+#define MAX_RANK 50.0
+
+struct _NautilusQuery
+{
+ GObject parent;
+
+ char *text;
+ GFile *location;
+ GPtrArray *mime_types;
+ gboolean show_hidden;
+ GPtrArray *date_range;
+ NautilusQueryRecursive recursive;
+ NautilusQuerySearchType search_type;
+ NautilusQuerySearchContent search_content;
+
+ gboolean searching;
+ char **prepared_words;
+ GMutex prepared_words_mutex;
+};
+
+static void nautilus_query_class_init (NautilusQueryClass *class);
+static void nautilus_query_init (NautilusQuery *query);
+
+G_DEFINE_TYPE (NautilusQuery, nautilus_query, G_TYPE_OBJECT);
+
+enum
+{
+ PROP_0,
+ PROP_DATE_RANGE,
+ PROP_LOCATION,
+ PROP_MIMETYPES,
+ PROP_RECURSIVE,
+ PROP_SEARCH_TYPE,
+ PROP_SEARCHING,
+ PROP_SHOW_HIDDEN,
+ PROP_TEXT,
+ LAST_PROP
+};
+
+static void
+finalize (GObject *object)
+{
+ NautilusQuery *query;
+
+ query = NAUTILUS_QUERY (object);
+
+ g_free (query->text);
+ g_strfreev (query->prepared_words);
+ g_clear_object (&query->location);
+ g_clear_pointer (&query->date_range, g_ptr_array_unref);
+ g_mutex_clear (&query->prepared_words_mutex);
+
+ G_OBJECT_CLASS (nautilus_query_parent_class)->finalize (object);
+}
+
+static void
+nautilus_query_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ NautilusQuery *self = NAUTILUS_QUERY (object);
+
+ switch (prop_id)
+ {
+ case PROP_DATE_RANGE:
+ {
+ g_value_set_pointer (value, self->date_range);
+ }
+ break;
+
+ case PROP_LOCATION:
+ {
+ g_value_set_object (value, self->location);
+ }
+ break;
+
+ case PROP_MIMETYPES:
+ {
+ g_value_set_pointer (value, self->mime_types);
+ }
+ break;
+
+ case PROP_RECURSIVE:
+ {
+ g_value_set_enum (value, self->recursive);
+ }
+ break;
+
+ case PROP_SEARCH_TYPE:
+ {
+ g_value_set_enum (value, self->search_type);
+ }
+ break;
+
+ case PROP_SEARCHING:
+ {
+ g_value_set_boolean (value, self->searching);
+ }
+ break;
+
+ case PROP_SHOW_HIDDEN:
+ {
+ g_value_set_boolean (value, self->show_hidden);
+ }
+ break;
+
+ case PROP_TEXT:
+ {
+ g_value_set_string (value, self->text);
+ }
+ break;
+
+ default:
+ {
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+ }
+}
+
+static void
+nautilus_query_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ NautilusQuery *self = NAUTILUS_QUERY (object);
+
+ switch (prop_id)
+ {
+ case PROP_DATE_RANGE:
+ {
+ nautilus_query_set_date_range (self, g_value_get_pointer (value));
+ }
+ break;
+
+ case PROP_LOCATION:
+ {
+ nautilus_query_set_location (self, g_value_get_object (value));
+ }
+ break;
+
+ case PROP_MIMETYPES:
+ {
+ nautilus_query_set_mime_types (self, g_value_get_pointer (value));
+ }
+ break;
+
+ case PROP_RECURSIVE:
+ {
+ nautilus_query_set_recursive (self, g_value_get_enum (value));
+ }
+ break;
+
+ case PROP_SEARCH_TYPE:
+ {
+ nautilus_query_set_search_type (self, g_value_get_enum (value));
+ }
+ break;
+
+ case PROP_SEARCHING:
+ {
+ nautilus_query_set_searching (self, g_value_get_boolean (value));
+ }
+ break;
+
+ case PROP_SHOW_HIDDEN:
+ {
+ nautilus_query_set_show_hidden_files (self, g_value_get_boolean (value));
+ }
+ break;
+
+ case PROP_TEXT:
+ {
+ nautilus_query_set_text (self, g_value_get_string (value));
+ }
+ break;
+
+ default:
+ {
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+ }
+}
+
+static void
+nautilus_query_class_init (NautilusQueryClass *class)
+{
+ GObjectClass *gobject_class;
+
+ gobject_class = G_OBJECT_CLASS (class);
+ gobject_class->finalize = finalize;
+ gobject_class->get_property = nautilus_query_get_property;
+ gobject_class->set_property = nautilus_query_set_property;
+
+ /**
+ * NautilusQuery::date-range:
+ *
+ * The date range of the query.
+ *
+ */
+ g_object_class_install_property (gobject_class,
+ PROP_DATE_RANGE,
+ g_param_spec_pointer ("date-range",
+ "Date range of the query",
+ "The range date of the query",
+ G_PARAM_READWRITE));
+
+ /**
+ * NautilusQuery::location:
+ *
+ * The location of the query.
+ *
+ */
+ g_object_class_install_property (gobject_class,
+ PROP_LOCATION,
+ g_param_spec_object ("location",
+ "Location of the query",
+ "The location of the query",
+ G_TYPE_FILE,
+ G_PARAM_READWRITE));
+
+ /**
+ * NautilusQuery::mimetypes: (type GPtrArray) (element-type gchar*)
+ *
+ * MIME types the query holds. An empty array means "Any type".
+ *
+ */
+ g_object_class_install_property (gobject_class,
+ PROP_MIMETYPES,
+ g_param_spec_pointer ("mimetypes",
+ "MIME types of the query",
+ "The MIME types of the query",
+ G_PARAM_READWRITE));
+
+ /**
+ * NautilusQuery::recursive:
+ *
+ * Whether the query is being performed on subdirectories or not.
+ *
+ */
+ g_object_class_install_property (gobject_class,
+ PROP_RECURSIVE,
+ g_param_spec_enum ("recursive",
+ "Whether the query is being performed on subdirectories",
+ "Whether the query is being performed on subdirectories or not",
+ NAUTILUS_TYPE_QUERY_RECURSIVE,
+ NAUTILUS_QUERY_RECURSIVE_ALWAYS,
+ G_PARAM_READWRITE));
+
+ /**
+ * NautilusQuery::search-type:
+ *
+ * The search type of the query.
+ *
+ */
+ g_object_class_install_property (gobject_class,
+ PROP_SEARCH_TYPE,
+ g_param_spec_enum ("search-type",
+ "Type of the query",
+ "The type of the query",
+ NAUTILUS_TYPE_QUERY_SEARCH_TYPE,
+ NAUTILUS_QUERY_SEARCH_TYPE_LAST_MODIFIED,
+ G_PARAM_READWRITE));
+
+ /**
+ * NautilusQuery::searching:
+ *
+ * Whether the query is being performed or not.
+ *
+ */
+ g_object_class_install_property (gobject_class,
+ PROP_SEARCHING,
+ g_param_spec_boolean ("searching",
+ "Whether the query is being performed",
+ "Whether the query is being performed or not",
+ FALSE,
+ G_PARAM_READWRITE));
+
+ /**
+ * NautilusQuery::show-hidden:
+ *
+ * Whether the search should include hidden files.
+ *
+ */
+ g_object_class_install_property (gobject_class,
+ PROP_SHOW_HIDDEN,
+ g_param_spec_boolean ("show-hidden",
+ "Show hidden files",
+ "Whether the search should show hidden files",
+ FALSE,
+ G_PARAM_READWRITE));
+
+ /**
+ * NautilusQuery::text:
+ *
+ * The search string.
+ *
+ */
+ g_object_class_install_property (gobject_class,
+ PROP_TEXT,
+ g_param_spec_string ("text",
+ "Text of the search",
+ "The text string of the search",
+ NULL,
+ G_PARAM_READWRITE));
+}
+
+static void
+nautilus_query_init (NautilusQuery *query)
+{
+ query->location = g_file_new_for_path (g_get_home_dir ());
+ query->mime_types = g_ptr_array_new ();
+ query->show_hidden = TRUE;
+ query->search_type = g_settings_get_enum (nautilus_preferences, "search-filter-time-type");
+ query->search_content = NAUTILUS_QUERY_SEARCH_CONTENT_SIMPLE;
+ g_mutex_init (&query->prepared_words_mutex);
+}
+
+static gchar *
+prepare_string_for_compare (const gchar *string)
+{
+ gchar *normalized, *res;
+
+ normalized = g_utf8_normalize (string, -1, G_NORMALIZE_NFD);
+ res = g_utf8_strdown (normalized, -1);
+ g_free (normalized);
+
+ return res;
+}
+
+gdouble
+nautilus_query_matches_string (NautilusQuery *query,
+ const gchar *string)
+{
+ gchar *prepared_string, *ptr;
+ gboolean found;
+ gdouble retval;
+ gint idx, nonexact_malus;
+
+ if (!query->text)
+ {
+ return -1;
+ }
+
+ g_mutex_lock (&query->prepared_words_mutex);
+ if (!query->prepared_words)
+ {
+ prepared_string = prepare_string_for_compare (query->text);
+ query->prepared_words = g_strsplit (prepared_string, " ", -1);
+ g_free (prepared_string);
+ }
+
+ prepared_string = prepare_string_for_compare (string);
+ found = TRUE;
+ ptr = NULL;
+ nonexact_malus = 0;
+
+ for (idx = 0; query->prepared_words[idx] != NULL; idx++)
+ {
+ if ((ptr = strstr (prepared_string, query->prepared_words[idx])) == NULL)
+ {
+ found = FALSE;
+ break;
+ }
+
+ nonexact_malus += strlen (ptr) - strlen (query->prepared_words[idx]);
+ }
+ g_mutex_unlock (&query->prepared_words_mutex);
+
+ if (!found)
+ {
+ g_free (prepared_string);
+ return -1;
+ }
+
+ /* The rank value depends on the numbers of letters before and after the match.
+ * To make the prefix matches prefered over sufix ones, the number of letters
+ * after the match is divided by a factor, so that it decreases the rank by a
+ * smaller amount.
+ */
+ retval = MAX (MIN_RANK, MAX_RANK - (gdouble) (ptr - prepared_string) - (gdouble) nonexact_malus / RANK_SCALE_FACTOR);
+ g_free (prepared_string);
+
+ return retval;
+}
+
+NautilusQuery *
+nautilus_query_new (void)
+{
+ return g_object_new (NAUTILUS_TYPE_QUERY, NULL);
+}
+
+
+char *
+nautilus_query_get_text (NautilusQuery *query)
+{
+ g_return_val_if_fail (NAUTILUS_IS_QUERY (query), NULL);
+
+ return g_strdup (query->text);
+}
+
+void
+nautilus_query_set_text (NautilusQuery *query,
+ const char *text)
+{
+ g_return_if_fail (NAUTILUS_IS_QUERY (query));
+
+ g_free (query->text);
+ query->text = g_strstrip (g_strdup (text));
+
+ g_mutex_lock (&query->prepared_words_mutex);
+ g_strfreev (query->prepared_words);
+ query->prepared_words = NULL;
+ g_mutex_unlock (&query->prepared_words_mutex);
+
+ g_object_notify (G_OBJECT (query), "text");
+}
+
+GFile *
+nautilus_query_get_location (NautilusQuery *query)
+{
+ g_return_val_if_fail (NAUTILUS_IS_QUERY (query), NULL);
+
+ return g_object_ref (query->location);
+}
+
+void
+nautilus_query_set_location (NautilusQuery *query,
+ GFile *location)
+{
+ g_return_if_fail (NAUTILUS_IS_QUERY (query));
+
+ if (g_set_object (&query->location, location))
+ {
+ g_object_notify (G_OBJECT (query), "location");
+ }
+}
+
+/**
+ * nautilus_query_get_mime_type:
+ * @query: A #NautilusQuery
+ *
+ * Retrieves the current MIME Types filter from @query. Its content must not be
+ * modified. It can be read by multiple threads.
+ *
+ * Returns: (transfer container) A #GPtrArray reference with MIME type name strings.
+ */
+GPtrArray *
+nautilus_query_get_mime_types (NautilusQuery *query)
+{
+ g_return_val_if_fail (NAUTILUS_IS_QUERY (query), NULL);
+
+ return g_ptr_array_ref (query->mime_types);
+}
+
+/**
+ * nautilus_query_set_mime_types:
+ * @query: A #NautilusQuery
+ * @mime_types: (transfer none): A #GPtrArray of MIME type strings
+ *
+ * Set a new MIME types filter for @query. Once set, the filter must not be
+ * modified, and it can only be replaced by setting another filter.
+ *
+ * Search engines that are already running for a previous filter will ignore the
+ * new filter. So, the caller must ensure that the search will be reloaded
+ * afterwards.
+ */
+void
+nautilus_query_set_mime_types (NautilusQuery *query,
+ GPtrArray *mime_types)
+{
+ g_return_if_fail (NAUTILUS_IS_QUERY (query));
+ g_return_if_fail (mime_types != NULL);
+
+ g_clear_pointer (&query->mime_types, g_ptr_array_unref);
+ query->mime_types = g_ptr_array_ref (mime_types);
+
+ g_object_notify (G_OBJECT (query), "mimetypes");
+}
+
+gboolean
+nautilus_query_get_show_hidden_files (NautilusQuery *query)
+{
+ g_return_val_if_fail (NAUTILUS_IS_QUERY (query), FALSE);
+
+ return query->show_hidden;
+}
+
+void
+nautilus_query_set_show_hidden_files (NautilusQuery *query,
+ gboolean show_hidden)
+{
+ g_return_if_fail (NAUTILUS_IS_QUERY (query));
+
+ if (query->show_hidden != show_hidden)
+ {
+ query->show_hidden = show_hidden;
+ g_object_notify (G_OBJECT (query), "show-hidden");
+ }
+}
+
+char *
+nautilus_query_to_readable_string (NautilusQuery *query)
+{
+ if (!query || !query->text || query->text[0] == '\0')
+ {
+ return g_strdup (_("Search"));
+ }
+
+ return g_strdup_printf (_("Search for “%s”"), query->text);
+}
+
+NautilusQuerySearchContent
+nautilus_query_get_search_content (NautilusQuery *query)
+{
+ g_return_val_if_fail (NAUTILUS_IS_QUERY (query), -1);
+
+ return query->search_content;
+}
+
+void
+nautilus_query_set_search_content (NautilusQuery *query,
+ NautilusQuerySearchContent content)
+{
+ g_return_if_fail (NAUTILUS_IS_QUERY (query));
+
+ if (query->search_content != content)
+ {
+ query->search_content = content;
+ g_object_notify (G_OBJECT (query), "search-type");
+ }
+}
+
+NautilusQuerySearchType
+nautilus_query_get_search_type (NautilusQuery *query)
+{
+ g_return_val_if_fail (NAUTILUS_IS_QUERY (query), -1);
+
+ return query->search_type;
+}
+
+void
+nautilus_query_set_search_type (NautilusQuery *query,
+ NautilusQuerySearchType type)
+{
+ g_return_if_fail (NAUTILUS_IS_QUERY (query));
+
+ if (query->search_type != type)
+ {
+ query->search_type = type;
+ g_object_notify (G_OBJECT (query), "search-type");
+ }
+}
+
+/**
+ * nautilus_query_get_date_range:
+ * @query: a #NautilusQuery
+ *
+ * Retrieves the #GptrArray composed of #GDateTime representing the date range.
+ * This function is thread safe.
+ *
+ * Returns: (transfer full): the #GptrArray composed of #GDateTime representing the date range.
+ */
+GPtrArray *
+nautilus_query_get_date_range (NautilusQuery *query)
+{
+ static GMutex mutex;
+
+ g_return_val_if_fail (NAUTILUS_IS_QUERY (query), NULL);
+
+ g_mutex_lock (&mutex);
+ if (query->date_range)
+ {
+ g_ptr_array_ref (query->date_range);
+ }
+ g_mutex_unlock (&mutex);
+
+ return query->date_range;
+}
+
+void
+nautilus_query_set_date_range (NautilusQuery *query,
+ GPtrArray *date_range)
+{
+ g_return_if_fail (NAUTILUS_IS_QUERY (query));
+
+ g_clear_pointer (&query->date_range, g_ptr_array_unref);
+ if (date_range)
+ {
+ query->date_range = g_ptr_array_ref (date_range);
+ }
+
+ g_object_notify (G_OBJECT (query), "date-range");
+}
+
+gboolean
+nautilus_query_get_searching (NautilusQuery *query)
+{
+ g_return_val_if_fail (NAUTILUS_IS_QUERY (query), FALSE);
+
+ return query->searching;
+}
+
+void
+nautilus_query_set_searching (NautilusQuery *query,
+ gboolean searching)
+{
+ g_return_if_fail (NAUTILUS_IS_QUERY (query));
+
+ searching = !!searching;
+
+ if (query->searching != searching)
+ {
+ query->searching = searching;
+
+ g_object_notify (G_OBJECT (query), "searching");
+ }
+}
+
+NautilusQueryRecursive
+nautilus_query_get_recursive (NautilusQuery *query)
+{
+ g_return_val_if_fail (NAUTILUS_IS_QUERY (query),
+ NAUTILUS_QUERY_RECURSIVE_ALWAYS);
+
+ return query->recursive;
+}
+
+void
+nautilus_query_set_recursive (NautilusQuery *query,
+ NautilusQueryRecursive recursive)
+{
+ g_return_if_fail (NAUTILUS_IS_QUERY (query));
+
+ if (query->recursive != recursive)
+ {
+ query->recursive = recursive;
+
+ g_object_notify (G_OBJECT (query), "recursive");
+ }
+}
+
+gboolean
+nautilus_query_is_empty (NautilusQuery *query)
+{
+ if (!query)
+ {
+ return TRUE;
+ }
+
+ if (!query->date_range &&
+ (!query->text || (query->text && query->text[0] == '\0')) &&
+ query->mime_types->len == 0)
+ {
+ return TRUE;
+ }
+
+ return FALSE;
+}
diff --git a/src/nautilus-query.h b/src/nautilus-query.h
new file mode 100644
index 0000000..47f053f
--- /dev/null
+++ b/src/nautilus-query.h
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2005 Novell, Inc.
+ *
+ * Nautilus is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * Nautilus is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; see the file COPYING. If not,
+ * see <http://www.gnu.org/licenses/>.
+ *
+ * Author: Anders Carlsson <andersca@imendio.com>
+ *
+ */
+
+#pragma once
+
+#include <glib-object.h>
+#include <gio/gio.h>
+
+typedef enum {
+ NAUTILUS_QUERY_SEARCH_TYPE_LAST_ACCESS,
+ NAUTILUS_QUERY_SEARCH_TYPE_LAST_MODIFIED,
+ NAUTILUS_QUERY_SEARCH_TYPE_CREATED
+} NautilusQuerySearchType;
+
+typedef enum {
+ NAUTILUS_QUERY_SEARCH_CONTENT_SIMPLE,
+ NAUTILUS_QUERY_SEARCH_CONTENT_FULL_TEXT,
+} NautilusQuerySearchContent;
+
+typedef enum {
+ NAUTILUS_QUERY_RECURSIVE_NEVER,
+ NAUTILUS_QUERY_RECURSIVE_ALWAYS,
+ NAUTILUS_QUERY_RECURSIVE_LOCAL_ONLY,
+ NAUTILUS_QUERY_RECURSIVE_INDEXED_ONLY,
+} NautilusQueryRecursive;
+
+#define NAUTILUS_TYPE_QUERY (nautilus_query_get_type ())
+
+G_DECLARE_FINAL_TYPE (NautilusQuery, nautilus_query, NAUTILUS, QUERY, GObject)
+
+NautilusQuery* nautilus_query_new (void);
+
+char * nautilus_query_get_text (NautilusQuery *query);
+void nautilus_query_set_text (NautilusQuery *query, const char *text);
+
+gboolean nautilus_query_get_show_hidden_files (NautilusQuery *query);
+void nautilus_query_set_show_hidden_files (NautilusQuery *query, gboolean show_hidden);
+
+GFile* nautilus_query_get_location (NautilusQuery *query);
+void nautilus_query_set_location (NautilusQuery *query,
+ GFile *location);
+
+GPtrArray * nautilus_query_get_mime_types (NautilusQuery *query);
+void nautilus_query_set_mime_types (NautilusQuery *query, GPtrArray *mime_types);
+
+NautilusQuerySearchContent nautilus_query_get_search_content (NautilusQuery *query);
+void nautilus_query_set_search_content (NautilusQuery *query,
+ NautilusQuerySearchContent content);
+
+NautilusQuerySearchType nautilus_query_get_search_type (NautilusQuery *query);
+void nautilus_query_set_search_type (NautilusQuery *query,
+ NautilusQuerySearchType type);
+
+GPtrArray* nautilus_query_get_date_range (NautilusQuery *query);
+void nautilus_query_set_date_range (NautilusQuery *query,
+ GPtrArray *date_range);
+
+NautilusQueryRecursive nautilus_query_get_recursive (NautilusQuery *query);
+void nautilus_query_set_recursive (NautilusQuery *query,
+ NautilusQueryRecursive recursive);
+
+gboolean nautilus_query_get_searching (NautilusQuery *query);
+
+void nautilus_query_set_searching (NautilusQuery *query,
+ gboolean searching);
+
+gdouble nautilus_query_matches_string (NautilusQuery *query, const gchar *string);
+
+char * nautilus_query_to_readable_string (NautilusQuery *query);
+
+gboolean nautilus_query_is_empty (NautilusQuery *query);
diff --git a/src/nautilus-rename-file-popover-controller.c b/src/nautilus-rename-file-popover-controller.c
new file mode 100644
index 0000000..0f3b329
--- /dev/null
+++ b/src/nautilus-rename-file-popover-controller.c
@@ -0,0 +1,439 @@
+/* nautilus-rename-file-popover-controller.c
+ *
+ * Copyright (C) 2016 the Nautilus developers
+ *
+ * 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/>.
+ *
+ */
+
+#include <glib/gi18n.h>
+
+#include <eel/eel-vfs-extensions.h>
+
+#include "nautilus-rename-file-popover-controller.h"
+
+#include "nautilus-directory.h"
+#include "nautilus-file-private.h"
+
+
+#define RENAME_ENTRY_MIN_CHARS 30
+#define RENAME_ENTRY_MAX_CHARS 50
+
+struct _NautilusRenameFilePopoverController
+{
+ NautilusFileNameWidgetController parent_instance;
+
+ NautilusFile *target_file;
+ gboolean target_is_folder;
+
+ GtkWidget *rename_file_popover;
+ GtkWidget *name_entry;
+ GtkWidget *title_label;
+
+ gulong closed_handler_id;
+ gulong file_changed_handler_id;
+ gulong key_press_event_handler_id;
+};
+
+G_DEFINE_TYPE (NautilusRenameFilePopoverController, nautilus_rename_file_popover_controller, NAUTILUS_TYPE_FILE_NAME_WIDGET_CONTROLLER)
+
+static void
+disconnect_signal_handlers (NautilusRenameFilePopoverController *self)
+{
+ g_assert (NAUTILUS_IS_RENAME_FILE_POPOVER_CONTROLLER (self));
+
+ g_clear_signal_handler (&self->closed_handler_id, self->rename_file_popover);
+ g_clear_signal_handler (&self->file_changed_handler_id, self->target_file);
+ g_clear_signal_handler (&self->key_press_event_handler_id, self->name_entry);
+}
+
+static void
+reset_state (NautilusRenameFilePopoverController *self)
+{
+ g_assert (NAUTILUS_IS_RENAME_FILE_POPOVER_CONTROLLER (self));
+
+ disconnect_signal_handlers (self);
+
+ g_clear_object (&self->target_file);
+
+ gtk_popover_popdown (GTK_POPOVER (self->rename_file_popover));
+}
+
+static void
+rename_file_popover_controller_on_closed (GtkPopover *popover,
+ gpointer user_data)
+{
+ NautilusRenameFilePopoverController *controller;
+
+ controller = NAUTILUS_RENAME_FILE_POPOVER_CONTROLLER (user_data);
+
+ reset_state (controller);
+
+ g_signal_emit_by_name (controller, "cancelled");
+}
+
+static gboolean
+nautilus_rename_file_popover_controller_name_is_valid (NautilusFileNameWidgetController *controller,
+ gchar *name,
+ gchar **error_message)
+{
+ NautilusRenameFilePopoverController *self;
+ gboolean is_valid;
+
+ self = NAUTILUS_RENAME_FILE_POPOVER_CONTROLLER (controller);
+
+ is_valid = TRUE;
+ if (strlen (name) == 0)
+ {
+ is_valid = FALSE;
+ }
+ else if (strstr (name, "/") != NULL)
+ {
+ is_valid = FALSE;
+ if (self->target_is_folder)
+ {
+ *error_message = _("Folder names cannot contain “/”.");
+ }
+ else
+ {
+ *error_message = _("File names cannot contain “/”.");
+ }
+ }
+ else if (strcmp (name, ".") == 0)
+ {
+ is_valid = FALSE;
+ if (self->target_is_folder)
+ {
+ *error_message = _("A folder cannot be called “.”.");
+ }
+ else
+ {
+ *error_message = _("A file cannot be called “.”.");
+ }
+ }
+ else if (strcmp (name, "..") == 0)
+ {
+ is_valid = FALSE;
+ if (self->target_is_folder)
+ {
+ *error_message = _("A folder cannot be called “..”.");
+ }
+ else
+ {
+ *error_message = _("A file cannot be called “..”.");
+ }
+ }
+ else if (nautilus_file_name_widget_controller_is_name_too_long (controller, name))
+ {
+ is_valid = FALSE;
+ if (self->target_is_folder)
+ {
+ *error_message = _("Folder name is too long.");
+ }
+ else
+ {
+ *error_message = _("File name is too long.");
+ }
+ }
+
+ if (is_valid && g_str_has_prefix (name, "."))
+ {
+ /* We must warn about the side effect */
+ if (self->target_is_folder)
+ {
+ *error_message = _("Folders with “.” at the beginning of their name are hidden.");
+ }
+ else
+ {
+ *error_message = _("Files with “.” at the beginning of their name are hidden.");
+ }
+ }
+
+ return is_valid;
+}
+
+static gboolean
+nautilus_rename_file_popover_controller_ignore_existing_file (NautilusFileNameWidgetController *controller,
+ NautilusFile *existing_file)
+{
+ NautilusRenameFilePopoverController *self;
+ g_autofree gchar *display_name = NULL;
+
+ self = NAUTILUS_RENAME_FILE_POPOVER_CONTROLLER (controller);
+
+ display_name = nautilus_file_get_display_name (existing_file);
+
+ return nautilus_file_compare_display_name (self->target_file, display_name) == 0;
+}
+
+static gboolean
+name_entry_on_f2_pressed (GtkWidget *widget,
+ NautilusRenameFilePopoverController *self)
+{
+ guint text_length;
+ gint start_pos;
+ gint end_pos;
+ gboolean all_selected;
+
+ text_length = (guint) gtk_entry_get_text_length (GTK_ENTRY (widget));
+ if (text_length == 0)
+ {
+ return GDK_EVENT_STOP;
+ }
+
+ gtk_editable_get_selection_bounds (GTK_EDITABLE (widget),
+ &start_pos, &end_pos);
+
+ all_selected = (start_pos == 0) && ((guint) end_pos == text_length);
+ if (!all_selected || !nautilus_file_is_regular_file (self->target_file))
+ {
+ gtk_editable_select_region (GTK_EDITABLE (widget), 0, -1);
+ }
+ else
+ {
+ gint start_offset;
+ gint end_offset;
+
+ /* Select the name part without the file extension */
+ eel_filename_get_rename_region (gtk_editable_get_text (GTK_EDITABLE (widget)),
+ &start_offset, &end_offset);
+ gtk_editable_select_region (GTK_EDITABLE (widget),
+ start_offset, end_offset);
+ }
+
+ return GDK_EVENT_STOP;
+}
+
+static gboolean
+name_entry_on_undo (GtkWidget *widget,
+ NautilusRenameFilePopoverController *self)
+{
+ g_autofree gchar *edit_name = NULL;
+
+ edit_name = nautilus_file_get_edit_name (self->target_file);
+
+ gtk_editable_set_text (GTK_EDITABLE (widget), edit_name);
+
+ gtk_editable_select_region (GTK_EDITABLE (widget), 0, -1);
+
+ return GDK_EVENT_STOP;
+}
+
+static gboolean
+on_event_controller_key_key_pressed (GtkEventControllerKey *controller,
+ guint keyval,
+ guint keycode,
+ GdkModifierType state,
+ gpointer user_data)
+{
+ GtkWidget *widget;
+ NautilusRenameFilePopoverController *self;
+
+ widget = gtk_event_controller_get_widget (GTK_EVENT_CONTROLLER (controller));
+ self = NAUTILUS_RENAME_FILE_POPOVER_CONTROLLER (user_data);
+
+ if (keyval == GDK_KEY_F2)
+ {
+ return name_entry_on_f2_pressed (widget, self);
+ }
+ else if (keyval == GDK_KEY_z && (state & GDK_CONTROL_MASK) != 0)
+ {
+ return name_entry_on_undo (widget, self);
+ }
+
+ return GDK_EVENT_PROPAGATE;
+}
+
+static void
+target_file_on_changed (NautilusFile *file,
+ gpointer user_data)
+{
+ NautilusRenameFilePopoverController *controller;
+
+ controller = NAUTILUS_RENAME_FILE_POPOVER_CONTROLLER (user_data);
+
+ if (nautilus_file_is_gone (file))
+ {
+ reset_state (controller);
+
+ g_signal_emit_by_name (controller, "cancelled");
+ }
+}
+
+NautilusRenameFilePopoverController *
+nautilus_rename_file_popover_controller_new (GtkWidget *relative_to)
+{
+ NautilusRenameFilePopoverController *self;
+ g_autoptr (GtkBuilder) builder = NULL;
+ GtkWidget *rename_file_popover;
+ GtkWidget *error_revealer;
+ GtkWidget *error_label;
+ GtkWidget *name_entry;
+ GtkWidget *activate_button;
+ GtkWidget *title_label;
+
+ builder = gtk_builder_new_from_resource ("/org/gnome/nautilus/ui/nautilus-rename-file-popover.ui");
+ rename_file_popover = GTK_WIDGET (gtk_builder_get_object (builder, "rename_file_popover"));
+ error_revealer = GTK_WIDGET (gtk_builder_get_object (builder, "error_revealer"));
+ error_label = GTK_WIDGET (gtk_builder_get_object (builder, "error_label"));
+ name_entry = GTK_WIDGET (gtk_builder_get_object (builder, "name_entry"));
+ activate_button = GTK_WIDGET (gtk_builder_get_object (builder, "rename_button"));
+ title_label = GTK_WIDGET (gtk_builder_get_object (builder, "title_label"));
+
+ self = g_object_new (NAUTILUS_TYPE_RENAME_FILE_POPOVER_CONTROLLER,
+ "error-revealer", error_revealer,
+ "error-label", error_label,
+ "name-entry", name_entry,
+ "activate-button", activate_button,
+ NULL);
+
+ self->rename_file_popover = g_object_ref_sink (rename_file_popover);
+ self->name_entry = name_entry;
+ self->title_label = title_label;
+
+ gtk_popover_set_default_widget (GTK_POPOVER (rename_file_popover), name_entry);
+
+ gtk_widget_set_parent (rename_file_popover, relative_to);
+
+ return self;
+}
+
+NautilusFile *
+nautilus_rename_file_popover_controller_get_target_file (NautilusRenameFilePopoverController *self)
+{
+ g_return_val_if_fail (NAUTILUS_IS_RENAME_FILE_POPOVER_CONTROLLER (self), NULL);
+
+ return self->target_file;
+}
+
+void
+nautilus_rename_file_popover_controller_show_for_file (NautilusRenameFilePopoverController *self,
+ NautilusFile *target_file,
+ GdkRectangle *pointing_to)
+{
+ g_autoptr (NautilusDirectory) containing_directory = NULL;
+ GtkEventController *controller;
+ g_autofree gchar *edit_name = NULL;
+ gint n_chars;
+
+ g_assert (NAUTILUS_IS_RENAME_FILE_POPOVER_CONTROLLER (self));
+ g_assert (NAUTILUS_IS_FILE (target_file));
+
+ reset_state (self);
+
+ self->target_file = g_object_ref (target_file);
+
+ if (!nautilus_file_is_self_owned (self->target_file))
+ {
+ g_autoptr (NautilusFile) parent = NULL;
+
+ parent = nautilus_file_get_parent (self->target_file);
+ containing_directory = nautilus_directory_get_for_file (parent);
+ }
+ else
+ {
+ containing_directory = nautilus_directory_get_for_file (self->target_file);
+ }
+
+ nautilus_file_name_widget_controller_set_containing_directory (NAUTILUS_FILE_NAME_WIDGET_CONTROLLER (self),
+ containing_directory);
+
+ self->target_is_folder = nautilus_file_is_directory (self->target_file);
+
+ self->closed_handler_id = g_signal_connect (self->rename_file_popover,
+ "closed",
+ G_CALLBACK (rename_file_popover_controller_on_closed),
+ self);
+
+ self->file_changed_handler_id = g_signal_connect (self->target_file,
+ "changed",
+ G_CALLBACK (target_file_on_changed),
+ self);
+
+ controller = gtk_event_controller_key_new ();
+ gtk_widget_add_controller (self->name_entry, controller);
+ g_signal_connect (controller, "key-pressed",
+ G_CALLBACK (on_event_controller_key_key_pressed), self);
+
+ gtk_label_set_text (GTK_LABEL (self->title_label),
+ self->target_is_folder ? _("Rename Folder") :
+ _("Rename File"));
+
+ edit_name = nautilus_file_get_edit_name (self->target_file);
+
+ gtk_editable_set_text (GTK_EDITABLE (self->name_entry), edit_name);
+
+ gtk_popover_set_pointing_to (GTK_POPOVER (self->rename_file_popover), pointing_to);
+
+ gtk_popover_popup (GTK_POPOVER (self->rename_file_popover));
+
+ if (nautilus_file_is_regular_file (self->target_file))
+ {
+ gint start_offset;
+ gint end_offset;
+
+ /* Select the name part without the file extension */
+ eel_filename_get_rename_region (edit_name,
+ &start_offset, &end_offset);
+ gtk_editable_select_region (GTK_EDITABLE (self->name_entry),
+ start_offset, end_offset);
+ }
+
+ n_chars = g_utf8_strlen (edit_name, -1);
+ gtk_editable_set_width_chars (GTK_EDITABLE (self->name_entry),
+ MIN (MAX (n_chars, RENAME_ENTRY_MIN_CHARS),
+ RENAME_ENTRY_MAX_CHARS));
+}
+
+static void
+on_name_accepted (NautilusFileNameWidgetController *controller)
+{
+ NautilusRenameFilePopoverController *self;
+
+ self = NAUTILUS_RENAME_FILE_POPOVER_CONTROLLER (controller);
+
+ reset_state (self);
+}
+
+static void
+nautilus_rename_file_popover_controller_init (NautilusRenameFilePopoverController *self)
+{
+ g_signal_connect_after (self, "name-accepted", G_CALLBACK (on_name_accepted), self);
+}
+
+static void
+nautilus_rename_file_popover_controller_finalize (GObject *object)
+{
+ NautilusRenameFilePopoverController *self;
+
+ self = NAUTILUS_RENAME_FILE_POPOVER_CONTROLLER (object);
+
+ reset_state (self);
+
+ g_clear_pointer (&self->rename_file_popover, gtk_widget_unparent);
+
+ G_OBJECT_CLASS (nautilus_rename_file_popover_controller_parent_class)->finalize (object);
+}
+
+static void
+nautilus_rename_file_popover_controller_class_init (NautilusRenameFilePopoverControllerClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ NautilusFileNameWidgetControllerClass *parent_class = NAUTILUS_FILE_NAME_WIDGET_CONTROLLER_CLASS (klass);
+
+ object_class->finalize = nautilus_rename_file_popover_controller_finalize;
+
+ parent_class->name_is_valid = nautilus_rename_file_popover_controller_name_is_valid;
+ parent_class->ignore_existing_file = nautilus_rename_file_popover_controller_ignore_existing_file;
+}
diff --git a/src/nautilus-rename-file-popover-controller.h b/src/nautilus-rename-file-popover-controller.h
new file mode 100644
index 0000000..d9b229e
--- /dev/null
+++ b/src/nautilus-rename-file-popover-controller.h
@@ -0,0 +1,37 @@
+/* nautilus-rename-file-popover-controller.h
+ *
+ * Copyright (C) 2016 the Nautilus developers
+ *
+ * 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/>.
+ *
+ */
+
+#pragma once
+
+#include <glib.h>
+#include <gtk/gtk.h>
+
+#include "nautilus-file-name-widget-controller.h"
+#include "nautilus-file.h"
+
+#define NAUTILUS_TYPE_RENAME_FILE_POPOVER_CONTROLLER nautilus_rename_file_popover_controller_get_type ()
+G_DECLARE_FINAL_TYPE (NautilusRenameFilePopoverController, nautilus_rename_file_popover_controller, NAUTILUS, RENAME_FILE_POPOVER_CONTROLLER, NautilusFileNameWidgetController)
+
+NautilusRenameFilePopoverController * nautilus_rename_file_popover_controller_new (GtkWidget *relative_to);
+
+NautilusFile * nautilus_rename_file_popover_controller_get_target_file (NautilusRenameFilePopoverController *controller);
+
+void nautilus_rename_file_popover_controller_show_for_file (NautilusRenameFilePopoverController *controller,
+ NautilusFile *target_file,
+ GdkRectangle *pointing_to);
diff --git a/src/nautilus-search-directory-file.c b/src/nautilus-search-directory-file.c
new file mode 100644
index 0000000..d118bab
--- /dev/null
+++ b/src/nautilus-search-directory-file.c
@@ -0,0 +1,313 @@
+/*
+ * nautilus-search-directory-file.c: Subclass of NautilusFile to help implement the
+ * searches
+ *
+ * Copyright (C) 2005 Novell, 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/>.
+ *
+ * Author: Anders Carlsson <andersca@imendio.com>
+ */
+
+#include "nautilus-search-directory-file.h"
+
+#include <eel/eel-glib-extensions.h>
+#include <glib/gi18n.h>
+#include <gtk/gtk.h>
+#include <string.h>
+
+#include "nautilus-directory-notify.h"
+#include "nautilus-directory-private.h"
+#include "nautilus-enums.h"
+#include "nautilus-file-private.h"
+#include "nautilus-file-utilities.h"
+#include "nautilus-keyfile-metadata.h"
+#include "nautilus-query.h"
+#include "nautilus-search-directory.h"
+
+struct _NautilusSearchDirectoryFile
+{
+ NautilusFile parent_instance;
+
+ gchar *metadata_filename;
+};
+
+G_DEFINE_TYPE (NautilusSearchDirectoryFile, nautilus_search_directory_file, NAUTILUS_TYPE_FILE);
+
+
+static void
+search_directory_file_monitor_add (NautilusFile *file,
+ gconstpointer client,
+ NautilusFileAttributes attributes)
+{
+ /* No need for monitoring, we always emit changed when files
+ * are added/removed, and no other metadata changes */
+
+ /* Update display name, in case this didn't happen yet */
+ nautilus_search_directory_file_update_display_name (NAUTILUS_SEARCH_DIRECTORY_FILE (file));
+}
+
+static void
+search_directory_file_monitor_remove (NautilusFile *file,
+ gconstpointer client)
+{
+ /* Do nothing here, we don't have any monitors */
+}
+
+static void
+search_directory_file_call_when_ready (NautilusFile *file,
+ NautilusFileAttributes file_attributes,
+ NautilusFileCallback callback,
+ gpointer callback_data)
+{
+ /* Update display name, in case this didn't happen yet */
+ nautilus_search_directory_file_update_display_name (NAUTILUS_SEARCH_DIRECTORY_FILE (file));
+
+ if (callback != NULL)
+ {
+ /* All data for directory-as-file is always up to date */
+ (*callback)(file, callback_data);
+ }
+}
+
+static void
+search_directory_file_cancel_call_when_ready (NautilusFile *file,
+ NautilusFileCallback callback,
+ gpointer callback_data)
+{
+ /* Do nothing here, we don't have any pending calls */
+}
+
+static gboolean
+search_directory_file_check_if_ready (NautilusFile *file,
+ NautilusFileAttributes attributes)
+{
+ return TRUE;
+}
+
+static gboolean
+search_directory_file_get_item_count (NautilusFile *file,
+ guint *count,
+ gboolean *count_unreadable)
+{
+ GList *file_list;
+
+ if (count)
+ {
+ NautilusDirectory *directory;
+
+ directory = nautilus_file_get_directory (file);
+ file_list = nautilus_directory_get_file_list (directory);
+
+ *count = g_list_length (file_list);
+
+ nautilus_file_list_free (file_list);
+ }
+
+ return TRUE;
+}
+
+static NautilusRequestStatus
+search_directory_file_get_deep_counts (NautilusFile *file,
+ guint *directory_count,
+ guint *file_count,
+ guint *unreadable_directory_count,
+ goffset *total_size)
+{
+ NautilusDirectory *directory;
+ NautilusFile *dir_file;
+ GList *file_list, *l;
+ guint dirs, files;
+ GFileType type;
+
+ directory = nautilus_file_get_directory (file);
+ file_list = nautilus_directory_get_file_list (directory);
+
+ dirs = files = 0;
+ for (l = file_list; l != NULL; l = l->next)
+ {
+ dir_file = NAUTILUS_FILE (l->data);
+ type = nautilus_file_get_file_type (dir_file);
+ if (type == G_FILE_TYPE_DIRECTORY)
+ {
+ dirs++;
+ }
+ else
+ {
+ files++;
+ }
+ }
+
+ if (directory_count != NULL)
+ {
+ *directory_count = dirs;
+ }
+ if (file_count != NULL)
+ {
+ *file_count = files;
+ }
+ if (unreadable_directory_count != NULL)
+ {
+ *unreadable_directory_count = 0;
+ }
+ if (total_size != NULL)
+ {
+ /* FIXME: Maybe we want to calculate this? */
+ *total_size = 0;
+ }
+
+ nautilus_file_list_free (file_list);
+
+ return NAUTILUS_REQUEST_DONE;
+}
+
+static char *
+search_directory_file_get_where_string (NautilusFile *file)
+{
+ return g_strdup (_("Search"));
+}
+
+static void
+search_directory_file_set_metadata (NautilusFile *file,
+ const char *key,
+ const char *value)
+{
+ NautilusSearchDirectoryFile *search_file;
+
+ search_file = NAUTILUS_SEARCH_DIRECTORY_FILE (file);
+ nautilus_keyfile_metadata_set_string (file,
+ search_file->metadata_filename,
+ "directory", key, value);
+}
+
+static void
+search_directory_file_set_metadata_as_list (NautilusFile *file,
+ const char *key,
+ char **value)
+{
+ NautilusSearchDirectoryFile *search_file;
+
+ search_file = NAUTILUS_SEARCH_DIRECTORY_FILE (file);
+ nautilus_keyfile_metadata_set_stringv (file,
+ search_file->metadata_filename,
+ "directory", key, (const gchar **) value);
+}
+
+void
+nautilus_search_directory_file_update_display_name (NautilusSearchDirectoryFile *search_file)
+{
+ NautilusFile *file;
+ NautilusDirectory *directory;
+ NautilusSearchDirectory *search_dir;
+ NautilusQuery *query;
+ char *display_name;
+ gboolean changed;
+
+
+ display_name = NULL;
+ file = NAUTILUS_FILE (search_file);
+ directory = nautilus_file_get_directory (file);
+ if (directory != NULL)
+ {
+ search_dir = NAUTILUS_SEARCH_DIRECTORY (directory);
+ query = nautilus_search_directory_get_query (search_dir);
+
+ if (query != NULL)
+ {
+ display_name = nautilus_query_to_readable_string (query);
+ g_object_unref (query);
+ }
+ }
+
+ if (display_name == NULL)
+ {
+ display_name = g_strdup (_("Search"));
+ }
+
+ changed = nautilus_file_set_display_name (file, display_name, NULL, TRUE);
+ if (changed)
+ {
+ nautilus_file_emit_changed (file);
+ }
+
+ g_free (display_name);
+}
+
+static void
+nautilus_search_directory_file_init (NautilusSearchDirectoryFile *search_file)
+{
+ NautilusFile *file;
+ gchar *xdg_dir;
+
+ file = NAUTILUS_FILE (search_file);
+
+ xdg_dir = nautilus_get_user_directory ();
+ search_file->metadata_filename = g_build_filename (xdg_dir,
+ "search-metadata",
+ NULL);
+ g_free (xdg_dir);
+
+ file->details->got_file_info = TRUE;
+ file->details->mime_type = g_ref_string_new_intern ("x-directory/normal");
+ file->details->type = G_FILE_TYPE_DIRECTORY;
+ file->details->size = 0;
+
+ file->details->file_info_is_up_to_date = TRUE;
+
+ file->details->custom_icon = NULL;
+ file->details->activation_uri = NULL;
+
+ file->details->directory_count = 0;
+ file->details->got_directory_count = TRUE;
+ file->details->directory_count_is_up_to_date = TRUE;
+
+ nautilus_file_set_display_name (file, _("Search"), NULL, TRUE);
+}
+
+static void
+nautilus_search_directory_file_finalize (GObject *object)
+{
+ NautilusSearchDirectoryFile *search_file;
+
+ search_file = NAUTILUS_SEARCH_DIRECTORY_FILE (object);
+
+ g_free (search_file->metadata_filename);
+
+ G_OBJECT_CLASS (nautilus_search_directory_file_parent_class)->finalize (object);
+}
+
+static void
+nautilus_search_directory_file_class_init (NautilusSearchDirectoryFileClass *klass)
+{
+ GObjectClass *object_class;
+ NautilusFileClass *file_class;
+
+ object_class = G_OBJECT_CLASS (klass);
+ file_class = NAUTILUS_FILE_CLASS (klass);
+
+ object_class->finalize = nautilus_search_directory_file_finalize;
+
+ file_class->default_file_type = G_FILE_TYPE_DIRECTORY;
+
+ file_class->monitor_add = search_directory_file_monitor_add;
+ file_class->monitor_remove = search_directory_file_monitor_remove;
+ file_class->call_when_ready = search_directory_file_call_when_ready;
+ file_class->cancel_call_when_ready = search_directory_file_cancel_call_when_ready;
+ file_class->check_if_ready = search_directory_file_check_if_ready;
+ file_class->get_item_count = search_directory_file_get_item_count;
+ file_class->get_deep_counts = search_directory_file_get_deep_counts;
+ file_class->get_where_string = search_directory_file_get_where_string;
+ file_class->set_metadata = search_directory_file_set_metadata;
+ file_class->set_metadata_as_list = search_directory_file_set_metadata_as_list;
+}
diff --git a/src/nautilus-search-directory-file.h b/src/nautilus-search-directory-file.h
new file mode 100644
index 0000000..05c0ec0
--- /dev/null
+++ b/src/nautilus-search-directory-file.h
@@ -0,0 +1,32 @@
+/*
+ nautilus-search-directory-file.h: Subclass of NautilusFile to implement the
+ the case of the search directory
+
+ Copyright (C) 2003 Red Hat, 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/>.
+
+ Author: Alexander Larsson <alexl@redhat.com>
+*/
+
+#pragma once
+
+#include "nautilus-file.h"
+
+#define NAUTILUS_TYPE_SEARCH_DIRECTORY_FILE nautilus_search_directory_file_get_type ()
+G_DECLARE_FINAL_TYPE (NautilusSearchDirectoryFile, nautilus_search_directory_file,
+ NAUTILUS, SEARCH_DIRECTORY_FILE,
+ NautilusFile)
+
+void nautilus_search_directory_file_update_display_name (NautilusSearchDirectoryFile *search_file);
diff --git a/src/nautilus-search-directory.c b/src/nautilus-search-directory.c
new file mode 100644
index 0000000..24545fa
--- /dev/null
+++ b/src/nautilus-search-directory.c
@@ -0,0 +1,1080 @@
+/*
+ * Copyright (C) 2005 Novell, 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/>.
+ *
+ * Author: Anders Carlsson <andersca@imendio.com>
+ */
+
+#include "nautilus-search-directory.h"
+
+#include <eel/eel-glib-extensions.h>
+#include <gio/gio.h>
+#include <gtk/gtk.h>
+#include <string.h>
+#include <sys/time.h>
+
+#include "nautilus-directory-private.h"
+#include "nautilus-file-private.h"
+#include "nautilus-file-utilities.h"
+#include "nautilus-file.h"
+#include "nautilus-query.h"
+#include "nautilus-search-directory-file.h"
+#include "nautilus-search-engine-model.h"
+#include "nautilus-search-engine.h"
+#include "nautilus-search-provider.h"
+
+struct _NautilusSearchDirectory
+{
+ NautilusDirectory parent_instance;
+
+ NautilusQuery *query;
+
+ NautilusSearchEngine *engine;
+
+ gboolean search_running;
+ /* When the search directory is stopped or cancelled, we migth wait
+ * until all data and signals from previous search are stopped and removed
+ * from the search engine. While this situation happens we don't want to connect
+ * clients to our signals, and we will wait until the search data and signals
+ * are valid and ready.
+ * The worst thing that can happens if we don't do this is that new clients
+ * migth get the information of old searchs if they are waiting_for_file_list.
+ * But that shouldn't be a big deal since old clients have the old information.
+ * But anyway it's currently unused for this case since the only client is
+ * nautilus-view and is not waiting_for_file_list :) .
+ *
+ * The other use case is for letting clients know if information of the directory
+ * is outdated or not valid. This might happens for automatic
+ * scheduled timeouts. */
+ gboolean search_ready_and_valid;
+
+ GList *files;
+ GHashTable *files_hash;
+
+ GList *monitor_list;
+ GList *callback_list;
+ GList *pending_callback_list;
+
+ GBinding *binding;
+
+ NautilusDirectory *base_model;
+};
+
+typedef struct
+{
+ gboolean monitor_hidden_files;
+ NautilusFileAttributes monitor_attributes;
+
+ gconstpointer client;
+} SearchMonitor;
+
+typedef struct
+{
+ NautilusSearchDirectory *search_directory;
+
+ NautilusDirectoryCallback callback;
+ gpointer callback_data;
+
+ NautilusFileAttributes wait_for_attributes;
+ gboolean wait_for_file_list;
+ GList *file_list;
+ GHashTable *non_ready_hash;
+} SearchCallback;
+
+enum
+{
+ PROP_0,
+ PROP_BASE_MODEL,
+ PROP_QUERY,
+ NUM_PROPERTIES
+};
+
+G_DEFINE_TYPE_WITH_CODE (NautilusSearchDirectory, nautilus_search_directory, NAUTILUS_TYPE_DIRECTORY,
+ nautilus_ensure_extension_points ();
+ /* It looks like you’re implementing an extension point.
+ * Did you modify nautilus_ensure_extension_builtins() accordingly?
+ *
+ * • Yes
+ * • Doing it right now
+ */
+ g_io_extension_point_implement (NAUTILUS_DIRECTORY_PROVIDER_EXTENSION_POINT_NAME,
+ g_define_type_id,
+ NAUTILUS_SEARCH_DIRECTORY_PROVIDER_NAME,
+ 0));
+
+static GParamSpec *properties[NUM_PROPERTIES] = { NULL, };
+
+static void search_engine_hits_added (NautilusSearchEngine *engine,
+ GList *hits,
+ NautilusSearchDirectory *self);
+static void search_engine_error (NautilusSearchEngine *engine,
+ const char *error,
+ NautilusSearchDirectory *self);
+static void search_callback_file_ready_callback (NautilusFile *file,
+ gpointer data);
+static void file_changed (NautilusFile *file,
+ NautilusSearchDirectory *self);
+
+static void
+reset_file_list (NautilusSearchDirectory *self)
+{
+ GList *list, *monitor_list;
+ NautilusFile *file;
+ SearchMonitor *monitor;
+
+ /* Remove file connections */
+ for (list = self->files; list != NULL; list = list->next)
+ {
+ file = list->data;
+
+ /* Disconnect change handler */
+ g_signal_handlers_disconnect_by_func (file, file_changed, self);
+
+ /* Remove monitors */
+ for (monitor_list = self->monitor_list; monitor_list;
+ monitor_list = monitor_list->next)
+ {
+ monitor = monitor_list->data;
+ nautilus_file_monitor_remove (file, monitor);
+ }
+ }
+
+ nautilus_file_list_free (self->files);
+ self->files = NULL;
+
+ g_hash_table_remove_all (self->files_hash);
+}
+
+static void
+set_hidden_files (NautilusSearchDirectory *self)
+{
+ GList *l;
+ SearchMonitor *monitor;
+ gboolean monitor_hidden = FALSE;
+
+ for (l = self->monitor_list; l != NULL; l = l->next)
+ {
+ monitor = l->data;
+ monitor_hidden |= monitor->monitor_hidden_files;
+
+ if (monitor_hidden)
+ {
+ break;
+ }
+ }
+
+ nautilus_query_set_show_hidden_files (self->query, monitor_hidden);
+}
+
+static void
+start_search (NautilusSearchDirectory *self)
+{
+ NautilusSearchEngineModel *model_provider;
+
+ if (!self->query)
+ {
+ return;
+ }
+
+ if (self->search_running)
+ {
+ return;
+ }
+
+ if (!self->monitor_list && !self->pending_callback_list)
+ {
+ return;
+ }
+
+ /* We need to start the search engine */
+ self->search_running = TRUE;
+ self->search_ready_and_valid = FALSE;
+
+ set_hidden_files (self);
+ nautilus_search_provider_set_query (NAUTILUS_SEARCH_PROVIDER (self->engine),
+ self->query);
+
+ model_provider = nautilus_search_engine_get_model_provider (self->engine);
+ nautilus_search_engine_model_set_model (model_provider, self->base_model);
+
+ reset_file_list (self);
+
+ nautilus_search_provider_start (NAUTILUS_SEARCH_PROVIDER (self->engine));
+}
+
+static void
+stop_search (NautilusSearchDirectory *self)
+{
+ if (!self->search_running)
+ {
+ return;
+ }
+
+ self->search_running = FALSE;
+ nautilus_search_provider_stop (NAUTILUS_SEARCH_PROVIDER (self->engine));
+
+ reset_file_list (self);
+}
+
+static void
+file_changed (NautilusFile *file,
+ NautilusSearchDirectory *self)
+{
+ GList list;
+
+ list.data = file;
+ list.next = NULL;
+
+ nautilus_directory_emit_files_changed (NAUTILUS_DIRECTORY (self), &list);
+}
+
+static void
+search_monitor_add (NautilusDirectory *directory,
+ gconstpointer client,
+ gboolean monitor_hidden_files,
+ NautilusFileAttributes file_attributes,
+ NautilusDirectoryCallback callback,
+ gpointer callback_data)
+{
+ GList *list;
+ SearchMonitor *monitor;
+ NautilusSearchDirectory *self;
+ NautilusFile *file;
+
+ self = NAUTILUS_SEARCH_DIRECTORY (directory);
+
+ monitor = g_new0 (SearchMonitor, 1);
+ monitor->monitor_hidden_files = monitor_hidden_files;
+ monitor->monitor_attributes = file_attributes;
+ monitor->client = client;
+
+ self->monitor_list = g_list_prepend (self->monitor_list, monitor);
+
+ if (callback != NULL)
+ {
+ (*callback)(directory, self->files, callback_data);
+ }
+
+ for (list = self->files; list != NULL; list = list->next)
+ {
+ file = list->data;
+
+ /* Add monitors */
+ nautilus_file_monitor_add (file, monitor, file_attributes);
+ }
+
+ start_search (self);
+}
+
+static void
+search_monitor_remove_file_monitors (SearchMonitor *monitor,
+ NautilusSearchDirectory *self)
+{
+ GList *list;
+ NautilusFile *file;
+
+ for (list = self->files; list != NULL; list = list->next)
+ {
+ file = list->data;
+
+ nautilus_file_monitor_remove (file, monitor);
+ }
+}
+
+static void
+search_monitor_destroy (SearchMonitor *monitor,
+ NautilusSearchDirectory *self)
+{
+ search_monitor_remove_file_monitors (monitor, self);
+
+ g_free (monitor);
+}
+
+static void
+search_monitor_remove (NautilusDirectory *directory,
+ gconstpointer client)
+{
+ NautilusSearchDirectory *self;
+ SearchMonitor *monitor;
+ GList *list;
+
+ self = NAUTILUS_SEARCH_DIRECTORY (directory);
+
+ for (list = self->monitor_list; list != NULL; list = list->next)
+ {
+ monitor = list->data;
+
+ if (monitor->client == client)
+ {
+ self->monitor_list = g_list_delete_link (self->monitor_list, list);
+
+ search_monitor_destroy (monitor, self);
+
+ break;
+ }
+ }
+
+ if (!self->monitor_list)
+ {
+ stop_search (self);
+ }
+}
+
+static void
+cancel_call_when_ready (gpointer key,
+ gpointer value,
+ gpointer user_data)
+{
+ SearchCallback *search_callback;
+ NautilusFile *file;
+
+ file = key;
+ search_callback = user_data;
+
+ nautilus_file_cancel_call_when_ready (file, search_callback_file_ready_callback,
+ search_callback);
+}
+
+static void
+search_callback_destroy (SearchCallback *search_callback)
+{
+ if (search_callback->non_ready_hash)
+ {
+ g_hash_table_foreach (search_callback->non_ready_hash, cancel_call_when_ready, search_callback);
+ g_hash_table_destroy (search_callback->non_ready_hash);
+ }
+
+ nautilus_file_list_free (search_callback->file_list);
+
+ g_free (search_callback);
+}
+
+static void
+search_callback_invoke_and_destroy (SearchCallback *search_callback)
+{
+ search_callback->callback (NAUTILUS_DIRECTORY (search_callback->search_directory),
+ search_callback->file_list,
+ search_callback->callback_data);
+
+ search_callback->search_directory->callback_list =
+ g_list_remove (search_callback->search_directory->callback_list, search_callback);
+
+ search_callback_destroy (search_callback);
+}
+
+static void
+search_callback_file_ready_callback (NautilusFile *file,
+ gpointer data)
+{
+ SearchCallback *search_callback = data;
+
+ g_hash_table_remove (search_callback->non_ready_hash, file);
+
+ if (g_hash_table_size (search_callback->non_ready_hash) == 0)
+ {
+ search_callback_invoke_and_destroy (search_callback);
+ }
+}
+
+static void
+search_callback_add_file_callbacks (SearchCallback *callback)
+{
+ GList *file_list_copy, *list;
+ NautilusFile *file;
+
+ file_list_copy = g_list_copy (callback->file_list);
+
+ for (list = file_list_copy; list != NULL; list = list->next)
+ {
+ file = list->data;
+
+ nautilus_file_call_when_ready (file,
+ callback->wait_for_attributes,
+ search_callback_file_ready_callback,
+ callback);
+ }
+ g_list_free (file_list_copy);
+}
+
+static SearchCallback *
+search_callback_find (NautilusSearchDirectory *self,
+ NautilusDirectoryCallback callback,
+ gpointer callback_data)
+{
+ SearchCallback *search_callback;
+ GList *list;
+
+ for (list = self->callback_list; list != NULL; list = list->next)
+ {
+ search_callback = list->data;
+
+ if (search_callback->callback == callback &&
+ search_callback->callback_data == callback_data)
+ {
+ return search_callback;
+ }
+ }
+
+ return NULL;
+}
+
+static SearchCallback *
+search_callback_find_pending (NautilusSearchDirectory *self,
+ NautilusDirectoryCallback callback,
+ gpointer callback_data)
+{
+ SearchCallback *search_callback;
+ GList *list;
+
+ for (list = self->pending_callback_list; list != NULL; list = list->next)
+ {
+ search_callback = list->data;
+
+ if (search_callback->callback == callback &&
+ search_callback->callback_data == callback_data)
+ {
+ return search_callback;
+ }
+ }
+
+ return NULL;
+}
+
+static GHashTable *
+file_list_to_hash_table (GList *file_list)
+{
+ GList *list;
+ GHashTable *table;
+
+ if (!file_list)
+ {
+ return NULL;
+ }
+
+ table = g_hash_table_new (NULL, NULL);
+
+ for (list = file_list; list != NULL; list = list->next)
+ {
+ g_hash_table_insert (table, list->data, list->data);
+ }
+
+ return table;
+}
+
+static void
+search_call_when_ready (NautilusDirectory *directory,
+ NautilusFileAttributes file_attributes,
+ gboolean wait_for_file_list,
+ NautilusDirectoryCallback callback,
+ gpointer callback_data)
+{
+ NautilusSearchDirectory *self;
+ SearchCallback *search_callback;
+
+ self = NAUTILUS_SEARCH_DIRECTORY (directory);
+
+ search_callback = search_callback_find (self, callback, callback_data);
+ if (search_callback == NULL)
+ {
+ search_callback = search_callback_find_pending (self, callback, callback_data);
+ }
+
+ if (search_callback)
+ {
+ g_warning ("tried to add a new callback while an old one was pending");
+ return;
+ }
+
+ search_callback = g_new0 (SearchCallback, 1);
+ search_callback->search_directory = self;
+ search_callback->callback = callback;
+ search_callback->callback_data = callback_data;
+ search_callback->wait_for_attributes = file_attributes;
+ search_callback->wait_for_file_list = wait_for_file_list;
+
+ if (wait_for_file_list && !self->search_ready_and_valid)
+ {
+ /* Add it to the pending callback list, which will be
+ * processed when the directory has valid data from the new
+ * search and all data and signals from previous searchs is removed. */
+ self->pending_callback_list =
+ g_list_prepend (self->pending_callback_list, search_callback);
+
+ /* We might need to start the search engine */
+ start_search (self);
+ }
+ else
+ {
+ search_callback->file_list = nautilus_file_list_copy (self->files);
+ search_callback->non_ready_hash = file_list_to_hash_table (self->files);
+
+ if (!search_callback->non_ready_hash)
+ {
+ /* If there are no ready files, we invoke the callback
+ * with an empty list.
+ */
+ search_callback_invoke_and_destroy (search_callback);
+ }
+ else
+ {
+ self->callback_list = g_list_prepend (self->callback_list, search_callback);
+ search_callback_add_file_callbacks (search_callback);
+ }
+ }
+}
+
+static void
+search_cancel_callback (NautilusDirectory *directory,
+ NautilusDirectoryCallback callback,
+ gpointer callback_data)
+{
+ NautilusSearchDirectory *self;
+ SearchCallback *search_callback;
+
+ self = NAUTILUS_SEARCH_DIRECTORY (directory);
+ search_callback = search_callback_find (self, callback, callback_data);
+
+ if (search_callback)
+ {
+ self->callback_list = g_list_remove (self->callback_list, search_callback);
+
+ search_callback_destroy (search_callback);
+
+ goto done;
+ }
+
+ /* Check for a pending callback */
+ search_callback = search_callback_find_pending (self, callback, callback_data);
+
+ if (search_callback)
+ {
+ self->pending_callback_list = g_list_remove (self->pending_callback_list, search_callback);
+
+ search_callback_destroy (search_callback);
+ }
+
+done:
+ if (!self->callback_list && !self->pending_callback_list)
+ {
+ stop_search (self);
+ }
+}
+
+static void
+search_callback_add_pending_file_callbacks (SearchCallback *callback)
+{
+ callback->file_list = nautilus_file_list_copy (callback->search_directory->files);
+ callback->non_ready_hash = file_list_to_hash_table (callback->search_directory->files);
+
+ search_callback_add_file_callbacks (callback);
+}
+
+static void
+search_directory_add_pending_files_callbacks (NautilusSearchDirectory *self)
+{
+ /* Add all file callbacks */
+ g_list_foreach (self->pending_callback_list,
+ (GFunc) search_callback_add_pending_file_callbacks, NULL);
+ self->callback_list = g_list_concat (self->callback_list,
+ self->pending_callback_list);
+
+ g_list_free (self->pending_callback_list);
+ self->pending_callback_list = NULL;
+}
+
+static void
+on_search_directory_search_ready_and_valid (NautilusSearchDirectory *self)
+{
+ search_directory_add_pending_files_callbacks (self);
+ self->search_ready_and_valid = TRUE;
+}
+
+static void
+search_engine_hits_added (NautilusSearchEngine *engine,
+ GList *hits,
+ NautilusSearchDirectory *self)
+{
+ GList *hit_list;
+ GList *file_list;
+ NautilusFile *file;
+ SearchMonitor *monitor;
+ GList *monitor_list;
+
+ file_list = NULL;
+
+ for (hit_list = hits; hit_list != NULL; hit_list = hit_list->next)
+ {
+ NautilusSearchHit *hit = hit_list->data;
+ const char *uri;
+
+ uri = nautilus_search_hit_get_uri (hit);
+
+ nautilus_search_hit_compute_scores (hit, self->query);
+
+ file = nautilus_file_get_by_uri (uri);
+ nautilus_file_set_search_relevance (file, nautilus_search_hit_get_relevance (hit));
+ nautilus_file_set_search_fts_snippet (file, nautilus_search_hit_get_fts_snippet (hit));
+
+ for (monitor_list = self->monitor_list; monitor_list; monitor_list = monitor_list->next)
+ {
+ monitor = monitor_list->data;
+
+ /* Add monitors */
+ nautilus_file_monitor_add (file, monitor, monitor->monitor_attributes);
+ }
+
+ g_signal_connect (file, "changed", G_CALLBACK (file_changed), self),
+
+ file_list = g_list_prepend (file_list, file);
+ g_hash_table_add (self->files_hash, file);
+ }
+
+ self->files = g_list_concat (self->files, file_list);
+
+ nautilus_directory_emit_files_added (NAUTILUS_DIRECTORY (self), file_list);
+
+ file = nautilus_directory_get_corresponding_file (NAUTILUS_DIRECTORY (self));
+ nautilus_file_emit_changed (file);
+ nautilus_file_unref (file);
+
+ search_directory_add_pending_files_callbacks (self);
+}
+
+static void
+search_engine_error (NautilusSearchEngine *engine,
+ const char *error_message,
+ NautilusSearchDirectory *self)
+{
+ GError *error;
+
+ error = g_error_new_literal (G_IO_ERROR, G_IO_ERROR_FAILED,
+ error_message);
+ nautilus_directory_emit_load_error (NAUTILUS_DIRECTORY (self),
+ error);
+ g_error_free (error);
+}
+
+static void
+search_engine_finished (NautilusSearchEngine *engine,
+ NautilusSearchProviderStatus status,
+ NautilusSearchDirectory *self)
+{
+ /* If the search engine is going to restart means it finished an old search
+ * that was stopped or cancelled.
+ * Don't emit the done loading signal in this case, since this means the search
+ * directory tried to start a new search before all the search providers were finished
+ * in the search engine.
+ * If we emit the done-loading signal in this situation the client will think
+ * that it finished the current search, not an old one like it's actually
+ * happening. */
+ if (status == NAUTILUS_SEARCH_PROVIDER_STATUS_NORMAL)
+ {
+ on_search_directory_search_ready_and_valid (self);
+ nautilus_directory_emit_done_loading (NAUTILUS_DIRECTORY (self));
+ }
+ else if (status == NAUTILUS_SEARCH_PROVIDER_STATUS_RESTARTING)
+ {
+ /* Remove file monitors of the files from an old search that just
+ * actually finished */
+ reset_file_list (self);
+ }
+}
+
+static void
+search_force_reload (NautilusDirectory *directory)
+{
+ NautilusSearchDirectory *self;
+ NautilusFile *file;
+
+ self = NAUTILUS_SEARCH_DIRECTORY (directory);
+
+ if (!self->query)
+ {
+ return;
+ }
+
+ self->search_ready_and_valid = FALSE;
+
+ /* Remove file monitors */
+ reset_file_list (self);
+ stop_search (self);
+
+ file = nautilus_directory_get_corresponding_file (directory);
+ nautilus_file_invalidate_all_attributes (file);
+ nautilus_file_unref (file);
+}
+
+static gboolean
+search_are_all_files_seen (NautilusDirectory *directory)
+{
+ NautilusSearchDirectory *self;
+
+ self = NAUTILUS_SEARCH_DIRECTORY (directory);
+
+ return (!self->query ||
+ self->search_ready_and_valid);
+}
+
+static gboolean
+search_contains_file (NautilusDirectory *directory,
+ NautilusFile *file)
+{
+ NautilusSearchDirectory *self;
+
+ self = NAUTILUS_SEARCH_DIRECTORY (directory);
+ return (g_hash_table_lookup (self->files_hash, file) != NULL);
+}
+
+static GList *
+search_get_file_list (NautilusDirectory *directory)
+{
+ NautilusSearchDirectory *self;
+
+ self = NAUTILUS_SEARCH_DIRECTORY (directory);
+
+ return nautilus_file_list_copy (self->files);
+}
+
+
+static gboolean
+search_is_editable (NautilusDirectory *directory)
+{
+ return FALSE;
+}
+
+static gboolean
+real_handles_location (GFile *location)
+{
+ g_autofree gchar *uri = NULL;
+
+ uri = g_file_get_uri (location);
+
+ return eel_uri_is_search (uri);
+}
+
+static void
+search_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ NautilusSearchDirectory *self = NAUTILUS_SEARCH_DIRECTORY (object);
+
+ switch (property_id)
+ {
+ case PROP_BASE_MODEL:
+ {
+ nautilus_search_directory_set_base_model (self, g_value_get_object (value));
+ }
+ break;
+
+ case PROP_QUERY:
+ {
+ nautilus_search_directory_set_query (self, g_value_get_object (value));
+ }
+ break;
+
+ default:
+ {
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ }
+ break;
+ }
+}
+
+static void
+search_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ NautilusSearchDirectory *self = NAUTILUS_SEARCH_DIRECTORY (object);
+
+ switch (property_id)
+ {
+ case PROP_BASE_MODEL:
+ {
+ g_value_set_object (value, nautilus_search_directory_get_base_model (self));
+ }
+ break;
+
+ case PROP_QUERY:
+ {
+ g_value_take_object (value, nautilus_search_directory_get_query (self));
+ }
+ break;
+
+ default:
+ {
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ }
+ break;
+ }
+}
+
+static void
+clear_base_model (NautilusSearchDirectory *self)
+{
+ if (self->base_model != NULL)
+ {
+ nautilus_directory_file_monitor_remove (self->base_model,
+ &self->base_model);
+ g_clear_object (&self->base_model);
+ }
+}
+
+static void
+search_connect_engine (NautilusSearchDirectory *self)
+{
+ g_signal_connect (self->engine, "hits-added",
+ G_CALLBACK (search_engine_hits_added),
+ self);
+ g_signal_connect (self->engine, "error",
+ G_CALLBACK (search_engine_error),
+ self);
+ g_signal_connect (self->engine, "finished",
+ G_CALLBACK (search_engine_finished),
+ self);
+}
+
+static void
+search_disconnect_engine (NautilusSearchDirectory *self)
+{
+ g_signal_handlers_disconnect_by_func (self->engine,
+ search_engine_hits_added,
+ self);
+ g_signal_handlers_disconnect_by_func (self->engine,
+ search_engine_error,
+ self);
+ g_signal_handlers_disconnect_by_func (self->engine,
+ search_engine_finished,
+ self);
+}
+
+static void
+search_dispose (GObject *object)
+{
+ NautilusSearchDirectory *self;
+ GList *list;
+
+ self = NAUTILUS_SEARCH_DIRECTORY (object);
+
+ clear_base_model (self);
+
+ /* Remove search monitors */
+ if (self->monitor_list)
+ {
+ for (list = self->monitor_list; list != NULL; list = list->next)
+ {
+ search_monitor_destroy ((SearchMonitor *) list->data, self);
+ }
+
+ g_list_free (self->monitor_list);
+ self->monitor_list = NULL;
+ }
+
+ reset_file_list (self);
+
+ if (self->callback_list)
+ {
+ /* Remove callbacks */
+ g_list_foreach (self->callback_list,
+ (GFunc) search_callback_destroy, NULL);
+ g_list_free (self->callback_list);
+ self->callback_list = NULL;
+ }
+
+ if (self->pending_callback_list)
+ {
+ g_list_foreach (self->pending_callback_list,
+ (GFunc) search_callback_destroy, NULL);
+ g_list_free (self->pending_callback_list);
+ self->pending_callback_list = NULL;
+ }
+
+ g_clear_object (&self->query);
+ stop_search (self);
+ search_disconnect_engine (self);
+
+ g_clear_object (&self->engine);
+
+ G_OBJECT_CLASS (nautilus_search_directory_parent_class)->dispose (object);
+}
+
+static void
+search_finalize (GObject *object)
+{
+ NautilusSearchDirectory *self;
+
+ self = NAUTILUS_SEARCH_DIRECTORY (object);
+
+ g_hash_table_destroy (self->files_hash);
+
+ G_OBJECT_CLASS (nautilus_search_directory_parent_class)->finalize (object);
+}
+
+static void
+nautilus_search_directory_init (NautilusSearchDirectory *self)
+{
+ self->query = NULL;
+ self->files_hash = g_hash_table_new (g_direct_hash, g_direct_equal);
+
+ self->engine = nautilus_search_engine_new ();
+ search_connect_engine (self);
+}
+
+static void
+nautilus_search_directory_class_init (NautilusSearchDirectoryClass *class)
+{
+ NautilusDirectoryClass *directory_class = NAUTILUS_DIRECTORY_CLASS (class);
+ GObjectClass *oclass = G_OBJECT_CLASS (class);
+
+ oclass->dispose = search_dispose;
+ oclass->finalize = search_finalize;
+ oclass->get_property = search_get_property;
+ oclass->set_property = search_set_property;
+
+ directory_class->are_all_files_seen = search_are_all_files_seen;
+ directory_class->contains_file = search_contains_file;
+ directory_class->force_reload = search_force_reload;
+ directory_class->call_when_ready = search_call_when_ready;
+ directory_class->cancel_callback = search_cancel_callback;
+
+ directory_class->file_monitor_add = search_monitor_add;
+ directory_class->file_monitor_remove = search_monitor_remove;
+
+ directory_class->get_file_list = search_get_file_list;
+ directory_class->is_editable = search_is_editable;
+ directory_class->handles_location = real_handles_location;
+
+ properties[PROP_BASE_MODEL] =
+ g_param_spec_object ("base-model",
+ "The base model",
+ "The base directory model for this directory",
+ NAUTILUS_TYPE_DIRECTORY,
+ G_PARAM_READWRITE);
+ properties[PROP_QUERY] =
+ g_param_spec_object ("query",
+ "The query",
+ "The query for this search directory",
+ NAUTILUS_TYPE_QUERY,
+ G_PARAM_READWRITE);
+
+ g_object_class_install_properties (oclass, NUM_PROPERTIES, properties);
+}
+
+void
+nautilus_search_directory_set_base_model (NautilusSearchDirectory *self,
+ NautilusDirectory *base_model)
+{
+ if (self->base_model == base_model)
+ {
+ return;
+ }
+
+ if (self->query != NULL)
+ {
+ GFile *query_location, *model_location;
+ gboolean is_equal;
+
+ query_location = nautilus_query_get_location (self->query);
+ model_location = nautilus_directory_get_location (base_model);
+
+ is_equal = g_file_equal (model_location, query_location);
+
+ g_object_unref (model_location);
+ g_object_unref (query_location);
+
+ if (!is_equal)
+ {
+ return;
+ }
+ }
+
+ clear_base_model (self);
+ self->base_model = nautilus_directory_ref (base_model);
+
+ if (self->base_model != NULL)
+ {
+ nautilus_directory_file_monitor_add (base_model, &self->base_model,
+ TRUE, NAUTILUS_FILE_ATTRIBUTE_INFO,
+ NULL, NULL);
+ }
+
+ g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_BASE_MODEL]);
+}
+
+NautilusDirectory *
+nautilus_search_directory_get_base_model (NautilusSearchDirectory *self)
+{
+ return self->base_model;
+}
+
+char *
+nautilus_search_directory_generate_new_uri (void)
+{
+ static int counter = 0;
+ char *uri;
+
+ uri = g_strdup_printf (EEL_SEARCH_URI "//%d/", counter++);
+
+ return uri;
+}
+
+void
+nautilus_search_directory_set_query (NautilusSearchDirectory *self,
+ NautilusQuery *query)
+{
+ NautilusFile *file;
+ NautilusQuery *old_query;
+
+ old_query = self->query;
+
+ if (self->query != query)
+ {
+ self->query = g_object_ref (query);
+
+ g_clear_pointer (&self->binding, g_binding_unbind);
+
+ if (query)
+ {
+ self->binding = g_object_bind_property (self->engine, "running",
+ query, "searching",
+ G_BINDING_DEFAULT | G_BINDING_SYNC_CREATE);
+ }
+
+ g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_QUERY]);
+
+ g_clear_object (&old_query);
+ }
+
+ file = nautilus_directory_get_existing_corresponding_file (NAUTILUS_DIRECTORY (self));
+ if (file != NULL)
+ {
+ nautilus_search_directory_file_update_display_name (NAUTILUS_SEARCH_DIRECTORY_FILE (file));
+ }
+ nautilus_file_unref (file);
+}
+
+NautilusQuery *
+nautilus_search_directory_get_query (NautilusSearchDirectory *self)
+{
+ if (self->query != NULL)
+ {
+ return g_object_ref (self->query);
+ }
+
+ return NULL;
+}
diff --git a/src/nautilus-search-directory.h b/src/nautilus-search-directory.h
new file mode 100644
index 0000000..abd5b0b
--- /dev/null
+++ b/src/nautilus-search-directory.h
@@ -0,0 +1,43 @@
+/*
+ nautilus-search-directory.h: Subclass of NautilusDirectory to implement
+ a virtual directory consisting of the search directory and the search
+ icons
+
+ Copyright (C) 2005 Novell, 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/>.
+*/
+
+#pragma once
+
+#include "nautilus-directory.h"
+
+#include "nautilus-types.h"
+
+#define NAUTILUS_SEARCH_DIRECTORY_PROVIDER_NAME "search-directory-provider"
+#define NAUTILUS_TYPE_SEARCH_DIRECTORY (nautilus_search_directory_get_type ())
+
+G_DECLARE_FINAL_TYPE (NautilusSearchDirectory, nautilus_search_directory,
+ NAUTILUS, SEARCH_DIRECTORY, NautilusDirectory)
+
+char *nautilus_search_directory_generate_new_uri (void);
+
+NautilusQuery *nautilus_search_directory_get_query (NautilusSearchDirectory *self);
+void nautilus_search_directory_set_query (NautilusSearchDirectory *self,
+ NautilusQuery *query);
+
+NautilusDirectory *
+ nautilus_search_directory_get_base_model (NautilusSearchDirectory *self);
+void nautilus_search_directory_set_base_model (NautilusSearchDirectory *self,
+ NautilusDirectory *base_model);
diff --git a/src/nautilus-search-engine-model.c b/src/nautilus-search-engine-model.c
new file mode 100644
index 0000000..46f2a0b
--- /dev/null
+++ b/src/nautilus-search-engine-model.c
@@ -0,0 +1,391 @@
+/*
+ * Copyright (C) 2005 Red Hat, Inc
+ *
+ * Nautilus is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * Nautilus is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; see the file COPYING. If not,
+ * see <http://www.gnu.org/licenses/>.
+ *
+ * Author: Alexander Larsson <alexl@redhat.com>
+ *
+ */
+
+#include <config.h>
+#include "nautilus-search-hit.h"
+#include "nautilus-search-provider.h"
+#include "nautilus-search-engine-model.h"
+#include "nautilus-directory.h"
+#include "nautilus-directory-private.h"
+#include "nautilus-file.h"
+#include "nautilus-ui-utilities.h"
+#define DEBUG_FLAG NAUTILUS_DEBUG_SEARCH
+#include "nautilus-debug.h"
+
+#include <string.h>
+#include <glib.h>
+#include <gio/gio.h>
+
+struct _NautilusSearchEngineModel
+{
+ GObject parent;
+
+ NautilusQuery *query;
+
+ GList *hits;
+ NautilusDirectory *directory;
+
+ gboolean query_pending;
+ guint finished_id;
+};
+
+enum
+{
+ PROP_0,
+ PROP_RUNNING,
+ LAST_PROP
+};
+
+static void nautilus_search_provider_init (NautilusSearchProviderInterface *iface);
+
+G_DEFINE_TYPE_WITH_CODE (NautilusSearchEngineModel,
+ nautilus_search_engine_model,
+ G_TYPE_OBJECT,
+ G_IMPLEMENT_INTERFACE (NAUTILUS_TYPE_SEARCH_PROVIDER,
+ nautilus_search_provider_init))
+
+static void
+finalize (GObject *object)
+{
+ NautilusSearchEngineModel *model;
+
+ model = NAUTILUS_SEARCH_ENGINE_MODEL (object);
+
+ if (model->hits != NULL)
+ {
+ g_list_free_full (model->hits, g_object_unref);
+ model->hits = NULL;
+ }
+
+ if (model->finished_id != 0)
+ {
+ g_source_remove (model->finished_id);
+ model->finished_id = 0;
+ }
+
+ g_clear_object (&model->directory);
+ g_clear_object (&model->query);
+
+ G_OBJECT_CLASS (nautilus_search_engine_model_parent_class)->finalize (object);
+}
+
+static gboolean
+search_finished (NautilusSearchEngineModel *model)
+{
+ model->finished_id = 0;
+
+ if (model->hits != NULL)
+ {
+ DEBUG ("Model engine hits added");
+ nautilus_search_provider_hits_added (NAUTILUS_SEARCH_PROVIDER (model),
+ model->hits);
+ g_list_free_full (model->hits, g_object_unref);
+ model->hits = NULL;
+ }
+
+ model->query_pending = FALSE;
+
+ g_object_notify (G_OBJECT (model), "running");
+
+ DEBUG ("Model engine finished");
+ nautilus_search_provider_finished (NAUTILUS_SEARCH_PROVIDER (model),
+ NAUTILUS_SEARCH_PROVIDER_STATUS_NORMAL);
+ g_object_unref (model);
+
+ return FALSE;
+}
+
+static void
+search_finished_idle (NautilusSearchEngineModel *model)
+{
+ if (model->finished_id != 0)
+ {
+ return;
+ }
+
+ model->finished_id = g_idle_add ((GSourceFunc) search_finished, model);
+}
+
+static void
+model_directory_ready_cb (NautilusDirectory *directory,
+ GList *list,
+ gpointer user_data)
+{
+ NautilusSearchEngineModel *model = user_data;
+ g_autoptr (GPtrArray) mime_types = NULL;
+ gchar *uri, *display_name;
+ GList *files, *hits, *l;
+ NautilusFile *file;
+ gdouble match;
+ gboolean found;
+ NautilusSearchHit *hit;
+ GDateTime *initial_date;
+ GDateTime *end_date;
+ GPtrArray *date_range;
+
+ files = nautilus_directory_get_file_list (directory);
+ mime_types = nautilus_query_get_mime_types (model->query);
+ hits = NULL;
+
+ for (l = files; l != NULL; l = l->next)
+ {
+ g_autoptr (GDateTime) mtime = NULL;
+ g_autoptr (GDateTime) atime = NULL;
+ g_autoptr (GDateTime) ctime = NULL;
+
+ file = l->data;
+
+ display_name = nautilus_file_get_display_name (file);
+ match = nautilus_query_matches_string (model->query, display_name);
+ found = (match > -1);
+
+ if (found && mime_types->len > 0)
+ {
+ found = FALSE;
+
+ for (gint i = 0; i < mime_types->len; i++)
+ {
+ if (nautilus_file_is_mime_type (file, g_ptr_array_index (mime_types, i)))
+ {
+ found = TRUE;
+ break;
+ }
+ }
+ }
+
+ mtime = g_date_time_new_from_unix_local (nautilus_file_get_mtime (file));
+ atime = g_date_time_new_from_unix_local (nautilus_file_get_atime (file));
+ ctime = g_date_time_new_from_unix_local (nautilus_file_get_btime (file));
+
+ date_range = nautilus_query_get_date_range (model->query);
+ if (found && date_range != NULL)
+ {
+ NautilusQuerySearchType type;
+ GDateTime *target_date;
+
+ type = nautilus_query_get_search_type (model->query);
+ initial_date = g_ptr_array_index (date_range, 0);
+ end_date = g_ptr_array_index (date_range, 1);
+
+ switch (type)
+ {
+ case NAUTILUS_QUERY_SEARCH_TYPE_LAST_ACCESS:
+ {
+ target_date = atime;
+ }
+ break;
+
+ case NAUTILUS_QUERY_SEARCH_TYPE_LAST_MODIFIED:
+ {
+ target_date = mtime;
+ }
+ break;
+
+ case NAUTILUS_QUERY_SEARCH_TYPE_CREATED:
+ {
+ target_date = ctime;
+ }
+ break;
+
+ default:
+ {
+ target_date = NULL;
+ }
+ }
+
+ found = nautilus_date_time_is_between_dates (target_date,
+ initial_date,
+ end_date);
+ g_ptr_array_unref (date_range);
+ }
+
+ if (found)
+ {
+ uri = nautilus_file_get_uri (file);
+ hit = nautilus_search_hit_new (uri);
+ nautilus_search_hit_set_fts_rank (hit, match);
+ nautilus_search_hit_set_modification_time (hit, mtime);
+ nautilus_search_hit_set_access_time (hit, atime);
+ nautilus_search_hit_set_creation_time (hit, ctime);
+
+ hits = g_list_prepend (hits, hit);
+
+ g_free (uri);
+ }
+
+ g_free (display_name);
+ }
+
+ nautilus_file_list_free (files);
+ model->hits = hits;
+
+ search_finished (model);
+}
+
+static void
+nautilus_search_engine_model_start (NautilusSearchProvider *provider)
+{
+ NautilusSearchEngineModel *model;
+
+ model = NAUTILUS_SEARCH_ENGINE_MODEL (provider);
+
+ if (model->query_pending)
+ {
+ return;
+ }
+
+ DEBUG ("Model engine start");
+
+ g_object_ref (model);
+ model->query_pending = TRUE;
+
+ g_object_notify (G_OBJECT (provider), "running");
+
+ if (model->directory == NULL)
+ {
+ search_finished_idle (model);
+ return;
+ }
+
+ nautilus_directory_call_when_ready (model->directory,
+ NAUTILUS_FILE_ATTRIBUTE_INFO,
+ TRUE, model_directory_ready_cb, model);
+}
+
+static void
+nautilus_search_engine_model_stop (NautilusSearchProvider *provider)
+{
+ NautilusSearchEngineModel *model;
+
+ model = NAUTILUS_SEARCH_ENGINE_MODEL (provider);
+
+ if (model->query_pending)
+ {
+ DEBUG ("Model engine stop");
+
+ nautilus_directory_cancel_callback (model->directory,
+ model_directory_ready_cb, model);
+ search_finished_idle (model);
+ }
+
+ g_clear_object (&model->directory);
+}
+
+static void
+nautilus_search_engine_model_set_query (NautilusSearchProvider *provider,
+ NautilusQuery *query)
+{
+ NautilusSearchEngineModel *model;
+
+ model = NAUTILUS_SEARCH_ENGINE_MODEL (provider);
+
+ g_object_ref (query);
+ g_clear_object (&model->query);
+ model->query = query;
+}
+
+static gboolean
+nautilus_search_engine_model_is_running (NautilusSearchProvider *provider)
+{
+ NautilusSearchEngineModel *model;
+
+ model = NAUTILUS_SEARCH_ENGINE_MODEL (provider);
+
+ return model->query_pending;
+}
+
+static void
+nautilus_search_provider_init (NautilusSearchProviderInterface *iface)
+{
+ iface->set_query = nautilus_search_engine_model_set_query;
+ iface->start = nautilus_search_engine_model_start;
+ iface->stop = nautilus_search_engine_model_stop;
+ iface->is_running = nautilus_search_engine_model_is_running;
+}
+
+static void
+nautilus_search_engine_model_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ NautilusSearchProvider *self = NAUTILUS_SEARCH_PROVIDER (object);
+
+ switch (prop_id)
+ {
+ case PROP_RUNNING:
+ {
+ g_value_set_boolean (value, nautilus_search_engine_model_is_running (self));
+ }
+ break;
+
+ default:
+ {
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+ }
+}
+
+static void
+nautilus_search_engine_model_class_init (NautilusSearchEngineModelClass *class)
+{
+ GObjectClass *gobject_class;
+
+ gobject_class = G_OBJECT_CLASS (class);
+ gobject_class->finalize = finalize;
+ gobject_class->get_property = nautilus_search_engine_model_get_property;
+
+ /**
+ * NautilusSearchEngine::running:
+ *
+ * Whether the search engine is running a search.
+ */
+ g_object_class_override_property (gobject_class, PROP_RUNNING, "running");
+}
+
+static void
+nautilus_search_engine_model_init (NautilusSearchEngineModel *engine)
+{
+}
+
+NautilusSearchEngineModel *
+nautilus_search_engine_model_new (void)
+{
+ NautilusSearchEngineModel *engine;
+
+ engine = g_object_new (NAUTILUS_TYPE_SEARCH_ENGINE_MODEL, NULL);
+
+ return engine;
+}
+
+void
+nautilus_search_engine_model_set_model (NautilusSearchEngineModel *model,
+ NautilusDirectory *directory)
+{
+ g_clear_object (&model->directory);
+ model->directory = nautilus_directory_ref (directory);
+}
+
+NautilusDirectory *
+nautilus_search_engine_model_get_model (NautilusSearchEngineModel *model)
+{
+ return model->directory;
+}
diff --git a/src/nautilus-search-engine-model.h b/src/nautilus-search-engine-model.h
new file mode 100644
index 0000000..4babc54
--- /dev/null
+++ b/src/nautilus-search-engine-model.h
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2005 Red Hat, Inc
+ *
+ * Nautilus is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * Nautilus is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; see the file COPYING. If not,
+ * see <http://www.gnu.org/licenses/>.
+ *
+ * Author: Alexander Larsson <alexl@redhat.com>
+ *
+ */
+
+#pragma once
+
+#include "nautilus-directory.h"
+
+#define NAUTILUS_TYPE_SEARCH_ENGINE_MODEL (nautilus_search_engine_model_get_type ())
+G_DECLARE_FINAL_TYPE (NautilusSearchEngineModel, nautilus_search_engine_model, NAUTILUS, SEARCH_ENGINE_MODEL, GObject)
+
+NautilusSearchEngineModel* nautilus_search_engine_model_new (void);
+void nautilus_search_engine_model_set_model (NautilusSearchEngineModel *model,
+ NautilusDirectory *directory);
+NautilusDirectory * nautilus_search_engine_model_get_model (NautilusSearchEngineModel *model); \ No newline at end of file
diff --git a/src/nautilus-search-engine-private.h b/src/nautilus-search-engine-private.h
new file mode 100644
index 0000000..e5f989f
--- /dev/null
+++ b/src/nautilus-search-engine-private.h
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2018 Canonical Ltd.
+ *
+ * Nautilus is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * Nautilus is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; see the file COPYING. If not,
+ * see <http://www.gnu.org/licenses/>.
+ *
+ * Author: Marco Trevisan <marco@ubuntu.com>
+ *
+ */
+
+#pragma once
+
+#include "nautilus-query.h"
+
+typedef enum {
+ NAUTILUS_SEARCH_ENGINE_TYPE_NON_INDEXED,
+ NAUTILUS_SEARCH_ENGINE_TYPE_INDEXED,
+} NautilusSearchEngineType;
+
+gboolean is_recursive_search (NautilusSearchEngineType engine_type, NautilusQueryRecursive recursive, GFile *location);
diff --git a/src/nautilus-search-engine-recent.c b/src/nautilus-search-engine-recent.c
new file mode 100644
index 0000000..a31b9d8
--- /dev/null
+++ b/src/nautilus-search-engine-recent.c
@@ -0,0 +1,465 @@
+/*
+ * Copyright (C) 2018 Canonical Ltd
+ *
+ * Nautilus is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * Nautilus is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; see the file COPYING. If not,
+ * see <http://www.gnu.org/licenses/>.
+ *
+ * Author: Marco Trevisan <marco.trevisan@canonical.com>
+ */
+
+#include <config.h>
+#include "nautilus-search-hit.h"
+#include "nautilus-search-provider.h"
+#include "nautilus-search-engine-recent.h"
+#include "nautilus-search-engine-private.h"
+#include "nautilus-ui-utilities.h"
+#define DEBUG_FLAG NAUTILUS_DEBUG_SEARCH
+#include "nautilus-debug.h"
+
+#include <string.h>
+#include <glib.h>
+#include <gio/gio.h>
+
+#define FILE_ATTRIBS G_FILE_ATTRIBUTE_STANDARD_IS_HIDDEN "," \
+ G_FILE_ATTRIBUTE_STANDARD_IS_BACKUP "," \
+ G_FILE_ATTRIBUTE_ACCESS_CAN_READ "," \
+ G_FILE_ATTRIBUTE_TIME_MODIFIED "," \
+ G_FILE_ATTRIBUTE_TIME_ACCESS "," \
+ G_FILE_ATTRIBUTE_TIME_CREATED
+
+struct _NautilusSearchEngineRecent
+{
+ GObject parent_instance;
+
+ NautilusQuery *query;
+ gboolean running;
+ GCancellable *cancellable;
+ GtkRecentManager *recent_manager;
+ guint add_hits_idle_id;
+};
+
+static void nautilus_search_provider_init (NautilusSearchProviderInterface *iface);
+
+G_DEFINE_TYPE_WITH_CODE (NautilusSearchEngineRecent,
+ nautilus_search_engine_recent,
+ G_TYPE_OBJECT,
+ G_IMPLEMENT_INTERFACE (NAUTILUS_TYPE_SEARCH_PROVIDER,
+ nautilus_search_provider_init))
+
+enum
+{
+ PROP_0,
+ PROP_RUNNING,
+ LAST_PROP
+};
+
+
+NautilusSearchEngineRecent *
+nautilus_search_engine_recent_new (void)
+{
+ return g_object_new (NAUTILUS_TYPE_SEARCH_ENGINE_RECENT, NULL);
+}
+
+static void
+nautilus_search_engine_recent_finalize (GObject *object)
+{
+ NautilusSearchEngineRecent *self = NAUTILUS_SEARCH_ENGINE_RECENT (object);
+
+ g_clear_handle_id (&self->add_hits_idle_id, g_source_remove);
+ g_cancellable_cancel (self->cancellable);
+
+ g_clear_object (&self->query);
+ g_clear_object (&self->cancellable);
+
+ G_OBJECT_CLASS (nautilus_search_engine_recent_parent_class)->finalize (object);
+}
+
+typedef struct
+{
+ NautilusSearchEngineRecent *recent;
+ GList *hits;
+} SearchHitsData;
+
+
+static gboolean
+search_thread_add_hits_idle (gpointer user_data)
+{
+ SearchHitsData *search_hits = user_data;
+ g_autoptr (NautilusSearchEngineRecent) self = search_hits->recent;
+ NautilusSearchProvider *provider = NAUTILUS_SEARCH_PROVIDER (self);
+
+ self->add_hits_idle_id = 0;
+
+ if (!g_cancellable_is_cancelled (self->cancellable))
+ {
+ nautilus_search_provider_hits_added (provider, search_hits->hits);
+ DEBUG ("Recent engine add hits");
+ }
+
+ self->running = FALSE;
+ g_list_free_full (search_hits->hits, g_object_unref);
+ g_clear_object (&self->cancellable);
+ g_free (search_hits);
+
+ nautilus_search_provider_finished (provider,
+ NAUTILUS_SEARCH_PROVIDER_STATUS_NORMAL);
+ g_object_notify (G_OBJECT (provider), "running");
+
+ return FALSE;
+}
+
+static void
+search_add_hits_idle (NautilusSearchEngineRecent *self,
+ GList *hits)
+{
+ SearchHitsData *search_hits;
+
+ if (self->add_hits_idle_id != 0)
+ {
+ g_list_free_full (hits, g_object_unref);
+ return;
+ }
+
+ search_hits = g_new0 (SearchHitsData, 1);
+ search_hits->recent = g_object_ref (self);
+ search_hits->hits = hits;
+
+ self->add_hits_idle_id = g_idle_add (search_thread_add_hits_idle, search_hits);
+}
+
+static gboolean
+is_file_valid_recursive (NautilusSearchEngineRecent *self,
+ GFile *file,
+ GDateTime **mtime,
+ GDateTime **atime,
+ GDateTime **ctime,
+ GError **error)
+{
+ g_autoptr (GFileInfo) file_info = NULL;
+
+ file_info = g_file_query_info (file, FILE_ATTRIBS,
+ G_FILE_QUERY_INFO_NONE,
+ self->cancellable, error);
+ if (*error != NULL)
+ {
+ return FALSE;
+ }
+
+ if (!g_file_info_get_attribute_boolean (file_info,
+ G_FILE_ATTRIBUTE_ACCESS_CAN_READ))
+ {
+ return FALSE;
+ }
+
+ if (mtime && atime && ctime)
+ {
+ *mtime = g_file_info_get_modification_date_time (file_info);
+ *atime = g_file_info_get_access_date_time (file_info);
+ *ctime = g_file_info_get_creation_date_time (file_info);
+ }
+
+ if (!nautilus_query_get_show_hidden_files (self->query))
+ {
+ if (!g_file_info_get_is_hidden (file_info) &&
+ !g_file_info_get_is_backup (file_info))
+ {
+ g_autoptr (GFile) parent = g_file_get_parent (file);
+
+ if (parent)
+ {
+ return is_file_valid_recursive (self, parent,
+ NULL, NULL, NULL,
+ error);
+ }
+ }
+ else
+ {
+ return FALSE;
+ }
+ }
+
+ return TRUE;
+}
+
+static gpointer
+recent_thread_func (gpointer user_data)
+{
+ g_autoptr (NautilusSearchEngineRecent) self = NAUTILUS_SEARCH_ENGINE_RECENT (user_data);
+ g_autoptr (GPtrArray) date_range = NULL;
+ g_autoptr (GFile) query_location = NULL;
+ g_autoptr (GPtrArray) mime_types = NULL;
+ GList *recent_items;
+ GList *hits;
+ GList *l;
+
+ g_return_val_if_fail (self->query, NULL);
+
+ hits = NULL;
+ recent_items = gtk_recent_manager_get_items (self->recent_manager);
+ mime_types = nautilus_query_get_mime_types (self->query);
+ date_range = nautilus_query_get_date_range (self->query);
+ query_location = nautilus_query_get_location (self->query);
+
+ for (l = recent_items; l != NULL; l = l->next)
+ {
+ GtkRecentInfo *info = l->data;
+ g_autoptr (GFile) file = NULL;
+ const gchar *uri;
+ const gchar *name;
+ gdouble rank;
+
+ uri = gtk_recent_info_get_uri (info);
+ file = g_file_new_for_uri (uri);
+
+ if (!g_file_has_prefix (file, query_location))
+ {
+ continue;
+ }
+
+ if (g_cancellable_is_cancelled (self->cancellable))
+ {
+ break;
+ }
+
+ name = gtk_recent_info_get_display_name (info);
+ rank = nautilus_query_matches_string (self->query, name);
+
+ if (rank <= 0)
+ {
+ g_autofree char *short_name = gtk_recent_info_get_short_name (info);
+ rank = nautilus_query_matches_string (self->query, short_name);
+ }
+
+ if (rank > 0)
+ {
+ NautilusSearchHit *hit;
+ g_autoptr (GDateTime) mtime = NULL;
+ g_autoptr (GDateTime) atime = NULL;
+ g_autoptr (GDateTime) ctime = NULL;
+ g_autoptr (GError) error = NULL;
+
+ if (!gtk_recent_info_is_local (info))
+ {
+ continue;
+ }
+
+ if (!is_file_valid_recursive (self, file, &mtime, &atime, &ctime, &error))
+ {
+ if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+ {
+ break;
+ }
+
+ if (error != NULL &&
+ !g_error_matches (error, G_IO_ERROR, G_IO_ERROR_EXISTS))
+ {
+ g_debug ("Impossible to read recent file info: %s",
+ error->message);
+ }
+
+ continue;
+ }
+
+ if (mime_types->len > 0)
+ {
+ const gchar *mime_type = gtk_recent_info_get_mime_type (info);
+ gboolean found = FALSE;
+
+ for (gint i = 0; mime_type != NULL && i < mime_types->len; i++)
+ {
+ if (g_content_type_is_a (mime_type, g_ptr_array_index (mime_types, i)))
+ {
+ found = TRUE;
+ break;
+ }
+ }
+
+ if (!found)
+ {
+ continue;
+ }
+ }
+
+ if (date_range != NULL)
+ {
+ NautilusQuerySearchType type;
+ GDateTime *target_date;
+ GDateTime *initial_date;
+ GDateTime *end_date;
+
+ initial_date = g_ptr_array_index (date_range, 0);
+ end_date = g_ptr_array_index (date_range, 1);
+ type = nautilus_query_get_search_type (self->query);
+
+ switch (type)
+ {
+ case NAUTILUS_QUERY_SEARCH_TYPE_LAST_ACCESS:
+ {
+ target_date = atime;
+ }
+ break;
+
+ case NAUTILUS_QUERY_SEARCH_TYPE_LAST_MODIFIED:
+ {
+ target_date = mtime;
+ }
+ break;
+
+ case NAUTILUS_QUERY_SEARCH_TYPE_CREATED:
+ {
+ target_date = ctime;
+ }
+ break;
+
+ default:
+ {
+ target_date = NULL;
+ }
+ }
+
+ if (!nautilus_date_time_is_between_dates (target_date,
+ initial_date,
+ end_date))
+ {
+ continue;
+ }
+ }
+
+ hit = nautilus_search_hit_new (uri);
+ nautilus_search_hit_set_fts_rank (hit, rank);
+ nautilus_search_hit_set_modification_time (hit, mtime);
+ nautilus_search_hit_set_access_time (hit, atime);
+ nautilus_search_hit_set_creation_time (hit, ctime);
+
+ hits = g_list_prepend (hits, hit);
+ }
+ }
+
+ search_add_hits_idle (self, hits);
+
+ g_list_free_full (recent_items, (GDestroyNotify) gtk_recent_info_unref);
+
+ return NULL;
+}
+
+static void
+nautilus_search_engine_recent_start (NautilusSearchProvider *provider)
+{
+ NautilusSearchEngineRecent *self = NAUTILUS_SEARCH_ENGINE_RECENT (provider);
+ g_autoptr (GFile) location = NULL;
+ g_autoptr (GThread) thread = NULL;
+
+ g_return_if_fail (self->query);
+ g_return_if_fail (self->cancellable == NULL);
+
+ location = nautilus_query_get_location (self->query);
+
+ if (!is_recursive_search (NAUTILUS_SEARCH_ENGINE_TYPE_INDEXED,
+ nautilus_query_get_recursive (self->query),
+ location))
+ {
+ search_add_hits_idle (self, NULL);
+ return;
+ }
+
+ self->running = TRUE;
+ self->cancellable = g_cancellable_new ();
+ thread = g_thread_new ("nautilus-search-recent", recent_thread_func,
+ g_object_ref (self));
+
+ g_object_notify (G_OBJECT (provider), "running");
+}
+
+static void
+nautilus_search_engine_recent_stop (NautilusSearchProvider *provider)
+{
+ NautilusSearchEngineRecent *self = NAUTILUS_SEARCH_ENGINE_RECENT (provider);
+
+ if (self->cancellable != NULL)
+ {
+ DEBUG ("Recent engine stop");
+ g_cancellable_cancel (self->cancellable);
+ }
+
+ self->running = FALSE;
+}
+
+static void
+nautilus_search_engine_recent_set_query (NautilusSearchProvider *provider,
+ NautilusQuery *query)
+{
+ NautilusSearchEngineRecent *self = NAUTILUS_SEARCH_ENGINE_RECENT (provider);
+
+ g_clear_object (&self->query);
+ self->query = g_object_ref (query);
+}
+
+static gboolean
+nautilus_search_engine_recent_is_running (NautilusSearchProvider *provider)
+{
+ NautilusSearchEngineRecent *self = NAUTILUS_SEARCH_ENGINE_RECENT (provider);
+
+ return self->running;
+}
+
+static void
+nautilus_search_engine_recent_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ NautilusSearchProvider *provider = NAUTILUS_SEARCH_PROVIDER (object);
+
+ switch (prop_id)
+ {
+ case PROP_RUNNING:
+ {
+ gboolean running;
+ running = nautilus_search_engine_recent_is_running (provider);
+ g_value_set_boolean (value, running);
+ }
+ break;
+
+ default:
+ {
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+ }
+}
+
+static void
+nautilus_search_provider_init (NautilusSearchProviderInterface *iface)
+{
+ iface->set_query = nautilus_search_engine_recent_set_query;
+ iface->start = nautilus_search_engine_recent_start;
+ iface->stop = nautilus_search_engine_recent_stop;
+ iface->is_running = nautilus_search_engine_recent_is_running;
+}
+
+static void
+nautilus_search_engine_recent_class_init (NautilusSearchEngineRecentClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->finalize = nautilus_search_engine_recent_finalize;
+ object_class->get_property = nautilus_search_engine_recent_get_property;
+
+ g_object_class_override_property (object_class, PROP_RUNNING, "running");
+}
+
+static void
+nautilus_search_engine_recent_init (NautilusSearchEngineRecent *self)
+{
+ self->recent_manager = gtk_recent_manager_get_default ();
+}
diff --git a/src/nautilus-search-engine-recent.h b/src/nautilus-search-engine-recent.h
new file mode 100644
index 0000000..a690de4
--- /dev/null
+++ b/src/nautilus-search-engine-recent.h
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2018 Canonical Ltd
+ *
+ * Nautilus is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * Nautilus is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; see the file COPYING. If not,
+ * see <http://www.gnu.org/licenses/>.
+ *
+ * Author: Marco Trevisan <marco.trevisan@canonical.com>
+ */
+
+#pragma once
+
+#include <glib-object.h>
+
+G_BEGIN_DECLS
+
+#define NAUTILUS_TYPE_SEARCH_ENGINE_RECENT (nautilus_search_engine_recent_get_type ())
+
+G_DECLARE_FINAL_TYPE (NautilusSearchEngineRecent, nautilus_search_engine_recent, NAUTILUS, SEARCH_ENGINE_RECENT, GObject);
+
+NautilusSearchEngineRecent *nautilus_search_engine_recent_new (void);
+
+G_END_DECLS
diff --git a/src/nautilus-search-engine-simple.c b/src/nautilus-search-engine-simple.c
new file mode 100644
index 0000000..4fa1a07
--- /dev/null
+++ b/src/nautilus-search-engine-simple.c
@@ -0,0 +1,614 @@
+/*
+ * Copyright (C) 2005 Red Hat, Inc
+ *
+ * Nautilus is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * Nautilus is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; see the file COPYING. If not,
+ * see <http://www.gnu.org/licenses/>.
+ *
+ * Author: Alexander Larsson <alexl@redhat.com>
+ *
+ */
+
+#include <config.h>
+#include "nautilus-search-engine-simple.h"
+
+#include "nautilus-search-engine-private.h"
+#include "nautilus-search-hit.h"
+#include "nautilus-search-provider.h"
+#include "nautilus-ui-utilities.h"
+#define DEBUG_FLAG NAUTILUS_DEBUG_SEARCH
+#include "nautilus-debug.h"
+
+#include <string.h>
+#include <glib.h>
+#include <gio/gio.h>
+
+#define BATCH_SIZE 500
+
+enum
+{
+ PROP_0,
+ PROP_RUNNING,
+ NUM_PROPERTIES
+};
+
+typedef struct
+{
+ NautilusSearchEngineSimple *engine;
+ GCancellable *cancellable;
+
+ GPtrArray *mime_types;
+ GList *found_list;
+
+ GQueue *directories; /* GFiles */
+
+ GHashTable *visited;
+
+ gint n_processed_files;
+ GList *hits;
+
+ NautilusQuery *query;
+
+ gint processing_id;
+ GMutex idle_mutex;
+ /* The following data can be accessed from different threads
+ * and needs to lock the mutex
+ */
+ GQueue *idle_queue;
+ gboolean finished;
+} SearchThreadData;
+
+
+struct _NautilusSearchEngineSimple
+{
+ GObject parent_instance;
+ NautilusQuery *query;
+
+ SearchThreadData *active_search;
+};
+
+static void nautilus_search_provider_init (NautilusSearchProviderInterface *iface);
+
+G_DEFINE_TYPE_WITH_CODE (NautilusSearchEngineSimple,
+ nautilus_search_engine_simple,
+ G_TYPE_OBJECT,
+ G_IMPLEMENT_INTERFACE (NAUTILUS_TYPE_SEARCH_PROVIDER,
+ nautilus_search_provider_init))
+
+static void
+finalize (GObject *object)
+{
+ NautilusSearchEngineSimple *simple = NAUTILUS_SEARCH_ENGINE_SIMPLE (object);
+ g_clear_object (&simple->query);
+
+ G_OBJECT_CLASS (nautilus_search_engine_simple_parent_class)->finalize (object);
+}
+
+static SearchThreadData *
+search_thread_data_new (NautilusSearchEngineSimple *engine,
+ NautilusQuery *query)
+{
+ SearchThreadData *data;
+ GFile *location;
+
+ data = g_new0 (SearchThreadData, 1);
+
+ data->engine = g_object_ref (engine);
+ data->directories = g_queue_new ();
+ data->visited = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
+ data->query = g_object_ref (query);
+
+ location = nautilus_query_get_location (query);
+
+ g_queue_push_tail (data->directories, location);
+ data->mime_types = nautilus_query_get_mime_types (query);
+
+ data->cancellable = g_cancellable_new ();
+
+ g_mutex_init (&data->idle_mutex);
+ data->idle_queue = g_queue_new ();
+
+ return data;
+}
+
+static void
+search_thread_data_free (SearchThreadData *data)
+{
+ GList *hits;
+
+ g_queue_foreach (data->directories,
+ (GFunc) g_object_unref, NULL);
+ g_queue_free (data->directories);
+ g_hash_table_destroy (data->visited);
+ g_object_unref (data->cancellable);
+ g_object_unref (data->query);
+ g_clear_pointer (&data->mime_types, g_ptr_array_unref);
+ g_list_free_full (data->hits, g_object_unref);
+ g_object_unref (data->engine);
+ g_mutex_clear (&data->idle_mutex);
+
+ while ((hits = g_queue_pop_head (data->idle_queue)))
+ {
+ g_list_free_full (hits, g_object_unref);
+ }
+ g_queue_free (data->idle_queue);
+
+ g_free (data);
+}
+
+static gboolean
+search_thread_done (SearchThreadData *data)
+{
+ NautilusSearchEngineSimple *engine = data->engine;
+
+ if (g_cancellable_is_cancelled (data->cancellable))
+ {
+ DEBUG ("Simple engine finished and cancelled");
+ }
+ else
+ {
+ DEBUG ("Simple engine finished");
+ }
+ engine->active_search = NULL;
+ nautilus_search_provider_finished (NAUTILUS_SEARCH_PROVIDER (engine),
+ NAUTILUS_SEARCH_PROVIDER_STATUS_NORMAL);
+
+ g_object_notify (G_OBJECT (engine), "running");
+
+ search_thread_data_free (data);
+
+ return G_SOURCE_REMOVE;
+}
+
+static void
+search_thread_process_hits_idle (SearchThreadData *data,
+ GList *hits)
+{
+ if (!g_cancellable_is_cancelled (data->cancellable))
+ {
+ DEBUG ("Simple engine add hits");
+ nautilus_search_provider_hits_added (NAUTILUS_SEARCH_PROVIDER (data->engine),
+ hits);
+ }
+}
+
+static gboolean
+search_thread_process_idle (gpointer user_data)
+{
+ SearchThreadData *thread_data;
+ GList *hits;
+
+ thread_data = user_data;
+
+ g_mutex_lock (&thread_data->idle_mutex);
+ hits = g_queue_pop_head (thread_data->idle_queue);
+ /* Even if the cancellable is cancelled, we need to make sure the search
+ * thread has aknowledge it, and therefore not using the thread data after
+ * freeing it. The search thread will mark as finished whenever the search
+ * is finished or cancelled.
+ * Nonetheless, we should stop yielding results if the search was cancelled
+ */
+ if (thread_data->finished)
+ {
+ if (hits == NULL || g_cancellable_is_cancelled (thread_data->cancellable))
+ {
+ g_mutex_unlock (&thread_data->idle_mutex);
+
+ if (hits)
+ {
+ g_list_free_full (hits, g_object_unref);
+ }
+ search_thread_done (thread_data);
+
+ return G_SOURCE_REMOVE;
+ }
+ }
+
+ g_mutex_unlock (&thread_data->idle_mutex);
+
+ if (hits)
+ {
+ search_thread_process_hits_idle (thread_data, hits);
+ g_list_free_full (hits, g_object_unref);
+ }
+
+ return G_SOURCE_CONTINUE;
+}
+
+static void
+finish_search_thread (SearchThreadData *thread_data)
+{
+ g_mutex_lock (&thread_data->idle_mutex);
+ thread_data->finished = TRUE;
+ g_mutex_unlock (&thread_data->idle_mutex);
+
+ /* If no results were processed, direclty finish the search, in the main
+ * thread.
+ */
+ if (thread_data->processing_id == 0)
+ {
+ g_idle_add (G_SOURCE_FUNC (search_thread_done), thread_data);
+ }
+}
+
+static void
+process_batch_in_idle (SearchThreadData *thread_data,
+ GList *hits)
+{
+ g_return_if_fail (hits != NULL);
+
+ g_mutex_lock (&thread_data->idle_mutex);
+ g_queue_push_tail (thread_data->idle_queue, hits);
+ g_mutex_unlock (&thread_data->idle_mutex);
+
+ if (thread_data->processing_id == 0)
+ {
+ thread_data->processing_id = g_idle_add (search_thread_process_idle, thread_data);
+ }
+}
+
+static void
+send_batch_in_idle (SearchThreadData *thread_data)
+{
+ thread_data->n_processed_files = 0;
+
+ if (thread_data->hits)
+ {
+ process_batch_in_idle (thread_data, thread_data->hits);
+ }
+ thread_data->hits = NULL;
+}
+
+#define STD_ATTRIBUTES \
+ G_FILE_ATTRIBUTE_STANDARD_NAME "," \
+ G_FILE_ATTRIBUTE_STANDARD_DISPLAY_NAME "," \
+ G_FILE_ATTRIBUTE_STANDARD_IS_BACKUP "," \
+ G_FILE_ATTRIBUTE_STANDARD_IS_HIDDEN "," \
+ G_FILE_ATTRIBUTE_STANDARD_TYPE "," \
+ G_FILE_ATTRIBUTE_TIME_MODIFIED "," \
+ G_FILE_ATTRIBUTE_TIME_ACCESS "," \
+ G_FILE_ATTRIBUTE_TIME_CREATED "," \
+ G_FILE_ATTRIBUTE_ID_FILE
+
+static void
+visit_directory (GFile *dir,
+ SearchThreadData *data)
+{
+ g_autoptr (GPtrArray) date_range = NULL;
+ NautilusQuerySearchType type;
+ NautilusQueryRecursive recursive;
+ GFileEnumerator *enumerator;
+ GFileInfo *info;
+ GFile *child;
+ const char *mime_type, *display_name;
+ gdouble match;
+ gboolean is_hidden, found;
+ const char *id;
+ gboolean visited;
+ GDateTime *initial_date;
+ GDateTime *end_date;
+ gchar *uri;
+
+ enumerator = g_file_enumerate_children (dir,
+ data->mime_types->len > 0 ?
+ STD_ATTRIBUTES ","
+ G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE
+ :
+ STD_ATTRIBUTES
+ ,
+ G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
+ data->cancellable, NULL);
+
+ if (enumerator == NULL)
+ {
+ return;
+ }
+
+ type = nautilus_query_get_search_type (data->query);
+ recursive = nautilus_query_get_recursive (data->query);
+ date_range = nautilus_query_get_date_range (data->query);
+
+ while ((info = g_file_enumerator_next_file (enumerator, data->cancellable, NULL)) != NULL)
+ {
+ g_autoptr (GDateTime) mtime = NULL;
+ g_autoptr (GDateTime) atime = NULL;
+ g_autoptr (GDateTime) ctime = NULL;
+
+ display_name = g_file_info_get_display_name (info);
+ if (display_name == NULL)
+ {
+ goto next;
+ }
+
+ is_hidden = g_file_info_get_is_hidden (info) || g_file_info_get_is_backup (info);
+ if (is_hidden && !nautilus_query_get_show_hidden_files (data->query))
+ {
+ goto next;
+ }
+
+ child = g_file_get_child (dir, g_file_info_get_name (info));
+ match = nautilus_query_matches_string (data->query, display_name);
+ found = (match > -1);
+
+ if (found && data->mime_types->len > 0)
+ {
+ mime_type = g_file_info_get_content_type (info);
+ found = FALSE;
+
+ for (gint i = 0; i < data->mime_types->len; i++)
+ {
+ if (g_content_type_is_a (mime_type, g_ptr_array_index (data->mime_types, i)))
+ {
+ found = TRUE;
+ break;
+ }
+ }
+ }
+
+ mtime = g_file_info_get_modification_date_time (info);
+ atime = g_file_info_get_access_date_time (info);
+ ctime = g_file_info_get_creation_date_time (info);
+
+ if (found && date_range != NULL)
+ {
+ GDateTime *target_date;
+
+ initial_date = g_ptr_array_index (date_range, 0);
+ end_date = g_ptr_array_index (date_range, 1);
+
+ switch (type)
+ {
+ case NAUTILUS_QUERY_SEARCH_TYPE_LAST_ACCESS:
+ {
+ target_date = atime;
+ }
+ break;
+
+ case NAUTILUS_QUERY_SEARCH_TYPE_LAST_MODIFIED:
+ {
+ target_date = mtime;
+ }
+ break;
+
+ case NAUTILUS_QUERY_SEARCH_TYPE_CREATED:
+ {
+ target_date = ctime;
+ }
+ break;
+
+ default:
+ {
+ target_date = NULL;
+ }
+ }
+
+ found = nautilus_date_time_is_between_dates (target_date,
+ initial_date,
+ end_date);
+ }
+
+ if (found)
+ {
+ NautilusSearchHit *hit;
+
+ uri = g_file_get_uri (child);
+ hit = nautilus_search_hit_new (uri);
+ g_free (uri);
+ nautilus_search_hit_set_fts_rank (hit, match);
+ nautilus_search_hit_set_modification_time (hit, mtime);
+ nautilus_search_hit_set_access_time (hit, atime);
+ nautilus_search_hit_set_creation_time (hit, ctime);
+
+ data->hits = g_list_prepend (data->hits, hit);
+ }
+
+ data->n_processed_files++;
+ if (data->n_processed_files > BATCH_SIZE)
+ {
+ send_batch_in_idle (data);
+ }
+
+ if (recursive != NAUTILUS_QUERY_RECURSIVE_NEVER &&
+ g_file_info_get_file_type (info) == G_FILE_TYPE_DIRECTORY &&
+ is_recursive_search (NAUTILUS_SEARCH_ENGINE_TYPE_NON_INDEXED,
+ recursive, child))
+ {
+ id = g_file_info_get_attribute_string (info, G_FILE_ATTRIBUTE_ID_FILE);
+ visited = FALSE;
+ if (id)
+ {
+ if (g_hash_table_lookup_extended (data->visited,
+ id, NULL, NULL))
+ {
+ visited = TRUE;
+ }
+ else
+ {
+ g_hash_table_insert (data->visited, g_strdup (id), NULL);
+ }
+ }
+
+ if (!visited)
+ {
+ g_queue_push_tail (data->directories, g_object_ref (child));
+ }
+ }
+
+ g_object_unref (child);
+next:
+ g_object_unref (info);
+ }
+
+ g_object_unref (enumerator);
+}
+
+
+static gpointer
+search_thread_func (gpointer user_data)
+{
+ SearchThreadData *data;
+ GFile *dir;
+ GFileInfo *info;
+ const char *id;
+
+ data = user_data;
+
+ /* Insert id for toplevel directory into visited */
+ dir = g_queue_peek_head (data->directories);
+ info = g_file_query_info (dir, G_FILE_ATTRIBUTE_ID_FILE, 0, data->cancellable, NULL);
+ if (info)
+ {
+ id = g_file_info_get_attribute_string (info, G_FILE_ATTRIBUTE_ID_FILE);
+ if (id)
+ {
+ g_hash_table_insert (data->visited, g_strdup (id), NULL);
+ }
+ g_object_unref (info);
+ }
+
+ while (!g_cancellable_is_cancelled (data->cancellable) &&
+ (dir = g_queue_pop_head (data->directories)) != NULL)
+ {
+ visit_directory (dir, data);
+ g_object_unref (dir);
+ }
+
+ if (!g_cancellable_is_cancelled (data->cancellable))
+ {
+ send_batch_in_idle (data);
+ }
+
+ finish_search_thread (data);
+
+ return NULL;
+}
+
+static void
+nautilus_search_engine_simple_start (NautilusSearchProvider *provider)
+{
+ NautilusSearchEngineSimple *simple;
+ SearchThreadData *data;
+ GThread *thread;
+
+ simple = NAUTILUS_SEARCH_ENGINE_SIMPLE (provider);
+
+ if (simple->active_search != NULL)
+ {
+ return;
+ }
+
+ DEBUG ("Simple engine start");
+
+ data = search_thread_data_new (simple, simple->query);
+
+ thread = g_thread_new ("nautilus-search-simple", search_thread_func, data);
+ simple->active_search = data;
+
+ g_object_notify (G_OBJECT (provider), "running");
+
+ g_thread_unref (thread);
+}
+
+static void
+nautilus_search_engine_simple_stop (NautilusSearchProvider *provider)
+{
+ NautilusSearchEngineSimple *simple = NAUTILUS_SEARCH_ENGINE_SIMPLE (provider);
+
+ if (simple->active_search != NULL)
+ {
+ DEBUG ("Simple engine stop");
+ g_cancellable_cancel (simple->active_search->cancellable);
+ }
+}
+
+static void
+nautilus_search_engine_simple_set_query (NautilusSearchProvider *provider,
+ NautilusQuery *query)
+{
+ NautilusSearchEngineSimple *simple = NAUTILUS_SEARCH_ENGINE_SIMPLE (provider);
+
+ g_clear_object (&simple->query);
+
+ simple->query = g_object_ref (query);
+}
+
+static gboolean
+nautilus_search_engine_simple_is_running (NautilusSearchProvider *provider)
+{
+ NautilusSearchEngineSimple *simple;
+
+ simple = NAUTILUS_SEARCH_ENGINE_SIMPLE (provider);
+
+ return simple->active_search != NULL;
+}
+
+static void
+nautilus_search_engine_simple_get_property (GObject *object,
+ guint arg_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ NautilusSearchEngineSimple *engine = NAUTILUS_SEARCH_ENGINE_SIMPLE (object);
+
+ switch (arg_id)
+ {
+ case PROP_RUNNING:
+ {
+ g_value_set_boolean (value, nautilus_search_engine_simple_is_running (NAUTILUS_SEARCH_PROVIDER (engine)));
+ }
+ break;
+ }
+}
+
+static void
+nautilus_search_provider_init (NautilusSearchProviderInterface *iface)
+{
+ iface->set_query = nautilus_search_engine_simple_set_query;
+ iface->start = nautilus_search_engine_simple_start;
+ iface->stop = nautilus_search_engine_simple_stop;
+ iface->is_running = nautilus_search_engine_simple_is_running;
+}
+
+static void
+nautilus_search_engine_simple_class_init (NautilusSearchEngineSimpleClass *class)
+{
+ GObjectClass *gobject_class;
+
+ gobject_class = G_OBJECT_CLASS (class);
+ gobject_class->finalize = finalize;
+ gobject_class->get_property = nautilus_search_engine_simple_get_property;
+
+ /**
+ * NautilusSearchEngine::running:
+ *
+ * Whether the search engine is running a search.
+ */
+ g_object_class_override_property (gobject_class, PROP_RUNNING, "running");
+}
+
+static void
+nautilus_search_engine_simple_init (NautilusSearchEngineSimple *engine)
+{
+ engine->query = NULL;
+ engine->active_search = NULL;
+}
+
+NautilusSearchEngineSimple *
+nautilus_search_engine_simple_new (void)
+{
+ NautilusSearchEngineSimple *engine;
+
+ engine = g_object_new (NAUTILUS_TYPE_SEARCH_ENGINE_SIMPLE, NULL);
+
+ return engine;
+}
diff --git a/src/nautilus-search-engine-simple.h b/src/nautilus-search-engine-simple.h
new file mode 100644
index 0000000..ff3c401
--- /dev/null
+++ b/src/nautilus-search-engine-simple.h
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2005 Red Hat, Inc
+ *
+ * Nautilus is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * Nautilus is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; see the file COPYING. If not,
+ * see <http://www.gnu.org/licenses/>.
+ *
+ * Author: Alexander Larsson <alexl@redhat.com>
+ *
+ */
+
+#include <glib-object.h>
+
+#pragma once
+
+G_BEGIN_DECLS
+
+#define NAUTILUS_TYPE_SEARCH_ENGINE_SIMPLE (nautilus_search_engine_simple_get_type ())
+
+G_DECLARE_FINAL_TYPE (NautilusSearchEngineSimple, nautilus_search_engine_simple, NAUTILUS, SEARCH_ENGINE_SIMPLE, GObject);
+
+NautilusSearchEngineSimple* nautilus_search_engine_simple_new (void);
+
+G_END_DECLS
diff --git a/src/nautilus-search-engine-tracker.c b/src/nautilus-search-engine-tracker.c
new file mode 100644
index 0000000..b4ae92a
--- /dev/null
+++ b/src/nautilus-search-engine-tracker.c
@@ -0,0 +1,627 @@
+/*
+ * Copyright (C) 2005 Mr Jamie McCracken
+ *
+ * Nautilus is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * Nautilus is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; see the file COPYING. If not,
+ * see <http://www.gnu.org/licenses/>.
+ *
+ * Author: Jamie McCracken <jamiemcc@gnome.org>
+ *
+ */
+
+#include <config.h>
+#include "nautilus-search-engine-tracker.h"
+
+#include "nautilus-search-engine-private.h"
+#include "nautilus-search-hit.h"
+#include "nautilus-search-provider.h"
+#include "nautilus-tracker-utilities.h"
+#define DEBUG_FLAG NAUTILUS_DEBUG_SEARCH
+#include "nautilus-debug.h"
+
+#include <string.h>
+#include <gio/gio.h>
+#include <libtracker-sparql/tracker-sparql.h>
+
+struct _NautilusSearchEngineTracker
+{
+ GObject parent_instance;
+
+ TrackerSparqlConnection *connection;
+ NautilusQuery *query;
+
+ gboolean query_pending;
+ GQueue *hits_pending;
+
+ gboolean recursive;
+ gboolean fts_enabled;
+
+ GCancellable *cancellable;
+};
+
+enum
+{
+ PROP_0,
+ PROP_RUNNING,
+ LAST_PROP
+};
+
+static void nautilus_search_provider_init (NautilusSearchProviderInterface *iface);
+
+G_DEFINE_TYPE_WITH_CODE (NautilusSearchEngineTracker,
+ nautilus_search_engine_tracker,
+ G_TYPE_OBJECT,
+ G_IMPLEMENT_INTERFACE (NAUTILUS_TYPE_SEARCH_PROVIDER,
+ nautilus_search_provider_init))
+
+static void
+finalize (GObject *object)
+{
+ NautilusSearchEngineTracker *tracker;
+
+ tracker = NAUTILUS_SEARCH_ENGINE_TRACKER (object);
+
+ if (tracker->cancellable)
+ {
+ g_cancellable_cancel (tracker->cancellable);
+ g_clear_object (&tracker->cancellable);
+ }
+
+ g_clear_object (&tracker->query);
+ g_queue_free_full (tracker->hits_pending, g_object_unref);
+ /* This is a singleton, no need to unref. */
+ tracker->connection = NULL;
+
+ G_OBJECT_CLASS (nautilus_search_engine_tracker_parent_class)->finalize (object);
+}
+
+#define BATCH_SIZE 100
+
+static void
+check_pending_hits (NautilusSearchEngineTracker *tracker,
+ gboolean force_send)
+{
+ GList *hits = NULL;
+ NautilusSearchHit *hit;
+
+ DEBUG ("Tracker engine add hits");
+
+ if (!force_send &&
+ g_queue_get_length (tracker->hits_pending) < BATCH_SIZE)
+ {
+ return;
+ }
+
+ while ((hit = g_queue_pop_head (tracker->hits_pending)))
+ {
+ hits = g_list_prepend (hits, hit);
+ }
+
+ nautilus_search_provider_hits_added (NAUTILUS_SEARCH_PROVIDER (tracker), hits);
+ g_list_free_full (hits, g_object_unref);
+}
+
+static void
+search_finished (NautilusSearchEngineTracker *tracker,
+ GError *error)
+{
+ DEBUG ("Tracker engine finished");
+
+ if (error == NULL)
+ {
+ check_pending_hits (tracker, TRUE);
+ }
+ else
+ {
+ g_queue_foreach (tracker->hits_pending, (GFunc) g_object_unref, NULL);
+ g_queue_clear (tracker->hits_pending);
+ }
+
+ tracker->query_pending = FALSE;
+
+ g_object_notify (G_OBJECT (tracker), "running");
+
+ if (error && !g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+ {
+ DEBUG ("Tracker engine error %s", error->message);
+ nautilus_search_provider_error (NAUTILUS_SEARCH_PROVIDER (tracker), error->message);
+ }
+ else
+ {
+ nautilus_search_provider_finished (NAUTILUS_SEARCH_PROVIDER (tracker),
+ NAUTILUS_SEARCH_PROVIDER_STATUS_NORMAL);
+ if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+ {
+ DEBUG ("Tracker engine finished and cancelled");
+ }
+ else
+ {
+ DEBUG ("Tracker engine finished correctly");
+ }
+ }
+
+ g_object_unref (tracker);
+}
+
+static void cursor_callback (GObject *object,
+ GAsyncResult *result,
+ gpointer user_data);
+
+static void
+cursor_next (NautilusSearchEngineTracker *tracker,
+ TrackerSparqlCursor *cursor)
+{
+ tracker_sparql_cursor_next_async (cursor,
+ tracker->cancellable,
+ cursor_callback,
+ tracker);
+}
+
+static void
+cursor_callback (GObject *object,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ NautilusSearchEngineTracker *tracker;
+ GError *error = NULL;
+ TrackerSparqlCursor *cursor;
+ NautilusSearchHit *hit;
+ const char *uri;
+ const char *mtime_str;
+ const char *atime_str;
+ const char *ctime_str;
+ const gchar *snippet;
+ GTimeVal tv;
+ gdouble rank, match;
+ gboolean success;
+ gchar *basename;
+
+ tracker = NAUTILUS_SEARCH_ENGINE_TRACKER (user_data);
+
+ cursor = TRACKER_SPARQL_CURSOR (object);
+ success = tracker_sparql_cursor_next_finish (cursor, result, &error);
+
+ if (!success)
+ {
+ search_finished (tracker, error);
+
+ g_clear_error (&error);
+ g_clear_object (&cursor);
+
+ return;
+ }
+
+ /* We iterate result by result, not n at a time. */
+ uri = tracker_sparql_cursor_get_string (cursor, 0, NULL);
+ rank = tracker_sparql_cursor_get_double (cursor, 1);
+ mtime_str = tracker_sparql_cursor_get_string (cursor, 2, NULL);
+ ctime_str = tracker_sparql_cursor_get_string (cursor, 3, NULL);
+ atime_str = tracker_sparql_cursor_get_string (cursor, 4, NULL);
+ basename = g_path_get_basename (uri);
+
+ hit = nautilus_search_hit_new (uri);
+ match = nautilus_query_matches_string (tracker->query, basename);
+ nautilus_search_hit_set_fts_rank (hit, rank + match);
+ g_free (basename);
+
+ if (tracker->fts_enabled)
+ {
+ snippet = tracker_sparql_cursor_get_string (cursor, 5, NULL);
+ if (snippet != NULL)
+ {
+ g_autofree gchar *escaped = NULL;
+ g_autoptr (GString) buffer = NULL;
+ /* Escape for markup, before adding our own markup. */
+ escaped = g_markup_escape_text (snippet, -1);
+ buffer = g_string_new (escaped);
+ g_string_replace (buffer, "_NAUTILUS_SNIPPET_DELIM_START_", "<b>", 0);
+ g_string_replace (buffer, "_NAUTILUS_SNIPPET_DELIM_END_", "</b>", 0);
+
+ nautilus_search_hit_set_fts_snippet (hit, buffer->str);
+ }
+ }
+
+ if (g_time_val_from_iso8601 (mtime_str, &tv))
+ {
+ GDateTime *date;
+ date = g_date_time_new_from_timeval_local (&tv);
+ nautilus_search_hit_set_modification_time (hit, date);
+ g_date_time_unref (date);
+ }
+ else
+ {
+ g_warning ("unable to parse mtime: %s", mtime_str);
+ }
+ if (g_time_val_from_iso8601 (atime_str, &tv))
+ {
+ GDateTime *date;
+ date = g_date_time_new_from_timeval_local (&tv);
+ nautilus_search_hit_set_access_time (hit, date);
+ g_date_time_unref (date);
+ }
+ else
+ {
+ g_warning ("unable to parse atime: %s", atime_str);
+ }
+ if (g_time_val_from_iso8601 (ctime_str, &tv))
+ {
+ GDateTime *date;
+ date = g_date_time_new_from_timeval_local (&tv);
+ nautilus_search_hit_set_creation_time (hit, date);
+ g_date_time_unref (date);
+ }
+ else
+ {
+ g_warning ("unable to parse ctime: %s", ctime_str);
+ }
+
+ g_queue_push_head (tracker->hits_pending, hit);
+ check_pending_hits (tracker, FALSE);
+
+ /* Get next */
+ cursor_next (tracker, cursor);
+}
+
+static void
+query_callback (GObject *object,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ NautilusSearchEngineTracker *tracker;
+ TrackerSparqlConnection *connection;
+ TrackerSparqlCursor *cursor;
+ GError *error = NULL;
+
+ tracker = NAUTILUS_SEARCH_ENGINE_TRACKER (user_data);
+
+ connection = TRACKER_SPARQL_CONNECTION (object);
+ cursor = tracker_sparql_connection_query_finish (connection,
+ result,
+ &error);
+
+ if (error != NULL)
+ {
+ search_finished (tracker, error);
+ g_error_free (error);
+ }
+ else
+ {
+ cursor_next (tracker, cursor);
+ }
+}
+
+static gboolean
+search_finished_idle (gpointer user_data)
+{
+ NautilusSearchEngineTracker *tracker = user_data;
+
+ DEBUG ("Tracker engine finished idle");
+
+ search_finished (tracker, NULL);
+
+ return FALSE;
+}
+
+/* This is used to compensate rank if fts:rank is not set (resp. fts:match is
+ * not used). The value was determined experimentally. I am convinced that
+ * fts:rank is currently always set to 5.0 in case of filename match.
+ */
+#define FILENAME_RANK "5.0"
+
+static void
+nautilus_search_engine_tracker_start (NautilusSearchProvider *provider)
+{
+ NautilusSearchEngineTracker *tracker;
+ gchar *query_text, *search_text, *location_uri, *downcase;
+ GFile *location;
+ GString *sparql;
+ g_autoptr (GPtrArray) mimetypes = NULL;
+ GPtrArray *date_range;
+
+ tracker = NAUTILUS_SEARCH_ENGINE_TRACKER (provider);
+
+ if (tracker->query_pending)
+ {
+ return;
+ }
+
+ DEBUG ("Tracker engine start");
+ g_object_ref (tracker);
+ tracker->query_pending = TRUE;
+
+ g_object_notify (G_OBJECT (provider), "running");
+
+ if (tracker->connection == NULL)
+ {
+ g_idle_add (search_finished_idle, provider);
+ return;
+ }
+
+ tracker->fts_enabled = nautilus_query_get_search_content (tracker->query);
+
+ query_text = nautilus_query_get_text (tracker->query);
+ downcase = g_utf8_strdown (query_text, -1);
+ search_text = tracker_sparql_escape_string (downcase);
+ g_free (query_text);
+ g_free (downcase);
+
+ location = nautilus_query_get_location (tracker->query);
+ location_uri = location ? g_file_get_uri (location) : NULL;
+ mimetypes = nautilus_query_get_mime_types (tracker->query);
+
+ sparql = g_string_new ("SELECT DISTINCT"
+ " ?url"
+ " xsd:double(COALESCE(?rank2, ?rank1)) AS ?rank"
+ " nfo:fileLastModified(?file)"
+ " nfo:fileCreated(?file)"
+ " nfo:fileLastAccessed(?file)");
+
+ if (tracker->fts_enabled && *search_text)
+ {
+ g_string_append (sparql,
+ "fts:snippet(?content,"
+ " '_NAUTILUS_SNIPPET_DELIM_START_',"
+ " '_NAUTILUS_SNIPPET_DELIM_END_', "
+ " '…',"
+ " 20)");
+ }
+
+ g_string_append (sparql, "FROM tracker:FileSystem ");
+
+ if (tracker->fts_enabled)
+ {
+ g_string_append (sparql, "FROM tracker:Documents ");
+ }
+
+ g_string_append (sparql,
+ "\nWHERE {"
+ " ?file a nfo:FileDataObject;"
+ " nfo:fileLastModified ?mtime;"
+ " nfo:fileLastAccessed ?atime;"
+ " nie:dataSource/tracker:available true;"
+ " nie:url ?url."
+ " OPTIONAL { ?file nfo:fileCreated ?ctime.}");
+
+ if (mimetypes->len > 0)
+ {
+ g_string_append (sparql,
+ " ?content nie:isStoredAs ?file;"
+ " nie:mimeType ?mime");
+ }
+
+ if (tracker->fts_enabled && *search_text)
+ {
+ /* Use fts:match only for content search to not lose some filename results due to stop words. */
+ g_string_append_printf (sparql,
+ " { "
+ " ?content nie:isStoredAs ?file ."
+ " ?content fts:match \"%s*\" ."
+ " BIND(fts:rank(?content) AS ?rank1) ."
+ " } UNION",
+ search_text);
+ }
+
+ g_string_append_printf (sparql,
+ " {"
+ " ?file nfo:fileName ?filename ."
+ " FILTER(fn:contains(fn:lower-case(?filename), '%s')) ."
+ " BIND(" FILENAME_RANK " AS ?rank2) ."
+ " }",
+ search_text);
+
+ g_string_append_printf (sparql, " . FILTER( ");
+
+ if (!tracker->recursive)
+ {
+ g_string_append_printf (sparql, "tracker:uri-is-parent('%s', ?url)", location_uri);
+ }
+ else
+ {
+ /* STRSTARTS is faster than tracker:uri-is-descendant().
+ * See https://gitlab.gnome.org/GNOME/tracker/-/issues/243
+ */
+ g_string_append_printf (sparql, "STRSTARTS(?url, '%s/')", location_uri);
+ }
+
+ date_range = nautilus_query_get_date_range (tracker->query);
+ if (date_range)
+ {
+ NautilusQuerySearchType type;
+ gchar *initial_date_format;
+ gchar *end_date_format;
+ GDateTime *initial_date;
+ GDateTime *end_date;
+ GDateTime *shifted_end_date;
+
+ initial_date = g_ptr_array_index (date_range, 0);
+ end_date = g_ptr_array_index (date_range, 1);
+ /* As we do for other searches, we want to make the end date inclusive.
+ * For that, add a day to it */
+ shifted_end_date = g_date_time_add_days (end_date, 1);
+
+ type = nautilus_query_get_search_type (tracker->query);
+ initial_date_format = g_date_time_format_iso8601 (initial_date);
+ end_date_format = g_date_time_format_iso8601 (shifted_end_date);
+
+ g_string_append (sparql, " && ");
+
+ if (type == NAUTILUS_QUERY_SEARCH_TYPE_LAST_ACCESS)
+ {
+ g_string_append_printf (sparql, "?atime >= \"%s\"^^xsd:dateTime", initial_date_format);
+ g_string_append_printf (sparql, " && ?atime <= \"%s\"^^xsd:dateTime", end_date_format);
+ }
+ else if (type == NAUTILUS_QUERY_SEARCH_TYPE_LAST_MODIFIED)
+ {
+ g_string_append_printf (sparql, "?mtime >= \"%s\"^^xsd:dateTime", initial_date_format);
+ g_string_append_printf (sparql, " && ?mtime <= \"%s\"^^xsd:dateTime", end_date_format);
+ }
+ else
+ {
+ g_string_append_printf (sparql, "?ctime >= \"%s\"^^xsd:dateTime", initial_date_format);
+ g_string_append_printf (sparql, " && ?ctime <= \"%s\"^^xsd:dateTime", end_date_format);
+ }
+
+
+ g_free (initial_date_format);
+ g_free (end_date_format);
+ g_ptr_array_unref (date_range);
+ }
+
+ if (mimetypes->len > 0)
+ {
+ g_string_append (sparql, " && (");
+
+ for (gint i = 0; i < mimetypes->len; i++)
+ {
+ if (i != 0)
+ {
+ g_string_append (sparql, " || ");
+ }
+
+ g_string_append_printf (sparql, "fn:contains(?mime, '%s')",
+ (gchar *) g_ptr_array_index (mimetypes, i));
+ }
+ g_string_append (sparql, ")\n");
+ }
+
+ g_string_append (sparql, ")} ORDER BY DESC (?rank)");
+
+ tracker->cancellable = g_cancellable_new ();
+ tracker_sparql_connection_query_async (tracker->connection,
+ sparql->str,
+ tracker->cancellable,
+ query_callback,
+ tracker);
+ g_string_free (sparql, TRUE);
+
+ g_free (search_text);
+ g_free (location_uri);
+ g_object_unref (location);
+}
+
+static void
+nautilus_search_engine_tracker_stop (NautilusSearchProvider *provider)
+{
+ NautilusSearchEngineTracker *tracker;
+
+ tracker = NAUTILUS_SEARCH_ENGINE_TRACKER (provider);
+
+ if (tracker->query_pending)
+ {
+ DEBUG ("Tracker engine stop");
+ g_cancellable_cancel (tracker->cancellable);
+ g_clear_object (&tracker->cancellable);
+ tracker->query_pending = FALSE;
+
+ g_object_notify (G_OBJECT (provider), "running");
+ }
+}
+
+static void
+nautilus_search_engine_tracker_set_query (NautilusSearchProvider *provider,
+ NautilusQuery *query)
+{
+ g_autoptr (GFile) location = NULL;
+ NautilusSearchEngineTracker *tracker;
+
+ tracker = NAUTILUS_SEARCH_ENGINE_TRACKER (provider);
+ location = nautilus_query_get_location (query);
+
+ g_clear_object (&tracker->query);
+
+ tracker->query = g_object_ref (query);
+ tracker->recursive = is_recursive_search (NAUTILUS_SEARCH_ENGINE_TYPE_INDEXED,
+ nautilus_query_get_recursive (query),
+ location);
+}
+
+static gboolean
+nautilus_search_engine_tracker_is_running (NautilusSearchProvider *provider)
+{
+ NautilusSearchEngineTracker *tracker;
+
+ tracker = NAUTILUS_SEARCH_ENGINE_TRACKER (provider);
+
+ return tracker->query_pending;
+}
+
+static void
+nautilus_search_provider_init (NautilusSearchProviderInterface *iface)
+{
+ iface->set_query = nautilus_search_engine_tracker_set_query;
+ iface->start = nautilus_search_engine_tracker_start;
+ iface->stop = nautilus_search_engine_tracker_stop;
+ iface->is_running = nautilus_search_engine_tracker_is_running;
+}
+
+static void
+nautilus_search_engine_tracker_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ NautilusSearchProvider *self = NAUTILUS_SEARCH_PROVIDER (object);
+
+ switch (prop_id)
+ {
+ case PROP_RUNNING:
+ {
+ g_value_set_boolean (value, nautilus_search_engine_tracker_is_running (self));
+ }
+ break;
+
+ default:
+ {
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+ }
+}
+
+static void
+nautilus_search_engine_tracker_class_init (NautilusSearchEngineTrackerClass *class)
+{
+ GObjectClass *gobject_class;
+
+ gobject_class = G_OBJECT_CLASS (class);
+ gobject_class->finalize = finalize;
+ gobject_class->get_property = nautilus_search_engine_tracker_get_property;
+
+ /**
+ * NautilusSearchEngine::running:
+ *
+ * Whether the search engine is running a search.
+ */
+ g_object_class_override_property (gobject_class, PROP_RUNNING, "running");
+}
+
+static void
+nautilus_search_engine_tracker_init (NautilusSearchEngineTracker *engine)
+{
+ GError *error = NULL;
+
+ engine->hits_pending = g_queue_new ();
+
+ engine->connection = nautilus_tracker_get_miner_fs_connection (&error);
+ if (error)
+ {
+ g_warning ("Could not establish a connection to Tracker: %s", error->message);
+ g_error_free (error);
+ }
+}
+
+
+NautilusSearchEngineTracker *
+nautilus_search_engine_tracker_new (void)
+{
+ return g_object_new (NAUTILUS_TYPE_SEARCH_ENGINE_TRACKER, NULL);
+}
diff --git a/src/nautilus-search-engine-tracker.h b/src/nautilus-search-engine-tracker.h
new file mode 100644
index 0000000..efc3038
--- /dev/null
+++ b/src/nautilus-search-engine-tracker.h
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2005 Mr Jamie McCracken
+ *
+ * Nautilus is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * Nautilus is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; see the file COPYING. If not,
+ * see <http://www.gnu.org/licenses/>.
+ *
+ * Author: Jamie McCracken (jamiemcc@gnome.org)
+ *
+ */
+
+#pragma once
+
+#include "nautilus-search-engine.h"
+
+#define NAUTILUS_TYPE_SEARCH_ENGINE_TRACKER (nautilus_search_engine_tracker_get_type ())
+G_DECLARE_FINAL_TYPE (NautilusSearchEngineTracker, nautilus_search_engine_tracker, NAUTILUS, SEARCH_ENGINE_TRACKER, GObject)
+
+NautilusSearchEngineTracker* nautilus_search_engine_tracker_new (void); \ No newline at end of file
diff --git a/src/nautilus-search-engine.c b/src/nautilus-search-engine.c
new file mode 100644
index 0000000..48bfa1f
--- /dev/null
+++ b/src/nautilus-search-engine.c
@@ -0,0 +1,588 @@
+/*
+ * Copyright (C) 2005 Novell, Inc.
+ *
+ * Nautilus is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * Nautilus is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; see the file COPYING. If not,
+ * see <http://www.gnu.org/licenses/>.
+ *
+ * Author: Anders Carlsson <andersca@imendio.com>
+ *
+ */
+
+#include <config.h>
+#include "nautilus-search-engine.h"
+#include "nautilus-search-engine-private.h"
+
+#include "nautilus-file-utilities.h"
+#include "nautilus-search-engine-model.h"
+#include <glib/gi18n.h>
+#define DEBUG_FLAG NAUTILUS_DEBUG_SEARCH
+#include "nautilus-debug.h"
+#include "nautilus-search-engine-recent.h"
+#include "nautilus-search-engine-simple.h"
+#include "nautilus-search-engine-tracker.h"
+
+typedef struct
+{
+ NautilusSearchEngineTracker *tracker;
+ NautilusSearchEngineRecent *recent;
+ NautilusSearchEngineSimple *simple;
+ NautilusSearchEngineModel *model;
+
+ GHashTable *uris;
+ guint providers_running;
+ guint providers_finished;
+ guint providers_error;
+
+ gboolean running;
+ gboolean restart;
+} NautilusSearchEnginePrivate;
+
+enum
+{
+ PROP_0,
+ PROP_RUNNING,
+ LAST_PROP
+};
+
+static void nautilus_search_provider_init (NautilusSearchProviderInterface *iface);
+
+static gboolean nautilus_search_engine_is_running (NautilusSearchProvider *provider);
+
+G_DEFINE_TYPE_WITH_CODE (NautilusSearchEngine,
+ nautilus_search_engine,
+ G_TYPE_OBJECT,
+ G_ADD_PRIVATE (NautilusSearchEngine)
+ G_IMPLEMENT_INTERFACE (NAUTILUS_TYPE_SEARCH_PROVIDER,
+ nautilus_search_provider_init))
+
+static void
+nautilus_search_engine_set_query (NautilusSearchProvider *provider,
+ NautilusQuery *query)
+{
+ NautilusSearchEngine *engine;
+ NautilusSearchEnginePrivate *priv;
+
+ engine = NAUTILUS_SEARCH_ENGINE (provider);
+ priv = nautilus_search_engine_get_instance_private (engine);
+
+ nautilus_search_provider_set_query (NAUTILUS_SEARCH_PROVIDER (priv->tracker), query);
+ nautilus_search_provider_set_query (NAUTILUS_SEARCH_PROVIDER (priv->recent), query);
+ nautilus_search_provider_set_query (NAUTILUS_SEARCH_PROVIDER (priv->model), query);
+ nautilus_search_provider_set_query (NAUTILUS_SEARCH_PROVIDER (priv->simple), query);
+}
+
+static void
+search_engine_start_real_setup (NautilusSearchEngine *engine)
+{
+ NautilusSearchEnginePrivate *priv;
+
+ priv = nautilus_search_engine_get_instance_private (engine);
+
+ priv->providers_running = 0;
+ priv->providers_finished = 0;
+ priv->providers_error = 0;
+
+ priv->restart = FALSE;
+
+ DEBUG ("Search engine start real setup");
+
+ g_object_ref (engine);
+}
+
+static void
+search_engine_start_real_tracker (NautilusSearchEngine *engine)
+{
+ NautilusSearchEnginePrivate *priv;
+
+ priv = nautilus_search_engine_get_instance_private (engine);
+
+ priv->providers_running++;
+ nautilus_search_provider_start (NAUTILUS_SEARCH_PROVIDER (priv->tracker));
+}
+
+static void
+search_engine_start_real_recent (NautilusSearchEngine *engine)
+{
+ NautilusSearchEnginePrivate *priv;
+
+ priv = nautilus_search_engine_get_instance_private (engine);
+
+ priv->providers_running++;
+ nautilus_search_provider_start (NAUTILUS_SEARCH_PROVIDER (priv->recent));
+}
+
+static void
+search_engine_start_real_model (NautilusSearchEngine *engine)
+{
+ NautilusSearchEnginePrivate *priv;
+
+ priv = nautilus_search_engine_get_instance_private (engine);
+ if (nautilus_search_engine_model_get_model (priv->model))
+ {
+ priv->providers_running++;
+ nautilus_search_provider_start (NAUTILUS_SEARCH_PROVIDER (priv->model));
+ }
+}
+
+static void
+search_engine_start_real_simple (NautilusSearchEngine *engine)
+{
+ NautilusSearchEnginePrivate *priv;
+
+ priv = nautilus_search_engine_get_instance_private (engine);
+ priv->providers_running++;
+
+ nautilus_search_provider_start (NAUTILUS_SEARCH_PROVIDER (priv->simple));
+}
+
+static void
+search_engine_start_real (NautilusSearchEngine *engine,
+ NautilusSearchEngineTarget target_engine)
+{
+ search_engine_start_real_setup (engine);
+
+ switch (target_engine)
+ {
+ case NAUTILUS_SEARCH_ENGINE_TRACKER_ENGINE:
+ {
+ search_engine_start_real_tracker (engine);
+ }
+ break;
+
+ case NAUTILUS_SEARCH_ENGINE_RECENT_ENGINE:
+ {
+ search_engine_start_real_recent (engine);
+ }
+ break;
+
+ case NAUTILUS_SEARCH_ENGINE_MODEL_ENGINE:
+ {
+ search_engine_start_real_model (engine);
+ }
+ break;
+
+ case NAUTILUS_SEARCH_ENGINE_SIMPLE_ENGINE:
+ {
+ search_engine_start_real_simple (engine);
+ }
+ break;
+
+ case NAUTILUS_SEARCH_ENGINE_ALL_ENGINES:
+ default:
+ {
+ search_engine_start_real_tracker (engine);
+ search_engine_start_real_recent (engine);
+ search_engine_start_real_model (engine);
+ search_engine_start_real_simple (engine);
+ }
+ }
+}
+
+void
+nautilus_search_engine_start_by_target (NautilusSearchProvider *provider,
+ NautilusSearchEngineTarget target_engine)
+{
+ NautilusSearchEngine *engine;
+ NautilusSearchEnginePrivate *priv;
+ gint num_finished;
+
+ engine = NAUTILUS_SEARCH_ENGINE (provider);
+ priv = nautilus_search_engine_get_instance_private (engine);
+
+ DEBUG ("Search engine start");
+
+ num_finished = priv->providers_error + priv->providers_finished;
+
+ if (priv->running)
+ {
+ if (num_finished == priv->providers_running &&
+ priv->restart)
+ {
+ search_engine_start_real (engine, target_engine);
+ }
+
+ return;
+ }
+
+ priv->running = TRUE;
+
+ g_object_notify (G_OBJECT (provider), "running");
+
+ if (num_finished < priv->providers_running)
+ {
+ priv->restart = TRUE;
+ }
+ else
+ {
+ search_engine_start_real (engine, target_engine);
+ }
+}
+
+
+
+static void
+nautilus_search_engine_start (NautilusSearchProvider *provider)
+{
+ NautilusSearchEngine *engine;
+ NautilusSearchEnginePrivate *priv;
+ gint num_finished;
+
+ engine = NAUTILUS_SEARCH_ENGINE (provider);
+ priv = nautilus_search_engine_get_instance_private (engine);
+
+ DEBUG ("Search engine start");
+
+ num_finished = priv->providers_error + priv->providers_finished;
+
+ if (priv->running)
+ {
+ if (num_finished == priv->providers_running &&
+ priv->restart)
+ {
+ search_engine_start_real (engine, NAUTILUS_SEARCH_ENGINE_ALL_ENGINES);
+ }
+
+ return;
+ }
+
+ priv->running = TRUE;
+
+ g_object_notify (G_OBJECT (provider), "running");
+
+ if (num_finished < priv->providers_running)
+ {
+ priv->restart = TRUE;
+ }
+ else
+ {
+ search_engine_start_real (engine, NAUTILUS_SEARCH_ENGINE_ALL_ENGINES);
+ }
+}
+
+static void
+nautilus_search_engine_stop (NautilusSearchProvider *provider)
+{
+ NautilusSearchEngine *engine;
+ NautilusSearchEnginePrivate *priv;
+
+ engine = NAUTILUS_SEARCH_ENGINE (provider);
+ priv = nautilus_search_engine_get_instance_private (engine);
+
+ DEBUG ("Search engine stop");
+
+ nautilus_search_provider_stop (NAUTILUS_SEARCH_PROVIDER (priv->tracker));
+ nautilus_search_provider_stop (NAUTILUS_SEARCH_PROVIDER (priv->recent));
+ nautilus_search_provider_stop (NAUTILUS_SEARCH_PROVIDER (priv->model));
+ nautilus_search_provider_stop (NAUTILUS_SEARCH_PROVIDER (priv->simple));
+
+ priv->running = FALSE;
+ priv->restart = FALSE;
+
+ g_object_notify (G_OBJECT (provider), "running");
+}
+
+static void
+search_provider_hits_added (NautilusSearchProvider *provider,
+ GList *hits,
+ NautilusSearchEngine *engine)
+{
+ NautilusSearchEnginePrivate *priv;
+ GList *added = NULL;
+ GList *l;
+
+ priv = nautilus_search_engine_get_instance_private (engine);
+
+ if (!priv->running || priv->restart)
+ {
+ DEBUG ("Ignoring hits-added, since engine is %s",
+ !priv->running ? "not running" : "waiting to restart");
+ return;
+ }
+
+ for (l = hits; l != NULL; l = l->next)
+ {
+ NautilusSearchHit *hit = l->data;
+ int count;
+ const char *uri;
+
+ uri = nautilus_search_hit_get_uri (hit);
+ count = GPOINTER_TO_INT (g_hash_table_lookup (priv->uris, uri));
+ if (count == 0)
+ {
+ added = g_list_prepend (added, hit);
+ }
+ g_hash_table_replace (priv->uris, g_strdup (uri), GINT_TO_POINTER (++count));
+ }
+ if (added != NULL)
+ {
+ added = g_list_reverse (added);
+ nautilus_search_provider_hits_added (NAUTILUS_SEARCH_PROVIDER (engine), added);
+ g_list_free (added);
+ }
+}
+
+static void
+check_providers_status (NautilusSearchEngine *engine)
+{
+ NautilusSearchEnginePrivate *priv;
+ gint num_finished;
+
+ priv = nautilus_search_engine_get_instance_private (engine);
+ num_finished = priv->providers_error + priv->providers_finished;
+
+ if (num_finished < priv->providers_running)
+ {
+ return;
+ }
+
+ if (num_finished == priv->providers_error)
+ {
+ DEBUG ("Search engine error");
+ nautilus_search_provider_error (NAUTILUS_SEARCH_PROVIDER (engine),
+ _("Unable to complete the requested search"));
+ }
+ else
+ {
+ if (priv->restart)
+ {
+ DEBUG ("Search engine finished and restarting");
+ }
+ else
+ {
+ DEBUG ("Search engine finished");
+ }
+ nautilus_search_provider_finished (NAUTILUS_SEARCH_PROVIDER (engine),
+ priv->restart ? NAUTILUS_SEARCH_PROVIDER_STATUS_RESTARTING :
+ NAUTILUS_SEARCH_PROVIDER_STATUS_NORMAL);
+ }
+
+ priv->running = FALSE;
+ g_object_notify (G_OBJECT (engine), "running");
+
+ g_hash_table_remove_all (priv->uris);
+
+ if (priv->restart)
+ {
+ nautilus_search_engine_start (NAUTILUS_SEARCH_PROVIDER (engine));
+ }
+
+ g_object_unref (engine);
+}
+
+static void
+search_provider_error (NautilusSearchProvider *provider,
+ const char *error_message,
+ NautilusSearchEngine *engine)
+{
+ NautilusSearchEnginePrivate *priv;
+
+ DEBUG ("Search provider error: %s", error_message);
+
+ priv = nautilus_search_engine_get_instance_private (engine);
+ priv->providers_error++;
+
+ check_providers_status (engine);
+}
+
+static void
+search_provider_finished (NautilusSearchProvider *provider,
+ NautilusSearchProviderStatus status,
+ NautilusSearchEngine *engine)
+{
+ NautilusSearchEnginePrivate *priv;
+
+ DEBUG ("Search provider finished");
+
+ priv = nautilus_search_engine_get_instance_private (engine);
+ priv->providers_finished++;
+
+ check_providers_status (engine);
+}
+
+static void
+connect_provider_signals (NautilusSearchEngine *engine,
+ NautilusSearchProvider *provider)
+{
+ g_signal_connect (provider, "hits-added",
+ G_CALLBACK (search_provider_hits_added),
+ engine);
+ g_signal_connect (provider, "finished",
+ G_CALLBACK (search_provider_finished),
+ engine);
+ g_signal_connect (provider, "error",
+ G_CALLBACK (search_provider_error),
+ engine);
+}
+
+static gboolean
+nautilus_search_engine_is_running (NautilusSearchProvider *provider)
+{
+ NautilusSearchEngine *engine;
+ NautilusSearchEnginePrivate *priv;
+
+ engine = NAUTILUS_SEARCH_ENGINE (provider);
+ priv = nautilus_search_engine_get_instance_private (engine);
+
+ return priv->running;
+}
+
+static void
+nautilus_search_provider_init (NautilusSearchProviderInterface *iface)
+{
+ iface->set_query = nautilus_search_engine_set_query;
+ iface->start = nautilus_search_engine_start;
+ iface->stop = nautilus_search_engine_stop;
+ iface->is_running = nautilus_search_engine_is_running;
+}
+
+static void
+nautilus_search_engine_finalize (GObject *object)
+{
+ NautilusSearchEngine *engine;
+ NautilusSearchEnginePrivate *priv;
+
+ engine = NAUTILUS_SEARCH_ENGINE (object);
+ priv = nautilus_search_engine_get_instance_private (engine);
+
+ g_hash_table_destroy (priv->uris);
+
+ g_clear_object (&priv->tracker);
+ g_clear_object (&priv->recent);
+ g_clear_object (&priv->model);
+ g_clear_object (&priv->simple);
+
+ G_OBJECT_CLASS (nautilus_search_engine_parent_class)->finalize (object);
+}
+
+static void
+nautilus_search_engine_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ NautilusSearchProvider *self = NAUTILUS_SEARCH_PROVIDER (object);
+
+ switch (prop_id)
+ {
+ case PROP_RUNNING:
+ {
+ g_value_set_boolean (value, nautilus_search_engine_is_running (self));
+ }
+ break;
+
+ default:
+ {
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+ }
+}
+
+static void
+nautilus_search_engine_class_init (NautilusSearchEngineClass *class)
+{
+ GObjectClass *object_class;
+
+ object_class = (GObjectClass *) class;
+
+ object_class->finalize = nautilus_search_engine_finalize;
+ object_class->get_property = nautilus_search_engine_get_property;
+
+ /**
+ * NautilusSearchEngine::running:
+ *
+ * Whether the search engine is running a search.
+ */
+ g_object_class_override_property (object_class, PROP_RUNNING, "running");
+}
+
+static void
+nautilus_search_engine_init (NautilusSearchEngine *engine)
+{
+ NautilusSearchEnginePrivate *priv;
+
+ priv = nautilus_search_engine_get_instance_private (engine);
+ priv->uris = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
+
+ priv->tracker = nautilus_search_engine_tracker_new ();
+ connect_provider_signals (engine, NAUTILUS_SEARCH_PROVIDER (priv->tracker));
+
+ priv->model = nautilus_search_engine_model_new ();
+ connect_provider_signals (engine, NAUTILUS_SEARCH_PROVIDER (priv->model));
+
+ priv->simple = nautilus_search_engine_simple_new ();
+ connect_provider_signals (engine, NAUTILUS_SEARCH_PROVIDER (priv->simple));
+
+ priv->recent = nautilus_search_engine_recent_new ();
+ connect_provider_signals (engine, NAUTILUS_SEARCH_PROVIDER (priv->recent));
+}
+
+NautilusSearchEngine *
+nautilus_search_engine_new (void)
+{
+ NautilusSearchEngine *engine;
+
+ engine = g_object_new (NAUTILUS_TYPE_SEARCH_ENGINE, NULL);
+
+ return engine;
+}
+
+NautilusSearchEngineModel *
+nautilus_search_engine_get_model_provider (NautilusSearchEngine *engine)
+{
+ NautilusSearchEnginePrivate *priv;
+
+ priv = nautilus_search_engine_get_instance_private (engine);
+
+ return priv->model;
+}
+
+gboolean
+is_recursive_search (NautilusSearchEngineType engine_type,
+ NautilusQueryRecursive recursive,
+ GFile *location)
+{
+ switch (recursive)
+ {
+ case NAUTILUS_QUERY_RECURSIVE_NEVER:
+ {
+ return FALSE;
+ }
+
+ case NAUTILUS_QUERY_RECURSIVE_ALWAYS:
+ {
+ return TRUE;
+ }
+
+ case NAUTILUS_QUERY_RECURSIVE_INDEXED_ONLY:
+ {
+ return engine_type == NAUTILUS_SEARCH_ENGINE_TYPE_INDEXED;
+ }
+
+ case NAUTILUS_QUERY_RECURSIVE_LOCAL_ONLY:
+ {
+ g_autoptr (GFileInfo) file_system_info = NULL;
+
+ file_system_info = g_file_query_filesystem_info (location,
+ G_FILE_ATTRIBUTE_FILESYSTEM_REMOTE,
+ NULL, NULL);
+ if (file_system_info != NULL)
+ {
+ return !g_file_info_get_attribute_boolean (file_system_info,
+ G_FILE_ATTRIBUTE_FILESYSTEM_REMOTE);
+ }
+ }
+ }
+
+ return TRUE;
+}
diff --git a/src/nautilus-search-engine.h b/src/nautilus-search-engine.h
new file mode 100644
index 0000000..33c3644
--- /dev/null
+++ b/src/nautilus-search-engine.h
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2005 Novell, Inc.
+ *
+ * Nautilus is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * Nautilus is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; see the file COPYING. If not,
+ * see <http://www.gnu.org/licenses/>.
+ *
+ * Author: Anders Carlsson <andersca@imendio.com>
+ *
+ */
+
+#pragma once
+
+#include <glib-object.h>
+
+#include "nautilus-directory.h"
+#include "nautilus-search-engine-model.h"
+#include "nautilus-search-provider.h"
+
+G_BEGIN_DECLS
+
+#define NAUTILUS_TYPE_SEARCH_ENGINE (nautilus_search_engine_get_type ())
+
+G_DECLARE_DERIVABLE_TYPE (NautilusSearchEngine, nautilus_search_engine, NAUTILUS, SEARCH_ENGINE, GObject)
+
+struct _NautilusSearchEngineClass
+{
+ GObjectClass parent_class;
+};
+
+NautilusSearchEngine *nautilus_search_engine_new (void);
+NautilusSearchEngineModel *
+ nautilus_search_engine_get_model_provider (NautilusSearchEngine *engine);
+
+G_END_DECLS
+
+void nautilus_search_engine_start_by_target (NautilusSearchProvider *provider,
+ NautilusSearchEngineTarget taregt_engine); \ No newline at end of file
diff --git a/src/nautilus-search-hit.c b/src/nautilus-search-hit.c
new file mode 100644
index 0000000..6efaa56
--- /dev/null
+++ b/src/nautilus-search-hit.c
@@ -0,0 +1,482 @@
+/*
+ * Copyright (C) 2012 Red Hat, Inc.
+ *
+ * Nautilus is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * Nautilus is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; see the file COPYING. If not,
+ * see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <config.h>
+
+#include <string.h>
+#include <gio/gio.h>
+
+#include "nautilus-search-hit.h"
+#include "nautilus-query.h"
+#define DEBUG_FLAG NAUTILUS_DEBUG_SEARCH_HIT
+#include "nautilus-debug.h"
+
+struct _NautilusSearchHit
+{
+ GObject parent_instance;
+
+ char *uri;
+
+ GDateTime *modification_time;
+ GDateTime *access_time;
+ GDateTime *creation_time;
+ gdouble fts_rank;
+ gchar *fts_snippet;
+
+ gdouble relevance;
+};
+
+enum
+{
+ PROP_URI = 1,
+ PROP_RELEVANCE,
+ PROP_MODIFICATION_TIME,
+ PROP_ACCESS_TIME,
+ PROP_CREATION_TIME,
+ PROP_FTS_RANK,
+ PROP_FTS_SNIPPET,
+ NUM_PROPERTIES
+};
+
+G_DEFINE_TYPE (NautilusSearchHit, nautilus_search_hit, G_TYPE_OBJECT)
+
+void
+nautilus_search_hit_compute_scores (NautilusSearchHit *hit,
+ NautilusQuery *query)
+{
+ GDateTime *now;
+ GFile *query_location;
+ GFile *hit_location;
+ GTimeSpan m_diff = G_MAXINT64;
+ GTimeSpan a_diff = G_MAXINT64;
+ GTimeSpan t_diff = G_MAXINT64;
+ gdouble recent_bonus = 0.0;
+ gdouble proximity_bonus = 0.0;
+ gdouble match_bonus = 0.0;
+
+ query_location = nautilus_query_get_location (query);
+ hit_location = g_file_new_for_uri (hit->uri);
+
+ if (g_file_has_prefix (hit_location, query_location))
+ {
+ GFile *parent, *location;
+ guint dir_count = 0;
+
+ parent = g_file_get_parent (hit_location);
+
+ while (!g_file_equal (parent, query_location))
+ {
+ dir_count++;
+ location = parent;
+ parent = g_file_get_parent (location);
+ g_object_unref (location);
+ }
+ g_object_unref (parent);
+
+ if (dir_count < 10)
+ {
+ proximity_bonus = 10000.0 - 1000.0 * dir_count;
+ }
+ }
+ g_object_unref (hit_location);
+
+ now = g_date_time_new_now_local ();
+ if (hit->modification_time != NULL)
+ {
+ m_diff = g_date_time_difference (now, hit->modification_time);
+ }
+ if (hit->access_time != NULL)
+ {
+ a_diff = g_date_time_difference (now, hit->access_time);
+ }
+ m_diff /= G_TIME_SPAN_DAY;
+ a_diff /= G_TIME_SPAN_DAY;
+ t_diff = MIN (m_diff, a_diff);
+ if (t_diff > 90)
+ {
+ recent_bonus = 0.0;
+ }
+ else if (t_diff > 30)
+ {
+ recent_bonus = 10.0;
+ }
+ else if (t_diff > 14)
+ {
+ recent_bonus = 30.0;
+ }
+ else if (t_diff > 7)
+ {
+ recent_bonus = 50.0;
+ }
+ else if (t_diff > 1)
+ {
+ recent_bonus = 70.0;
+ }
+ else
+ {
+ recent_bonus = 100.0;
+ }
+
+ if (hit->fts_rank > 0)
+ {
+ match_bonus = MIN (500, 10.0 * hit->fts_rank);
+ }
+ else
+ {
+ match_bonus = 0.0;
+ }
+
+ hit->relevance = recent_bonus + proximity_bonus + match_bonus;
+ DEBUG ("Hit %s computed relevance %.2f (%.2f + %.2f + %.2f)", hit->uri, hit->relevance,
+ proximity_bonus, recent_bonus, match_bonus);
+
+ g_date_time_unref (now);
+ g_object_unref (query_location);
+}
+
+const char *
+nautilus_search_hit_get_uri (NautilusSearchHit *hit)
+{
+ return hit->uri;
+}
+
+gdouble
+nautilus_search_hit_get_relevance (NautilusSearchHit *hit)
+{
+ return hit->relevance;
+}
+
+const gchar *
+nautilus_search_hit_get_fts_snippet (NautilusSearchHit *hit)
+{
+ return hit->fts_snippet;
+}
+
+static void
+nautilus_search_hit_set_uri (NautilusSearchHit *hit,
+ const char *uri)
+{
+ g_free (hit->uri);
+ hit->uri = g_strdup (uri);
+}
+
+void
+nautilus_search_hit_set_fts_rank (NautilusSearchHit *hit,
+ gdouble rank)
+{
+ hit->fts_rank = rank;
+}
+
+void
+nautilus_search_hit_set_modification_time (NautilusSearchHit *hit,
+ GDateTime *date)
+{
+ if (hit->modification_time != NULL)
+ {
+ g_date_time_unref (hit->modification_time);
+ }
+ if (date != NULL)
+ {
+ hit->modification_time = g_date_time_ref (date);
+ }
+ else
+ {
+ hit->modification_time = NULL;
+ }
+}
+
+void
+nautilus_search_hit_set_access_time (NautilusSearchHit *hit,
+ GDateTime *date)
+{
+ if (hit->access_time != NULL)
+ {
+ g_date_time_unref (hit->access_time);
+ }
+ if (date != NULL)
+ {
+ hit->access_time = g_date_time_ref (date);
+ }
+ else
+ {
+ hit->access_time = NULL;
+ }
+}
+
+void
+nautilus_search_hit_set_creation_time (NautilusSearchHit *hit,
+ GDateTime *date)
+{
+ if (hit->creation_time != NULL)
+ {
+ g_date_time_unref (hit->creation_time);
+ }
+ if (date != NULL)
+ {
+ hit->creation_time = g_date_time_ref (date);
+ }
+ else
+ {
+ hit->creation_time = NULL;
+ }
+}
+
+void
+nautilus_search_hit_set_fts_snippet (NautilusSearchHit *hit,
+ const gchar *snippet)
+{
+ g_free (hit->fts_snippet);
+
+ hit->fts_snippet = g_strdup (snippet);
+}
+
+static void
+nautilus_search_hit_set_property (GObject *object,
+ guint arg_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ NautilusSearchHit *hit;
+
+ hit = NAUTILUS_SEARCH_HIT (object);
+
+ switch (arg_id)
+ {
+ case PROP_RELEVANCE:
+ {
+ hit->relevance = g_value_get_double (value);
+ }
+ break;
+
+ case PROP_FTS_RANK:
+ {
+ nautilus_search_hit_set_fts_rank (hit, g_value_get_double (value));
+ }
+ break;
+
+ case PROP_URI:
+ {
+ nautilus_search_hit_set_uri (hit, g_value_get_string (value));
+ }
+ break;
+
+ case PROP_MODIFICATION_TIME:
+ {
+ nautilus_search_hit_set_modification_time (hit, g_value_get_boxed (value));
+ }
+ break;
+
+ case PROP_ACCESS_TIME:
+ {
+ nautilus_search_hit_set_access_time (hit, g_value_get_boxed (value));
+ }
+ break;
+
+ case PROP_CREATION_TIME:
+ {
+ nautilus_search_hit_set_creation_time (hit, g_value_get_boxed (value));
+ }
+ break;
+
+ case PROP_FTS_SNIPPET:
+ {
+ g_free (hit->fts_snippet);
+ hit->fts_snippet = g_strdup (g_value_get_string (value));
+ }
+ break;
+
+ default:
+ {
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, arg_id, pspec);
+ }
+ break;
+ }
+}
+
+static void
+nautilus_search_hit_get_property (GObject *object,
+ guint arg_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ NautilusSearchHit *hit;
+
+ hit = NAUTILUS_SEARCH_HIT (object);
+
+ switch (arg_id)
+ {
+ case PROP_RELEVANCE:
+ {
+ g_value_set_double (value, hit->relevance);
+ }
+ break;
+
+ case PROP_FTS_RANK:
+ {
+ g_value_set_double (value, hit->fts_rank);
+ }
+ break;
+
+ case PROP_URI:
+ {
+ g_value_set_string (value, hit->uri);
+ }
+ break;
+
+ case PROP_MODIFICATION_TIME:
+ {
+ g_value_set_boxed (value, hit->modification_time);
+ }
+ break;
+
+ case PROP_ACCESS_TIME:
+ {
+ g_value_set_boxed (value, hit->access_time);
+ }
+ break;
+
+ case PROP_CREATION_TIME:
+ {
+ g_value_set_boxed (value, hit->creation_time);
+ }
+ break;
+
+ case PROP_FTS_SNIPPET:
+ {
+ g_value_set_string (value, hit->fts_snippet);
+ }
+ break;
+
+ default:
+ {
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, arg_id, pspec);
+ }
+ break;
+ }
+}
+
+static void
+nautilus_search_hit_finalize (GObject *object)
+{
+ NautilusSearchHit *hit = NAUTILUS_SEARCH_HIT (object);
+
+ g_free (hit->uri);
+
+ if (hit->access_time != NULL)
+ {
+ g_date_time_unref (hit->access_time);
+ }
+ if (hit->modification_time != NULL)
+ {
+ g_date_time_unref (hit->modification_time);
+ }
+ if (hit->creation_time != NULL)
+ {
+ g_date_time_unref (hit->creation_time);
+ }
+
+ g_free (hit->fts_snippet);
+
+ G_OBJECT_CLASS (nautilus_search_hit_parent_class)->finalize (object);
+}
+
+static void
+nautilus_search_hit_class_init (NautilusSearchHitClass *class)
+{
+ GObjectClass *object_class;
+
+ object_class = (GObjectClass *) class;
+
+ object_class->finalize = nautilus_search_hit_finalize;
+ object_class->get_property = nautilus_search_hit_get_property;
+ object_class->set_property = nautilus_search_hit_set_property;
+
+ g_object_class_install_property (object_class,
+ PROP_URI,
+ g_param_spec_string ("uri",
+ "URI",
+ "URI",
+ NULL,
+ G_PARAM_CONSTRUCT_ONLY | G_PARAM_WRITABLE | G_PARAM_READABLE));
+ g_object_class_install_property (object_class,
+ PROP_MODIFICATION_TIME,
+ g_param_spec_boxed ("modification-time",
+ "Modification time",
+ "Modification time",
+ G_TYPE_DATE_TIME,
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT));
+ g_object_class_install_property (object_class,
+ PROP_ACCESS_TIME,
+ g_param_spec_boxed ("access-time",
+ "acess time",
+ "access time",
+ G_TYPE_DATE_TIME,
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT));
+
+ g_object_class_install_property (object_class,
+ PROP_CREATION_TIME,
+ g_param_spec_boxed ("creation-time",
+ "creation time",
+ "creation time",
+ G_TYPE_DATE_TIME,
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT));
+ g_object_class_install_property (object_class,
+ PROP_RELEVANCE,
+ g_param_spec_double ("relevance",
+ NULL,
+ NULL,
+ -G_MAXDOUBLE, G_MAXDOUBLE,
+ 0,
+ G_PARAM_READWRITE));
+ g_object_class_install_property (object_class,
+ PROP_FTS_RANK,
+ g_param_spec_double ("fts-rank",
+ NULL,
+ NULL,
+ -G_MAXDOUBLE, G_MAXDOUBLE,
+ 0,
+ G_PARAM_READWRITE));
+ g_object_class_install_property (object_class,
+ PROP_FTS_SNIPPET,
+ g_param_spec_string ("fts-snippet",
+ "fts-snippet",
+ "fts-snippet",
+ NULL,
+ G_PARAM_READWRITE));
+}
+
+static void
+nautilus_search_hit_init (NautilusSearchHit *hit)
+{
+ hit = G_TYPE_INSTANCE_GET_PRIVATE (hit,
+ NAUTILUS_TYPE_SEARCH_HIT,
+ NautilusSearchHit);
+}
+
+NautilusSearchHit *
+nautilus_search_hit_new (const char *uri)
+{
+ NautilusSearchHit *hit;
+
+ hit = g_object_new (NAUTILUS_TYPE_SEARCH_HIT,
+ "uri", uri,
+ NULL);
+
+ return hit;
+}
diff --git a/src/nautilus-search-hit.h b/src/nautilus-search-hit.h
new file mode 100644
index 0000000..ec07465
--- /dev/null
+++ b/src/nautilus-search-hit.h
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2012 Red Hat, Inc.
+ *
+ * Nautilus is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * Nautilus is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; see the file COPYING. If not,
+ * see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#pragma once
+
+#include <glib-object.h>
+#include "nautilus-query.h"
+
+G_BEGIN_DECLS
+
+#define NAUTILUS_TYPE_SEARCH_HIT (nautilus_search_hit_get_type ())
+
+G_DECLARE_FINAL_TYPE (NautilusSearchHit, nautilus_search_hit, NAUTILUS, SEARCH_HIT, GObject);
+
+NautilusSearchHit * nautilus_search_hit_new (const char *uri);
+
+void nautilus_search_hit_set_fts_rank (NautilusSearchHit *hit,
+ gdouble fts_rank);
+void nautilus_search_hit_set_modification_time (NautilusSearchHit *hit,
+ GDateTime *date);
+void nautilus_search_hit_set_access_time (NautilusSearchHit *hit,
+ GDateTime *date);
+void nautilus_search_hit_set_creation_time (NautilusSearchHit *hit,
+ GDateTime *date);
+void nautilus_search_hit_set_fts_snippet (NautilusSearchHit *hit,
+ const gchar *snippet);
+void nautilus_search_hit_compute_scores (NautilusSearchHit *hit,
+ NautilusQuery *query);
+
+const char * nautilus_search_hit_get_uri (NautilusSearchHit *hit);
+gdouble nautilus_search_hit_get_relevance (NautilusSearchHit *hit);
+const gchar * nautilus_search_hit_get_fts_snippet (NautilusSearchHit *hit);
+
+G_END_DECLS
diff --git a/src/nautilus-search-popover.c b/src/nautilus-search-popover.c
new file mode 100644
index 0000000..e8e3136
--- /dev/null
+++ b/src/nautilus-search-popover.c
@@ -0,0 +1,1092 @@
+/* nautilus-search-popover.c
+ *
+ * Copyright (C) 2015 Georges Basile Stavracas Neto <georges.stavracas@gmail.com>
+ *
+ * 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 3 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/>.
+ */
+
+#include "nautilus-enum-types.h"
+#include "nautilus-search-popover.h"
+#include "nautilus-mime-actions.h"
+
+#include <glib/gi18n.h>
+#include "nautilus-file.h"
+#include "nautilus-ui-utilities.h"
+#include "nautilus-global-preferences.h"
+
+ #define SEARCH_FILTER_MAX_YEARS 5
+
+struct _NautilusSearchPopover
+{
+ GtkPopover parent;
+
+ GtkWidget *around_revealer;
+ GtkWidget *around_stack;
+ GtkWidget *calendar;
+ GtkWidget *clear_date_button;
+ GtkWidget *dates_listbox;
+ GtkWidget *date_entry;
+ GtkWidget *date_stack;
+ GtkWidget *select_date_button;
+ GtkWidget *select_date_button_label;
+ GtkWidget *type_label;
+ GtkWidget *type_listbox;
+ GtkWidget *type_stack;
+ GtkWidget *last_used_button;
+ GtkWidget *last_modified_button;
+ GtkWidget *created_button;
+ GtkWidget *full_text_search_button;
+ GtkWidget *filename_search_button;
+
+ NautilusQuery *query;
+ GtkSingleSelection *other_types_model;
+
+ gboolean fts_enabled;
+};
+
+static void show_date_selection_widgets (NautilusSearchPopover *popover,
+ gboolean visible);
+
+static void show_other_types_dialog (NautilusSearchPopover *popover);
+
+static void update_date_label (NautilusSearchPopover *popover,
+ GPtrArray *date_range);
+
+G_DEFINE_TYPE (NautilusSearchPopover, nautilus_search_popover, GTK_TYPE_POPOVER)
+
+enum
+{
+ PROP_0,
+ PROP_QUERY,
+ PROP_FTS_ENABLED,
+ LAST_PROP
+};
+
+enum
+{
+ MIME_TYPE,
+ TIME_TYPE,
+ DATE_RANGE,
+ LAST_SIGNAL
+};
+
+static guint signals[LAST_SIGNAL];
+
+
+/* Callbacks */
+
+static void
+calendar_day_selected (GtkCalendar *calendar,
+ NautilusSearchPopover *popover)
+{
+ GDateTime *date;
+ GPtrArray *date_range;
+
+ date = gtk_calendar_get_date (calendar);
+
+ date_range = g_ptr_array_new_full (2, (GDestroyNotify) g_date_time_unref);
+ g_ptr_array_add (date_range, g_date_time_ref (date));
+ g_ptr_array_add (date_range, g_date_time_ref (date));
+ update_date_label (popover, date_range);
+ g_signal_emit_by_name (popover, "date-range", date_range);
+
+ g_ptr_array_unref (date_range);
+ g_date_time_unref (date);
+}
+
+/* Range on dates are partially implemented. For now just use it for differentation
+ * between a exact day or a range of a first day until now.
+ */
+static void
+setup_date (NautilusSearchPopover *popover,
+ NautilusQuery *query)
+{
+ GPtrArray *date_range;
+ GDateTime *date_initial;
+
+ date_range = nautilus_query_get_date_range (query);
+
+ if (date_range)
+ {
+ date_initial = g_ptr_array_index (date_range, 0);
+
+ g_signal_handlers_block_by_func (popover->calendar, calendar_day_selected, popover);
+
+ gtk_calendar_select_day (GTK_CALENDAR (popover->calendar), date_initial);
+
+ update_date_label (popover, date_range);
+
+ g_signal_handlers_unblock_by_func (popover->calendar, calendar_day_selected, popover);
+ }
+}
+
+static void
+query_date_changed (GObject *object,
+ GParamSpec *pspec,
+ NautilusSearchPopover *popover)
+{
+ setup_date (popover, NAUTILUS_QUERY (object));
+}
+
+static void
+clear_date_button_clicked (GtkButton *button,
+ NautilusSearchPopover *popover)
+{
+ nautilus_search_popover_reset_date_range (popover);
+}
+
+static void
+date_entry_activate (GtkEntry *entry,
+ NautilusSearchPopover *popover)
+{
+ if (gtk_entry_get_text_length (entry) > 0)
+ {
+ GDateTime *now;
+ GDateTime *date_time;
+ GDate *date;
+
+ date = g_date_new ();
+ g_date_set_parse (date, gtk_editable_get_text (GTK_EDITABLE (entry)));
+
+ /* Invalid date silently does nothing */
+ if (!g_date_valid (date))
+ {
+ g_date_free (date);
+ return;
+ }
+
+ now = g_date_time_new_now_local ();
+ date_time = g_date_time_new_local (g_date_get_year (date),
+ g_date_get_month (date),
+ g_date_get_day (date),
+ 0,
+ 0,
+ 0);
+
+ /* Future dates also silently fails */
+ if (g_date_time_compare (date_time, now) != 1)
+ {
+ GPtrArray *date_range;
+
+ date_range = g_ptr_array_new_full (2, (GDestroyNotify) g_date_time_unref);
+ g_ptr_array_add (date_range, g_date_time_ref (date_time));
+ g_ptr_array_add (date_range, g_date_time_ref (date_time));
+ update_date_label (popover, date_range);
+ show_date_selection_widgets (popover, FALSE);
+ g_signal_emit_by_name (popover, "date-range", date_range);
+
+ g_ptr_array_unref (date_range);
+ }
+
+ g_date_time_unref (now);
+ g_date_time_unref (date_time);
+ g_date_free (date);
+ }
+}
+
+static void
+dates_listbox_row_activated (GtkListBox *listbox,
+ GtkListBoxRow *row,
+ NautilusSearchPopover *popover)
+{
+ GDateTime *date;
+ GDateTime *now;
+ GPtrArray *date_range = NULL;
+
+ now = g_date_time_new_now_local ();
+ date = g_object_get_data (G_OBJECT (row), "date");
+ if (date)
+ {
+ date_range = g_ptr_array_new_full (2, (GDestroyNotify) g_date_time_unref);
+ g_ptr_array_add (date_range, g_date_time_ref (date));
+ g_ptr_array_add (date_range, g_date_time_ref (now));
+ }
+ update_date_label (popover, date_range);
+ show_date_selection_widgets (popover, FALSE);
+ g_signal_emit_by_name (popover, "date-range", date_range);
+
+ if (date_range)
+ {
+ g_ptr_array_unref (date_range);
+ }
+ g_date_time_unref (now);
+}
+
+static void
+listbox_header_func (GtkListBoxRow *row,
+ GtkListBoxRow *before,
+ NautilusSearchPopover *popover)
+{
+ gboolean show_separator;
+
+ show_separator = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (row), "show-separator"));
+
+ if (show_separator)
+ {
+ GtkWidget *separator;
+
+ separator = gtk_separator_new (GTK_ORIENTATION_HORIZONTAL);
+ gtk_widget_show (separator);
+
+ gtk_list_box_row_set_header (row, separator);
+ }
+}
+
+static void
+select_date_button_clicked (GtkButton *button,
+ NautilusSearchPopover *popover)
+{
+ /* Hide the type selection widgets when date selection
+ * widgets are shown.
+ */
+ gtk_stack_set_visible_child_name (GTK_STACK (popover->type_stack), "type-button");
+
+ show_date_selection_widgets (popover, TRUE);
+}
+
+static void
+select_type_button_clicked (GtkButton *button,
+ NautilusSearchPopover *popover)
+{
+ GtkListBoxRow *selected_row;
+
+ selected_row = gtk_list_box_get_selected_row (GTK_LIST_BOX (popover->type_listbox));
+
+ gtk_stack_set_visible_child_name (GTK_STACK (popover->type_stack), "type-list");
+ if (selected_row != NULL)
+ {
+ gtk_widget_grab_focus (GTK_WIDGET (selected_row));
+ }
+
+ /* Hide the date selection widgets when the type selection
+ * listbox is shown.
+ */
+ show_date_selection_widgets (popover, FALSE);
+}
+
+static void
+toggle_calendar_icon_clicked (GtkEntry *entry,
+ GtkEntryIconPosition position,
+ NautilusSearchPopover *popover)
+{
+ const gchar *current_visible_child;
+ const gchar *child;
+ const gchar *icon_name;
+ const gchar *tooltip;
+
+ current_visible_child = gtk_stack_get_visible_child_name (GTK_STACK (popover->around_stack));
+
+ if (g_strcmp0 (current_visible_child, "date-list") == 0)
+ {
+ child = "date-calendar";
+ icon_name = "view-list-symbolic";
+ tooltip = _("Show a list to select the date");
+ }
+ else
+ {
+ child = "date-list";
+ icon_name = "x-office-calendar-symbolic";
+ tooltip = _("Show a calendar to select the date");
+ }
+
+ gtk_stack_set_visible_child_name (GTK_STACK (popover->around_stack), child);
+ gtk_entry_set_icon_from_icon_name (entry, GTK_ENTRY_ICON_SECONDARY, icon_name);
+ gtk_entry_set_icon_tooltip_text (entry, GTK_ENTRY_ICON_SECONDARY, tooltip);
+}
+
+static void
+types_listbox_row_activated (GtkListBox *listbox,
+ GtkListBoxRow *row,
+ NautilusSearchPopover *popover)
+{
+ gint group;
+
+ group = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (row), "mimetype-group"));
+
+ /* The -1 group stands for the "Other Types" group, for which
+ * we should show the mimetype dialog.
+ */
+ if (group == -1)
+ {
+ show_other_types_dialog (popover);
+ }
+ else
+ {
+ gtk_label_set_label (GTK_LABEL (popover->type_label),
+ nautilus_mime_types_group_get_name (group));
+
+ g_signal_emit_by_name (popover, "mime-type", group, NULL);
+ }
+
+ gtk_stack_set_visible_child_name (GTK_STACK (popover->type_stack), "type-button");
+}
+
+static void
+search_time_type_changed (GtkCheckButton *button,
+ NautilusSearchPopover *popover)
+{
+ NautilusQuerySearchType type = -1;
+
+ if (gtk_check_button_get_active (GTK_CHECK_BUTTON (popover->last_modified_button)))
+ {
+ type = NAUTILUS_QUERY_SEARCH_TYPE_LAST_MODIFIED;
+ }
+ else if (gtk_check_button_get_active (GTK_CHECK_BUTTON (popover->last_used_button)))
+ {
+ type = NAUTILUS_QUERY_SEARCH_TYPE_LAST_ACCESS;
+ }
+ else
+ {
+ type = NAUTILUS_QUERY_SEARCH_TYPE_CREATED;
+ }
+
+ g_settings_set_enum (nautilus_preferences, "search-filter-time-type", type);
+
+ g_signal_emit_by_name (popover, "time-type", type, NULL);
+}
+
+static void
+search_fts_mode_changed (GtkToggleButton *button,
+ NautilusSearchPopover *popover)
+{
+ if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (popover->full_text_search_button)) &&
+ popover->fts_enabled == FALSE)
+ {
+ popover->fts_enabled = TRUE;
+ g_settings_set_boolean (nautilus_preferences, NAUTILUS_PREFERENCES_FTS_ENABLED, TRUE);
+ g_object_notify (G_OBJECT (popover), "fts-enabled");
+ }
+ else if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (popover->filename_search_button)) &&
+ popover->fts_enabled == TRUE)
+ {
+ popover->fts_enabled = FALSE;
+ g_settings_set_boolean (nautilus_preferences, NAUTILUS_PREFERENCES_FTS_ENABLED, FALSE);
+ g_object_notify (G_OBJECT (popover), "fts-enabled");
+ }
+}
+
+/* Auxiliary methods */
+
+static GtkWidget *
+create_row_for_label (const gchar *text,
+ gboolean show_separator)
+{
+ GtkWidget *row;
+ GtkWidget *label;
+
+ row = gtk_list_box_row_new ();
+
+ g_object_set_data (G_OBJECT (row), "show-separator", GINT_TO_POINTER (show_separator));
+
+ label = g_object_new (GTK_TYPE_LABEL,
+ "label", text,
+ "hexpand", TRUE,
+ "xalign", 0.0,
+ "margin-start", 6,
+ NULL);
+
+ gtk_list_box_row_set_child (GTK_LIST_BOX_ROW (row), label);
+ gtk_widget_show (row);
+
+ return row;
+}
+
+static void
+fill_fuzzy_dates_listbox (NautilusSearchPopover *popover)
+{
+ GDateTime *maximum_dt, *now;
+ GtkWidget *row;
+ GDateTime *current_date;
+ GPtrArray *date_range;
+ gint days, max_days;
+
+ days = 1;
+ maximum_dt = g_date_time_new_from_unix_local (0);
+ now = g_date_time_new_now_local ();
+ max_days = SEARCH_FILTER_MAX_YEARS * 365;
+
+ /* Add the no date filter element first */
+ row = create_row_for_label (_("Any time"), TRUE);
+ gtk_list_box_insert (GTK_LIST_BOX (popover->dates_listbox), row, -1);
+
+ /* This is a tricky loop. The main intention here is that each
+ * timeslice (day, week, month) have 2 or 3 entries.
+ *
+ * For the first appearance of each timeslice, there is made a
+ * check in order to be sure that there is no offset added to days.
+ */
+ while (days <= max_days)
+ {
+ gchar *label;
+ gint normalized;
+ gint step;
+
+ if (days < 7)
+ {
+ /* days */
+ normalized = days;
+ step = 2;
+ }
+ else if (days < 30)
+ {
+ /* weeks */
+ normalized = days / 7;
+ if (normalized == 1)
+ {
+ days = 7;
+ }
+ step = 7;
+ }
+ else if (days < 365)
+ {
+ /* months */
+ normalized = days / 30;
+ if (normalized == 1)
+ {
+ days = 30;
+ }
+ step = 90;
+ }
+ else
+ {
+ /* years */
+ normalized = days / 365;
+ if (normalized == 1)
+ {
+ days = 365;
+ }
+ step = 365;
+ }
+
+ current_date = g_date_time_add_days (now, -days);
+ date_range = g_ptr_array_new_full (2, (GDestroyNotify) g_date_time_unref);
+ g_ptr_array_add (date_range, g_date_time_ref (current_date));
+ g_ptr_array_add (date_range, g_date_time_ref (now));
+ label = get_text_for_date_range (date_range, FALSE);
+ row = create_row_for_label (label, normalized == 1);
+ g_object_set_data_full (G_OBJECT (row),
+ "date",
+ g_date_time_ref (current_date),
+ (GDestroyNotify) g_date_time_unref);
+
+ gtk_list_box_insert (GTK_LIST_BOX (popover->dates_listbox), row, -1);
+
+ g_free (label);
+ g_date_time_unref (current_date);
+ g_ptr_array_unref (date_range);
+
+ days += step;
+ }
+
+ g_date_time_unref (maximum_dt);
+ g_date_time_unref (now);
+}
+
+static void
+fill_types_listbox (NautilusSearchPopover *popover)
+{
+ GtkWidget *row;
+ int i;
+ gint n_groups;
+
+ n_groups = nautilus_mime_types_get_number_of_groups ();
+ /* Mimetypes */
+ for (i = 0; i < n_groups; i++)
+ {
+ /* On the third row, which is right below "Folders", there should be an
+ * separator to logically group the types.
+ */
+ row = create_row_for_label (nautilus_mime_types_group_get_name (i), i == 3);
+ g_object_set_data (G_OBJECT (row), "mimetype-group", GINT_TO_POINTER (i));
+
+ gtk_list_box_insert (GTK_LIST_BOX (popover->type_listbox), row, -1);
+ }
+
+ /* Other types */
+ row = create_row_for_label (_("Other Type…"), TRUE);
+ g_object_set_data (G_OBJECT (row), "mimetype-group", GINT_TO_POINTER (-1));
+ gtk_list_box_insert (GTK_LIST_BOX (popover->type_listbox), row, -1);
+}
+
+static void
+show_date_selection_widgets (NautilusSearchPopover *popover,
+ gboolean visible)
+{
+ gtk_stack_set_visible_child_name (GTK_STACK (popover->date_stack),
+ visible ? "date-entry" : "date-button");
+ gtk_stack_set_visible_child_name (GTK_STACK (popover->around_stack), "date-list");
+ gtk_entry_set_icon_from_icon_name (GTK_ENTRY (popover->date_entry),
+ GTK_ENTRY_ICON_SECONDARY,
+ "x-office-calendar-symbolic");
+
+ gtk_widget_set_visible (popover->around_revealer, visible);
+
+ gtk_revealer_set_reveal_child (GTK_REVEALER (popover->around_revealer), visible);
+}
+
+static void
+on_other_types_dialog_response (GtkDialog *dialog,
+ gint response_id,
+ NautilusSearchPopover *popover)
+{
+ if (response_id == GTK_RESPONSE_OK)
+ {
+ GtkStringObject *item;
+ const char *mimetype;
+ g_autofree gchar *description = NULL;
+
+ item = gtk_single_selection_get_selected_item (popover->other_types_model);
+ mimetype = gtk_string_object_get_string (item);
+ description = g_content_type_get_description (mimetype);
+
+ gtk_label_set_label (GTK_LABEL (popover->type_label), description);
+
+ g_signal_emit_by_name (popover, "mime-type", -1, mimetype);
+
+ gtk_stack_set_visible_child_name (GTK_STACK (popover->type_stack), "type-button");
+ }
+
+ g_clear_object (&popover->other_types_model);
+ gtk_window_destroy (GTK_WINDOW (dialog));
+}
+
+static void
+on_other_types_activate (GtkListView *self,
+ guint position,
+ gpointer user_data)
+{
+ GtkDialog *dialog = GTK_DIALOG (user_data);
+
+ gtk_dialog_response (dialog, GTK_RESPONSE_OK);
+}
+
+static void
+on_other_types_bind (GtkSignalListItemFactory *factory,
+ GtkListItem *listitem,
+ gpointer user_data)
+{
+ GtkLabel *label;
+ GtkStringObject *item;
+ g_autofree gchar *description = NULL;
+
+ label = GTK_LABEL (gtk_list_item_get_child (listitem));
+ item = GTK_STRING_OBJECT (gtk_list_item_get_item (listitem));
+
+ description = g_content_type_get_description (gtk_string_object_get_string (item));
+ gtk_label_set_text (label, description);
+}
+
+static void
+on_other_types_setup (GtkSignalListItemFactory *factory,
+ GtkListItem *listitem,
+ gpointer user_data)
+{
+ GtkWidget *label;
+
+ label = gtk_label_new (NULL);
+ gtk_widget_set_halign (label, GTK_ALIGN_START);
+ gtk_widget_set_margin_start (label, 12);
+ gtk_widget_set_margin_end (label, 12);
+ gtk_widget_set_margin_top (label, 6);
+ gtk_widget_set_margin_bottom (label, 6);
+ gtk_list_item_set_child (listitem, label);
+}
+
+static gchar *
+join_type_and_description (GtkStringObject *object,
+ gpointer user_data)
+{
+ const gchar *content_type = gtk_string_object_get_string (object);
+ g_autofree gchar *description = g_content_type_get_description (content_type);
+
+ return g_strdup_printf ("%s %s", content_type, description);
+}
+
+static void
+show_other_types_dialog (NautilusSearchPopover *popover)
+{
+ GtkStringList *string_model;
+ GtkStringSorter *sorter;
+ GtkSortListModel *sort_model;
+ GtkStringFilter *filter;
+ GtkFilterListModel *filter_model;
+ g_autoptr (GList) mime_infos = NULL;
+ GtkWidget *dialog;
+ GtkWidget *content_area;
+ GtkWidget *search_entry;
+ GtkWidget *scrolled;
+ GtkListItemFactory *factory;
+ GtkWidget *listview;
+ GtkRoot *toplevel;
+
+ string_model = gtk_string_list_new (NULL);
+ mime_infos = g_content_types_get_registered ();
+ for (GList *l = mime_infos; l != NULL; l = l->next)
+ {
+ gtk_string_list_append (string_model, l->data);
+ }
+ sorter = gtk_string_sorter_new (gtk_property_expression_new (GTK_TYPE_STRING_OBJECT, NULL, "string"));
+ sort_model = gtk_sort_list_model_new (G_LIST_MODEL (string_model), GTK_SORTER (sorter));
+ filter = gtk_string_filter_new (gtk_cclosure_expression_new (G_TYPE_STRING,
+ NULL, 0, NULL,
+ G_CALLBACK (join_type_and_description),
+ NULL, NULL));
+ filter_model = gtk_filter_list_model_new (G_LIST_MODEL (sort_model), GTK_FILTER (filter));
+ popover->other_types_model = gtk_single_selection_new (G_LIST_MODEL (filter_model));
+
+ toplevel = gtk_widget_get_root (GTK_WIDGET (popover));
+ dialog = gtk_dialog_new_with_buttons (_("Select type"),
+ GTK_WINDOW (toplevel),
+ GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT | GTK_DIALOG_USE_HEADER_BAR,
+ _("_Cancel"), GTK_RESPONSE_CANCEL,
+ _("Select"), GTK_RESPONSE_OK,
+ NULL);
+ gtk_dialog_set_default_response (GTK_DIALOG (dialog), GTK_RESPONSE_OK);
+ gtk_window_set_default_size (GTK_WINDOW (dialog), 400, 600);
+
+ /* If there are 0 results, make action insensitive */
+ g_object_bind_property (filter_model,
+ "n-items",
+ gtk_dialog_get_widget_for_response (GTK_DIALOG (dialog), GTK_RESPONSE_OK),
+ "sensitive",
+ G_BINDING_DEFAULT);
+
+ content_area = gtk_dialog_get_content_area (GTK_DIALOG (dialog));
+
+ search_entry = gtk_search_entry_new ();
+ gtk_search_entry_set_key_capture_widget (GTK_SEARCH_ENTRY (search_entry), content_area);
+ g_object_bind_property (search_entry, "text", filter, "search", G_BINDING_SYNC_CREATE);
+ gtk_box_append (GTK_BOX (content_area), search_entry);
+ gtk_widget_set_margin_start (search_entry, 12);
+ gtk_widget_set_margin_end (search_entry, 12);
+ gtk_widget_set_margin_top (search_entry, 6);
+ gtk_widget_set_margin_bottom (search_entry, 6);
+
+ gtk_box_append (GTK_BOX (content_area), gtk_separator_new (GTK_ORIENTATION_VERTICAL));
+
+ scrolled = gtk_scrolled_window_new ();
+ gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolled),
+ GTK_POLICY_AUTOMATIC,
+ GTK_POLICY_AUTOMATIC);
+
+ gtk_widget_set_vexpand (scrolled, TRUE);
+ gtk_box_append (GTK_BOX (content_area), scrolled);
+
+ factory = gtk_signal_list_item_factory_new ();
+ g_signal_connect (factory, "setup", G_CALLBACK (on_other_types_setup), NULL);
+ g_signal_connect (factory, "bind", G_CALLBACK (on_other_types_bind), NULL);
+
+ listview = gtk_list_view_new (GTK_SELECTION_MODEL (g_object_ref (popover->other_types_model)),
+ factory);
+ g_signal_connect (listview, "activate", G_CALLBACK (on_other_types_activate), dialog);
+
+ gtk_scrolled_window_set_child (GTK_SCROLLED_WINDOW (scrolled), listview);
+
+ g_signal_connect (dialog, "response", G_CALLBACK (on_other_types_dialog_response), popover);
+ gtk_widget_show (dialog);
+}
+
+static void
+update_date_label (NautilusSearchPopover *popover,
+ GPtrArray *date_range)
+{
+ if (date_range)
+ {
+ gint days;
+ GDateTime *initial_date;
+ GDateTime *end_date;
+ GDateTime *now;
+ gchar *label;
+
+ now = g_date_time_new_now_local ();
+ initial_date = g_ptr_array_index (date_range, 0);
+ end_date = g_ptr_array_index (date_range, 0);
+ days = g_date_time_difference (end_date, initial_date) / G_TIME_SPAN_DAY;
+
+ label = get_text_for_date_range (date_range, TRUE);
+
+ gtk_editable_set_text (GTK_EDITABLE (popover->date_entry), days < 1 ? label : "");
+
+ gtk_widget_show (popover->clear_date_button);
+ gtk_label_set_label (GTK_LABEL (popover->select_date_button_label), label);
+
+ g_date_time_unref (now);
+ g_free (label);
+ }
+ else
+ {
+ gtk_label_set_label (GTK_LABEL (popover->select_date_button_label),
+ _("Select Dates…"));
+ gtk_editable_set_text (GTK_EDITABLE (popover->date_entry), "");
+ gtk_widget_hide (popover->clear_date_button);
+ }
+}
+
+void
+nautilus_search_popover_set_fts_sensitive (NautilusSearchPopover *popover,
+ gboolean sensitive)
+{
+ gtk_widget_set_sensitive (popover->full_text_search_button, sensitive);
+ gtk_widget_set_sensitive (popover->filename_search_button, sensitive);
+}
+
+static void
+nautilus_search_popover_closed (GtkPopover *popover)
+{
+ NautilusSearchPopover *self = NAUTILUS_SEARCH_POPOVER (popover);
+ GDateTime *now;
+
+ /* Always switch back to the initial states */
+ gtk_stack_set_visible_child_name (GTK_STACK (self->type_stack), "type-button");
+ show_date_selection_widgets (self, FALSE);
+
+ /* If we're closing an ongoing query, the popover must not
+ * clear the current settings.
+ */
+ if (self->query)
+ {
+ return;
+ }
+
+ now = g_date_time_new_now_local ();
+
+ /* Reselect today at the calendar */
+ g_signal_handlers_block_by_func (self->calendar, calendar_day_selected, self);
+
+ gtk_calendar_select_day (GTK_CALENDAR (self->calendar), now);
+
+ g_signal_handlers_unblock_by_func (self->calendar, calendar_day_selected, self);
+}
+
+static void
+nautilus_search_popover_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ NautilusSearchPopover *self;
+
+ self = NAUTILUS_SEARCH_POPOVER (object);
+
+ switch (prop_id)
+ {
+ case PROP_QUERY:
+ {
+ g_value_set_object (value, self->query);
+ }
+ break;
+
+ case PROP_FTS_ENABLED:
+ {
+ g_value_set_boolean (value, self->fts_enabled);
+ }
+ break;
+
+ default:
+ {
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+ }
+}
+
+static void
+nautilus_search_popover_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ NautilusSearchPopover *self;
+
+ self = NAUTILUS_SEARCH_POPOVER (object);
+
+ switch (prop_id)
+ {
+ case PROP_QUERY:
+ {
+ nautilus_search_popover_set_query (self, g_value_get_object (value));
+ }
+ break;
+
+ case PROP_FTS_ENABLED:
+ {
+ self->fts_enabled = g_value_get_boolean (value);
+ }
+ break;
+
+ default:
+ {
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+ }
+}
+
+
+static void
+nautilus_search_popover_class_init (NautilusSearchPopoverClass *klass)
+{
+ GtkPopoverClass *popover_class = GTK_POPOVER_CLASS (klass);
+ GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->get_property = nautilus_search_popover_get_property;
+ object_class->set_property = nautilus_search_popover_set_property;
+
+ popover_class->closed = nautilus_search_popover_closed;
+
+ signals[DATE_RANGE] = g_signal_new ("date-range",
+ NAUTILUS_TYPE_SEARCH_POPOVER,
+ G_SIGNAL_RUN_LAST,
+ 0,
+ NULL,
+ NULL,
+ g_cclosure_marshal_generic,
+ G_TYPE_NONE,
+ 1,
+ G_TYPE_PTR_ARRAY);
+
+ signals[MIME_TYPE] = g_signal_new ("mime-type",
+ NAUTILUS_TYPE_SEARCH_POPOVER,
+ G_SIGNAL_RUN_LAST,
+ 0,
+ NULL,
+ NULL,
+ g_cclosure_marshal_generic,
+ G_TYPE_NONE,
+ 2,
+ G_TYPE_INT,
+ G_TYPE_STRING);
+
+ signals[TIME_TYPE] = g_signal_new ("time-type",
+ NAUTILUS_TYPE_SEARCH_POPOVER,
+ G_SIGNAL_RUN_LAST,
+ 0,
+ NULL,
+ NULL,
+ g_cclosure_marshal_generic,
+ G_TYPE_NONE,
+ 1,
+ G_TYPE_INT);
+
+ /**
+ * NautilusSearchPopover::query:
+ *
+ * The current #NautilusQuery being edited.
+ */
+ g_object_class_install_property (object_class,
+ PROP_QUERY,
+ g_param_spec_object ("query",
+ "Query of the popover",
+ "The current query being edited",
+ NAUTILUS_TYPE_QUERY,
+ G_PARAM_READWRITE));
+
+ g_object_class_install_property (object_class,
+ PROP_FTS_ENABLED,
+ g_param_spec_boolean ("fts-enabled",
+ "fts enabled",
+ "fts enabled",
+ FALSE,
+ G_PARAM_READWRITE));
+
+ gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/nautilus/ui/nautilus-search-popover.ui");
+
+ gtk_widget_class_bind_template_child (widget_class, NautilusSearchPopover, around_revealer);
+ gtk_widget_class_bind_template_child (widget_class, NautilusSearchPopover, around_stack);
+ gtk_widget_class_bind_template_child (widget_class, NautilusSearchPopover, clear_date_button);
+ gtk_widget_class_bind_template_child (widget_class, NautilusSearchPopover, calendar);
+ gtk_widget_class_bind_template_child (widget_class, NautilusSearchPopover, dates_listbox);
+ gtk_widget_class_bind_template_child (widget_class, NautilusSearchPopover, date_entry);
+ gtk_widget_class_bind_template_child (widget_class, NautilusSearchPopover, date_stack);
+ gtk_widget_class_bind_template_child (widget_class, NautilusSearchPopover, select_date_button);
+ gtk_widget_class_bind_template_child (widget_class, NautilusSearchPopover, select_date_button_label);
+ gtk_widget_class_bind_template_child (widget_class, NautilusSearchPopover, type_label);
+ gtk_widget_class_bind_template_child (widget_class, NautilusSearchPopover, type_listbox);
+ gtk_widget_class_bind_template_child (widget_class, NautilusSearchPopover, type_stack);
+ gtk_widget_class_bind_template_child (widget_class, NautilusSearchPopover, last_used_button);
+ gtk_widget_class_bind_template_child (widget_class, NautilusSearchPopover, last_modified_button);
+ gtk_widget_class_bind_template_child (widget_class, NautilusSearchPopover, created_button);
+ gtk_widget_class_bind_template_child (widget_class, NautilusSearchPopover, full_text_search_button);
+ gtk_widget_class_bind_template_child (widget_class, NautilusSearchPopover, filename_search_button);
+
+ gtk_widget_class_bind_template_callback (widget_class, calendar_day_selected);
+ gtk_widget_class_bind_template_callback (widget_class, clear_date_button_clicked);
+ gtk_widget_class_bind_template_callback (widget_class, date_entry_activate);
+ gtk_widget_class_bind_template_callback (widget_class, dates_listbox_row_activated);
+ gtk_widget_class_bind_template_callback (widget_class, select_date_button_clicked);
+ gtk_widget_class_bind_template_callback (widget_class, select_type_button_clicked);
+ gtk_widget_class_bind_template_callback (widget_class, toggle_calendar_icon_clicked);
+ gtk_widget_class_bind_template_callback (widget_class, types_listbox_row_activated);
+ gtk_widget_class_bind_template_callback (widget_class, search_time_type_changed);
+ gtk_widget_class_bind_template_callback (widget_class, search_fts_mode_changed);
+}
+
+static void
+nautilus_search_popover_init (NautilusSearchPopover *self)
+{
+ NautilusQuerySearchType filter_time_type;
+
+ gtk_widget_init_template (GTK_WIDGET (self));
+
+ /* Fuzzy dates listbox */
+ gtk_list_box_set_header_func (GTK_LIST_BOX (self->dates_listbox),
+ (GtkListBoxUpdateHeaderFunc) listbox_header_func,
+ self,
+ NULL);
+
+ fill_fuzzy_dates_listbox (self);
+
+ /* Types listbox */
+ gtk_list_box_set_header_func (GTK_LIST_BOX (self->type_listbox),
+ (GtkListBoxUpdateHeaderFunc) listbox_header_func,
+ self,
+ NULL);
+
+ fill_types_listbox (self);
+
+ gtk_list_box_select_row (GTK_LIST_BOX (self->type_listbox),
+ gtk_list_box_get_row_at_index (GTK_LIST_BOX (self->type_listbox), 0));
+
+ filter_time_type = g_settings_get_enum (nautilus_preferences, "search-filter-time-type");
+ if (filter_time_type == NAUTILUS_QUERY_SEARCH_TYPE_LAST_MODIFIED)
+ {
+ gtk_check_button_set_active (GTK_CHECK_BUTTON (self->last_modified_button), TRUE);
+ gtk_check_button_set_active (GTK_CHECK_BUTTON (self->last_used_button), FALSE);
+ gtk_check_button_set_active (GTK_CHECK_BUTTON (self->created_button), FALSE);
+ }
+ else if (filter_time_type == NAUTILUS_QUERY_SEARCH_TYPE_LAST_ACCESS)
+ {
+ gtk_check_button_set_active (GTK_CHECK_BUTTON (self->last_modified_button), FALSE);
+ gtk_check_button_set_active (GTK_CHECK_BUTTON (self->last_used_button), TRUE);
+ gtk_check_button_set_active (GTK_CHECK_BUTTON (self->created_button), FALSE);
+ }
+ else
+ {
+ gtk_check_button_set_active (GTK_CHECK_BUTTON (self->last_modified_button), FALSE);
+ gtk_check_button_set_active (GTK_CHECK_BUTTON (self->last_used_button), FALSE);
+ gtk_check_button_set_active (GTK_CHECK_BUTTON (self->created_button), TRUE);
+ }
+
+ self->fts_enabled = g_settings_get_boolean (nautilus_preferences,
+ NAUTILUS_PREFERENCES_FTS_ENABLED);
+ if (self->fts_enabled)
+ {
+ gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (self->full_text_search_button), TRUE);
+ }
+ else
+ {
+ gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (self->filename_search_button), TRUE);
+ }
+}
+
+GtkWidget *
+nautilus_search_popover_new (void)
+{
+ return g_object_new (NAUTILUS_TYPE_SEARCH_POPOVER, NULL);
+}
+
+/**
+ * nautilus_search_popover_get_query:
+ * @popover: a #NautilusSearchPopover
+ *
+ * Gets the current query for @popover.
+ *
+ * Returns: (transfer none): the current #NautilusQuery from @popover.
+ */
+NautilusQuery *
+nautilus_search_popover_get_query (NautilusSearchPopover *popover)
+{
+ g_return_val_if_fail (NAUTILUS_IS_SEARCH_POPOVER (popover), NULL);
+
+ return popover->query;
+}
+
+/**
+ * nautilus_search_popover_set_query:
+ * @popover: a #NautilusSearchPopover
+ * @query (nullable): a #NautilusQuery
+ *
+ * Sets the current query for @popover.
+ *
+ * Returns:
+ */
+void
+nautilus_search_popover_set_query (NautilusSearchPopover *popover,
+ NautilusQuery *query)
+{
+ NautilusQuery *previous_query;
+
+ g_return_if_fail (NAUTILUS_IS_SEARCH_POPOVER (popover));
+
+ previous_query = popover->query;
+
+ if (popover->query != query)
+ {
+ /* Disconnect signals and bindings from the old query */
+ if (previous_query)
+ {
+ g_signal_handlers_disconnect_by_func (previous_query, query_date_changed, popover);
+ }
+
+ g_set_object (&popover->query, query);
+
+ if (query)
+ {
+ /* Date */
+ setup_date (popover, query);
+
+ g_signal_connect (query,
+ "notify::date",
+ G_CALLBACK (query_date_changed),
+ popover);
+ }
+ else
+ {
+ nautilus_search_popover_reset_mime_types (popover);
+ nautilus_search_popover_reset_date_range (popover);
+ }
+ }
+}
+
+void
+nautilus_search_popover_reset_mime_types (NautilusSearchPopover *popover)
+{
+ g_return_if_fail (NAUTILUS_IS_SEARCH_POPOVER (popover));
+
+ gtk_list_box_select_row (GTK_LIST_BOX (popover->type_listbox),
+ gtk_list_box_get_row_at_index (GTK_LIST_BOX (popover->type_listbox), 0));
+
+ gtk_label_set_label (GTK_LABEL (popover->type_label),
+ nautilus_mime_types_group_get_name (0));
+ g_signal_emit_by_name (popover, "mime-type", 0, NULL);
+ gtk_stack_set_visible_child_name (GTK_STACK (popover->type_stack), "type-button");
+}
+
+void
+nautilus_search_popover_reset_date_range (NautilusSearchPopover *popover)
+{
+ g_return_if_fail (NAUTILUS_IS_SEARCH_POPOVER (popover));
+
+ gtk_list_box_select_row (GTK_LIST_BOX (popover->dates_listbox),
+ gtk_list_box_get_row_at_index (GTK_LIST_BOX (popover->dates_listbox), 0));
+
+ update_date_label (popover, NULL);
+ show_date_selection_widgets (popover, FALSE);
+ g_signal_emit_by_name (popover, "date-range", NULL);
+}
+
+gboolean
+nautilus_search_popover_get_fts_enabled (NautilusSearchPopover *popover)
+{
+ return popover->fts_enabled;
+}
diff --git a/src/nautilus-search-popover.h b/src/nautilus-search-popover.h
new file mode 100644
index 0000000..2137592
--- /dev/null
+++ b/src/nautilus-search-popover.h
@@ -0,0 +1,52 @@
+/* nautilus-search-popover.h
+ *
+ * Copyright (C) 2015 Georges Basile Stavracas Neto <georges.stavracas@gmail.com>
+ *
+ * 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 3 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/>.
+ */
+
+#pragma once
+
+#include <glib.h>
+#include <gtk/gtk.h>
+
+#include "nautilus-query.h"
+
+G_BEGIN_DECLS
+
+typedef enum {
+ NAUTILUS_SEARCH_FILTER_CONTENT, /* Full text or filename */
+ NAUTILUS_SEARCH_FILTER_DATE, /* When */
+ NAUTILUS_SEARCH_FILTER_LAST, /* Last modified or last used */
+ NAUTILUS_SEARCH_FILTER_TYPE /* What */
+} NautilusSearchFilter;
+
+#define NAUTILUS_TYPE_SEARCH_POPOVER (nautilus_search_popover_get_type())
+
+G_DECLARE_FINAL_TYPE (NautilusSearchPopover, nautilus_search_popover, NAUTILUS, SEARCH_POPOVER, GtkPopover)
+
+GtkWidget* nautilus_search_popover_new (void);
+
+NautilusQuery* nautilus_search_popover_get_query (NautilusSearchPopover *popover);
+
+void nautilus_search_popover_set_query (NautilusSearchPopover *popover,
+ NautilusQuery *query);
+void nautilus_search_popover_reset_date_range (NautilusSearchPopover *popover);
+void nautilus_search_popover_reset_mime_types (NautilusSearchPopover *popover);
+
+gboolean nautilus_search_popover_get_fts_enabled (NautilusSearchPopover *popover);
+void nautilus_search_popover_set_fts_sensitive (NautilusSearchPopover *popover,
+ gboolean sensitive);
+
+G_END_DECLS \ No newline at end of file
diff --git a/src/nautilus-search-provider.c b/src/nautilus-search-provider.c
new file mode 100644
index 0000000..9a4a655
--- /dev/null
+++ b/src/nautilus-search-provider.c
@@ -0,0 +1,145 @@
+/*
+ * Copyright (C) 2012 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <config.h>
+#include "nautilus-search-provider.h"
+#include "nautilus-enum-types.h"
+
+#include <glib-object.h>
+
+enum
+{
+ HITS_ADDED,
+ FINISHED,
+ ERROR,
+ LAST_SIGNAL
+};
+
+static guint signals[LAST_SIGNAL];
+
+G_DEFINE_INTERFACE (NautilusSearchProvider, nautilus_search_provider, G_TYPE_OBJECT)
+
+static void
+nautilus_search_provider_default_init (NautilusSearchProviderInterface *iface)
+{
+ /**
+ * NautilusSearchProvider::running:
+ *
+ * Whether the provider is running a search.
+ */
+ g_object_interface_install_property (iface,
+ g_param_spec_boolean ("running",
+ "Whether the provider is running",
+ "Whether the provider is running a search",
+ FALSE,
+ G_PARAM_READABLE));
+
+ signals[HITS_ADDED] = g_signal_new ("hits-added",
+ NAUTILUS_TYPE_SEARCH_PROVIDER,
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (NautilusSearchProviderInterface, hits_added),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__POINTER,
+ G_TYPE_NONE, 1,
+ G_TYPE_POINTER);
+
+ signals[FINISHED] = g_signal_new ("finished",
+ NAUTILUS_TYPE_SEARCH_PROVIDER,
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (NautilusSearchProviderInterface, finished),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__ENUM,
+ G_TYPE_NONE, 1,
+ NAUTILUS_TYPE_SEARCH_PROVIDER_STATUS);
+
+ signals[ERROR] = g_signal_new ("error",
+ NAUTILUS_TYPE_SEARCH_PROVIDER,
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (NautilusSearchProviderInterface, error),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__STRING,
+ G_TYPE_NONE, 1,
+ G_TYPE_STRING);
+}
+
+void
+nautilus_search_provider_set_query (NautilusSearchProvider *provider,
+ NautilusQuery *query)
+{
+ g_return_if_fail (NAUTILUS_IS_SEARCH_PROVIDER (provider));
+ g_return_if_fail (NAUTILUS_SEARCH_PROVIDER_GET_IFACE (provider)->set_query != NULL);
+ g_return_if_fail (NAUTILUS_IS_QUERY (query));
+
+ NAUTILUS_SEARCH_PROVIDER_GET_IFACE (provider)->set_query (provider, query);
+}
+
+void
+nautilus_search_provider_start (NautilusSearchProvider *provider)
+{
+ g_return_if_fail (NAUTILUS_IS_SEARCH_PROVIDER (provider));
+ g_return_if_fail (NAUTILUS_SEARCH_PROVIDER_GET_IFACE (provider)->start != NULL);
+
+ NAUTILUS_SEARCH_PROVIDER_GET_IFACE (provider)->start (provider);
+}
+
+void
+nautilus_search_provider_stop (NautilusSearchProvider *provider)
+{
+ g_return_if_fail (NAUTILUS_IS_SEARCH_PROVIDER (provider));
+ g_return_if_fail (NAUTILUS_SEARCH_PROVIDER_GET_IFACE (provider)->stop != NULL);
+
+ NAUTILUS_SEARCH_PROVIDER_GET_IFACE (provider)->stop (provider);
+}
+
+void
+nautilus_search_provider_hits_added (NautilusSearchProvider *provider,
+ GList *hits)
+{
+ g_return_if_fail (NAUTILUS_IS_SEARCH_PROVIDER (provider));
+
+ g_signal_emit (provider, signals[HITS_ADDED], 0, hits);
+}
+
+void
+nautilus_search_provider_finished (NautilusSearchProvider *provider,
+ NautilusSearchProviderStatus status)
+{
+ g_return_if_fail (NAUTILUS_IS_SEARCH_PROVIDER (provider));
+
+ g_signal_emit (provider, signals[FINISHED], 0, status);
+}
+
+void
+nautilus_search_provider_error (NautilusSearchProvider *provider,
+ const char *error_message)
+{
+ g_return_if_fail (NAUTILUS_IS_SEARCH_PROVIDER (provider));
+
+ g_warning ("Provider %s failed with error %s\n",
+ G_OBJECT_TYPE_NAME (provider), error_message);
+ g_signal_emit (provider, signals[ERROR], 0, error_message);
+}
+
+gboolean
+nautilus_search_provider_is_running (NautilusSearchProvider *provider)
+{
+ g_return_val_if_fail (NAUTILUS_IS_SEARCH_PROVIDER (provider), FALSE);
+ g_return_val_if_fail (NAUTILUS_SEARCH_PROVIDER_GET_IFACE (provider)->is_running, FALSE);
+
+ return NAUTILUS_SEARCH_PROVIDER_GET_IFACE (provider)->is_running (provider);
+}
diff --git a/src/nautilus-search-provider.h b/src/nautilus-search-provider.h
new file mode 100644
index 0000000..a1ff6f3
--- /dev/null
+++ b/src/nautilus-search-provider.h
@@ -0,0 +1,101 @@
+/*
+ * Copyright (C) 2012 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include <glib-object.h>
+#include "nautilus-query.h"
+#include "nautilus-search-hit.h"
+
+G_BEGIN_DECLS
+
+typedef enum {
+ NAUTILUS_SEARCH_PROVIDER_STATUS_NORMAL,
+ NAUTILUS_SEARCH_PROVIDER_STATUS_RESTARTING
+} NautilusSearchProviderStatus;
+
+typedef enum {
+ NAUTILUS_SEARCH_ENGINE_ALL_ENGINES,
+ NAUTILUS_SEARCH_ENGINE_TRACKER_ENGINE,
+ NAUTILUS_SEARCH_ENGINE_RECENT_ENGINE,
+ NAUTILUS_SEARCH_ENGINE_MODEL_ENGINE,
+ NAUTILUS_SEARCH_ENGINE_SIMPLE_ENGINE,
+} NautilusSearchEngineTarget;
+
+#define NAUTILUS_TYPE_SEARCH_PROVIDER (nautilus_search_provider_get_type ())
+
+G_DECLARE_INTERFACE (NautilusSearchProvider, nautilus_search_provider, NAUTILUS, SEARCH_PROVIDER, GObject)
+
+struct _NautilusSearchProviderInterface {
+ GTypeInterface g_iface;
+
+ /* VTable */
+ void (*set_query) (NautilusSearchProvider *provider, NautilusQuery *query);
+ void (*start) (NautilusSearchProvider *provider);
+ void (*stop) (NautilusSearchProvider *provider);
+
+ /* Signals */
+ void (*hits_added) (NautilusSearchProvider *provider, GList *hits);
+ /* This signal has a status parameter because it's necesary to discern
+ * when the search engine finished normally or wheter it finished in a
+ * different situation that will cause the engine to do some action after
+ * finishing.
+ *
+ * For example, the search engine restarts itself if the client starts a
+ * new search before all the search providers finished its current ongoing search.
+ *
+ * A real use case of this is when the user change quickly the query of the search,
+ * the search engine stops all the search providers, but given that each search
+ * provider has its own thread it will be actually stopped in a unknown time.
+ * To fix that, the search engine marks itself for restarting if the client
+ * starts a new search and not all providers finished. Then it will emit
+ * its finished signal and restart all providers with the new search.
+ *
+ * That can cause that when the search engine emits its finished signal,
+ * it actually relates to old searchs that it stopped and not the one
+ * the client started lately.
+ * The client doesn't have a way to know wheter the finished signal
+ * relates to its current search or with an old search.
+ *
+ * To fix this situation, provide with the signal a status parameter, that
+ * provides a hint of how the search engine stopped or if it is going to realize
+ * some action afterwards, like restarting.
+ */
+ void (*finished) (NautilusSearchProvider *provider,
+ NautilusSearchProviderStatus status);
+ void (*error) (NautilusSearchProvider *provider, const char *error_message);
+ gboolean (*is_running) (NautilusSearchProvider *provider);
+};
+
+GType nautilus_search_provider_get_type (void) G_GNUC_CONST;
+
+/* Interface Functions */
+void nautilus_search_provider_set_query (NautilusSearchProvider *provider,
+ NautilusQuery *query);
+void nautilus_search_provider_start (NautilusSearchProvider *provider);
+void nautilus_search_provider_stop (NautilusSearchProvider *provider);
+
+void nautilus_search_provider_hits_added (NautilusSearchProvider *provider,
+ GList *hits);
+void nautilus_search_provider_finished (NautilusSearchProvider *provider,
+ NautilusSearchProviderStatus status);
+void nautilus_search_provider_error (NautilusSearchProvider *provider,
+ const char *error_message);
+
+gboolean nautilus_search_provider_is_running (NautilusSearchProvider *provider);
+
+G_END_DECLS \ No newline at end of file
diff --git a/src/nautilus-self-check-functions.c b/src/nautilus-self-check-functions.c
new file mode 100644
index 0000000..6de4d70
--- /dev/null
+++ b/src/nautilus-self-check-functions.c
@@ -0,0 +1,37 @@
+/*
+ * Nautilus
+ *
+ * Copyright (C) 1999, 2000 Eazel, Inc.
+ *
+ * Nautilus is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * Nautilus is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ *
+ * Author: Darin Adler <darin@bentspoon.com>
+ */
+
+/* nautilus-self-check-functions.c: Wrapper for all self check functions
+ * in Nautilus proper.
+ */
+
+#include <config.h>
+
+#if !defined (NAUTILUS_OMIT_SELF_CHECK)
+
+#include "nautilus-self-check-functions.h"
+
+void nautilus_run_self_checks(void)
+{
+ NAUTILUS_FOR_EACH_SELF_CHECK_FUNCTION (NAUTILUS_CALL_SELF_CHECK_FUNCTION)
+}
+
+#endif /* ! NAUTILUS_OMIT_SELF_CHECK */
diff --git a/src/nautilus-self-check-functions.h b/src/nautilus-self-check-functions.h
new file mode 100644
index 0000000..ccbea39
--- /dev/null
+++ b/src/nautilus-self-check-functions.h
@@ -0,0 +1,46 @@
+
+/*
+ * Nautilus
+ *
+ * Copyright (C) 1999, 2000 Eazel, Inc.
+ *
+ * Nautilus is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * Nautilus is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ *
+ * Author: Darin Adler <darin@bentspoon.com>
+ */
+
+/* nautilus-self-check-functions.h: Wrapper and prototypes for all self
+ * check functions in Nautilus proper.
+ */
+
+#pragma once
+
+void nautilus_run_self_checks (void);
+
+/* Putting the prototypes for these self-check functions in each
+ header file for the files they are defined in would make compiling
+ the self-check framework take way too long (since one file would
+ have to include everything).
+
+ So we put the list of functions here instead.
+
+ Instead of just putting prototypes here, we put this macro that
+ can be used to do operations on the whole list of functions.
+*/
+
+#define NAUTILUS_FOR_EACH_SELF_CHECK_FUNCTION(macro) \
+/* Add new self-check functions to the list above this line. */
+
+/* Generate prototypes for all the functions. */
+NAUTILUS_FOR_EACH_SELF_CHECK_FUNCTION (NAUTILUS_SELF_CHECK_FUNCTION_PROTOTYPE)
diff --git a/src/nautilus-shell-search-provider.c b/src/nautilus-shell-search-provider.c
new file mode 100644
index 0000000..55e9e1a
--- /dev/null
+++ b/src/nautilus-shell-search-provider.c
@@ -0,0 +1,875 @@
+/*
+ * nautilus-shell-search-provider.c - Implementation of a GNOME Shell
+ * search provider
+ *
+ * Copyright (C) 2012 Red Hat, Inc.
+ *
+ * Nautilus is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * Nautilus is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Cosimo Cecchi <cosimoc@gnome.org>
+ *
+ */
+
+#include <config.h>
+
+#include <gio/gio.h>
+#include <string.h>
+#include <glib/gi18n.h>
+#include <gdk/gdk.h>
+
+#include "nautilus-file.h"
+#include "nautilus-file-utilities.h"
+#include "nautilus-search-engine.h"
+#include "nautilus-search-provider.h"
+#include "nautilus-ui-utilities.h"
+
+#include "nautilus-application.h"
+#include "nautilus-bookmark-list.h"
+#include "nautilus-shell-search-provider-generated.h"
+#include "nautilus-shell-search-provider.h"
+
+typedef struct
+{
+ NautilusShellSearchProvider *self;
+
+ NautilusSearchEngine *engine;
+ NautilusQuery *query;
+
+ GHashTable *hits;
+ GDBusMethodInvocation *invocation;
+
+ gint64 start_time;
+} PendingSearch;
+
+struct _NautilusShellSearchProvider
+{
+ GObject parent;
+
+ NautilusShellSearchProvider2 *skeleton;
+
+ PendingSearch *current_search;
+
+ GList *metas_requests;
+ GHashTable *metas_cache;
+};
+
+G_DEFINE_TYPE (NautilusShellSearchProvider, nautilus_shell_search_provider, G_TYPE_OBJECT)
+
+static gchar *
+get_display_name (NautilusShellSearchProvider *self,
+ NautilusFile *file)
+{
+ GFile *location;
+ NautilusBookmark *bookmark;
+ NautilusBookmarkList *bookmarks;
+
+ bookmarks = nautilus_application_get_bookmarks (NAUTILUS_APPLICATION (g_application_get_default ()));
+
+ location = nautilus_file_get_location (file);
+ bookmark = nautilus_bookmark_list_item_with_location (bookmarks, location, NULL);
+ g_object_unref (location);
+
+ if (bookmark)
+ {
+ return g_strdup (nautilus_bookmark_get_name (bookmark));
+ }
+ else
+ {
+ return nautilus_file_get_display_name (file);
+ }
+}
+
+static GIcon *
+get_gicon (NautilusShellSearchProvider *self,
+ NautilusFile *file)
+{
+ GFile *location;
+ NautilusBookmark *bookmark;
+ NautilusBookmarkList *bookmarks;
+
+ bookmarks = nautilus_application_get_bookmarks (NAUTILUS_APPLICATION (g_application_get_default ()));
+
+ location = nautilus_file_get_location (file);
+ bookmark = nautilus_bookmark_list_item_with_location (bookmarks, location, NULL);
+ g_object_unref (location);
+
+ if (bookmark)
+ {
+ return nautilus_bookmark_get_icon (bookmark);
+ }
+ else
+ {
+ return nautilus_file_get_gicon (file, 0);
+ }
+}
+
+static void
+pending_search_free (PendingSearch *search)
+{
+ g_hash_table_destroy (search->hits);
+ g_clear_object (&search->query);
+ g_clear_object (&search->engine);
+ g_clear_object (&search->invocation);
+
+ g_slice_free (PendingSearch, search);
+}
+
+static void
+pending_search_finish (PendingSearch *search,
+ GDBusMethodInvocation *invocation,
+ GVariant *result)
+{
+ NautilusShellSearchProvider *self = search->self;
+
+ g_dbus_method_invocation_return_value (invocation, result);
+
+ if (search == self->current_search)
+ {
+ self->current_search = NULL;
+ }
+
+ g_application_release (g_application_get_default ());
+ pending_search_free (search);
+}
+
+static void
+cancel_current_search (NautilusShellSearchProvider *self)
+{
+ if (self->current_search != NULL)
+ {
+ g_debug ("*** Cancel current search");
+ nautilus_search_provider_stop (NAUTILUS_SEARCH_PROVIDER (self->current_search->engine));
+ }
+}
+
+static void
+cancel_current_search_ignoring_partial_results (NautilusShellSearchProvider *self)
+{
+ if (self->current_search != NULL)
+ {
+ cancel_current_search (self);
+ g_signal_handlers_disconnect_by_data (G_OBJECT (self->current_search->engine),
+ self->current_search);
+ pending_search_finish (self->current_search, self->current_search->invocation,
+ g_variant_new ("(as)", NULL));
+ }
+}
+
+static void
+search_hits_added_cb (NautilusSearchEngine *engine,
+ GList *hits,
+ gpointer user_data)
+{
+ PendingSearch *search = user_data;
+ GList *l;
+ NautilusSearchHit *hit;
+ const gchar *hit_uri;
+
+ g_debug ("*** Search engine hits added");
+
+ for (l = hits; l != NULL; l = l->next)
+ {
+ hit = l->data;
+ nautilus_search_hit_compute_scores (hit, search->query);
+ hit_uri = nautilus_search_hit_get_uri (hit);
+ g_debug (" %s", hit_uri);
+
+ g_hash_table_replace (search->hits, g_strdup (hit_uri), g_object_ref (hit));
+ }
+}
+
+static gint
+search_hit_compare_relevance (gconstpointer a,
+ gconstpointer b)
+{
+ NautilusSearchHit *hit_a, *hit_b;
+ gdouble relevance_a, relevance_b;
+
+ hit_a = NAUTILUS_SEARCH_HIT ((gpointer) a);
+ hit_b = NAUTILUS_SEARCH_HIT ((gpointer) b);
+
+ relevance_a = nautilus_search_hit_get_relevance (hit_a);
+ relevance_b = nautilus_search_hit_get_relevance (hit_b);
+
+ if (relevance_a > relevance_b)
+ {
+ return -1;
+ }
+ else if (relevance_a == relevance_b)
+ {
+ return 0;
+ }
+
+ return 1;
+}
+
+static void
+search_finished_cb (NautilusSearchEngine *engine,
+ NautilusSearchProviderStatus status,
+ gpointer user_data)
+{
+ PendingSearch *search = user_data;
+ GList *hits, *l;
+ NautilusSearchHit *hit;
+ GVariantBuilder builder;
+ gint64 current_time;
+
+ current_time = g_get_monotonic_time ();
+ g_debug ("*** Search engine search finished - time elapsed %dms",
+ (gint) ((current_time - search->start_time) / 1000));
+
+ hits = g_hash_table_get_values (search->hits);
+ hits = g_list_sort (hits, search_hit_compare_relevance);
+
+ g_variant_builder_init (&builder, G_VARIANT_TYPE ("as"));
+
+ for (l = hits; l != NULL; l = l->next)
+ {
+ hit = l->data;
+ g_variant_builder_add (&builder, "s", nautilus_search_hit_get_uri (hit));
+ }
+
+ g_list_free (hits);
+ pending_search_finish (search, search->invocation,
+ g_variant_new ("(as)", &builder));
+}
+
+static void
+search_error_cb (NautilusSearchEngine *engine,
+ const gchar *error_message,
+ gpointer user_data)
+{
+ NautilusShellSearchProvider *self = user_data;
+ PendingSearch *search = self->current_search;
+
+ g_debug ("*** Search engine search error");
+ pending_search_finish (search, search->invocation,
+ g_variant_new ("(as)", NULL));
+}
+
+typedef struct
+{
+ gchar *uri;
+ gchar *string_for_compare;
+} SearchHitCandidate;
+
+static void
+search_hit_candidate_free (SearchHitCandidate *candidate)
+{
+ g_free (candidate->uri);
+ g_free (candidate->string_for_compare);
+
+ g_slice_free (SearchHitCandidate, candidate);
+}
+
+static SearchHitCandidate *
+search_hit_candidate_new (const gchar *uri,
+ const gchar *name)
+{
+ SearchHitCandidate *candidate = g_slice_new0 (SearchHitCandidate);
+
+ candidate->uri = g_strdup (uri);
+ candidate->string_for_compare = g_strdup (name);
+
+ return candidate;
+}
+
+static void
+search_add_volumes_and_bookmarks (PendingSearch *search)
+{
+ NautilusSearchHit *hit;
+ NautilusBookmark *bookmark;
+ const gchar *name;
+ gchar *string, *uri;
+ gdouble match;
+ GList *l, *m, *drives, *volumes, *mounts, *mounts_to_check, *candidates;
+ GDrive *drive;
+ GVolume *volume;
+ GMount *mount;
+ GFile *location;
+ SearchHitCandidate *candidate;
+ NautilusBookmarkList *bookmarks;
+ GList *all_bookmarks;
+ GVolumeMonitor *volume_monitor;
+
+ bookmarks = nautilus_application_get_bookmarks (NAUTILUS_APPLICATION (g_application_get_default ()));
+ all_bookmarks = nautilus_bookmark_list_get_all (bookmarks);
+ volume_monitor = g_volume_monitor_get ();
+ candidates = NULL;
+
+ /* first add bookmarks */
+ for (l = all_bookmarks; l != NULL; l = l->next)
+ {
+ bookmark = NAUTILUS_BOOKMARK (l->data);
+ name = nautilus_bookmark_get_name (bookmark);
+ if (name == NULL)
+ {
+ continue;
+ }
+
+ uri = nautilus_bookmark_get_uri (bookmark);
+ candidate = search_hit_candidate_new (uri, name);
+ candidates = g_list_prepend (candidates, candidate);
+
+ g_free (uri);
+ }
+
+ /* home dir */
+ uri = nautilus_get_home_directory_uri ();
+ candidate = search_hit_candidate_new (uri, _("Home"));
+ candidates = g_list_prepend (candidates, candidate);
+ g_free (uri);
+
+ /* trash */
+ candidate = search_hit_candidate_new ("trash:///", _("Trash"));
+ candidates = g_list_prepend (candidates, candidate);
+
+ /* now add mounts */
+ mounts_to_check = NULL;
+
+ /* first check all connected drives */
+ drives = g_volume_monitor_get_connected_drives (volume_monitor);
+ for (l = drives; l != NULL; l = l->next)
+ {
+ drive = l->data;
+ volumes = g_drive_get_volumes (drive);
+
+ for (m = volumes; m != NULL; m = m->next)
+ {
+ volume = m->data;
+ mount = g_volume_get_mount (volume);
+ if (mount != NULL)
+ {
+ mounts_to_check = g_list_prepend (mounts_to_check, mount);
+ }
+ }
+
+ g_list_free_full (volumes, g_object_unref);
+ }
+ g_list_free_full (drives, g_object_unref);
+
+ /* then volumes that don't have a drive */
+ volumes = g_volume_monitor_get_volumes (volume_monitor);
+ for (l = volumes; l != NULL; l = l->next)
+ {
+ volume = l->data;
+ drive = g_volume_get_drive (volume);
+
+ if (drive == NULL)
+ {
+ mount = g_volume_get_mount (volume);
+ if (mount != NULL)
+ {
+ mounts_to_check = g_list_prepend (mounts_to_check, mount);
+ }
+ }
+ g_clear_object (&drive);
+ }
+ g_list_free_full (volumes, g_object_unref);
+
+ /* then mounts that have no volume */
+ mounts = g_volume_monitor_get_mounts (volume_monitor);
+ for (l = mounts; l != NULL; l = l->next)
+ {
+ mount = l->data;
+
+ if (g_mount_is_shadowed (mount))
+ {
+ continue;
+ }
+
+ volume = g_mount_get_volume (mount);
+ if (volume == NULL)
+ {
+ mounts_to_check = g_list_prepend (mounts_to_check, g_object_ref (mount));
+ }
+ g_clear_object (&volume);
+ }
+ g_list_free_full (mounts, g_object_unref);
+
+ /* actually add mounts to candidates */
+ for (l = mounts_to_check; l != NULL; l = l->next)
+ {
+ mount = l->data;
+
+ string = g_mount_get_name (mount);
+ if (string == NULL)
+ {
+ continue;
+ }
+
+ location = g_mount_get_default_location (mount);
+ uri = g_file_get_uri (location);
+ candidate = search_hit_candidate_new (uri, string);
+ candidates = g_list_prepend (candidates, candidate);
+
+ g_free (uri);
+ g_free (string);
+ g_object_unref (location);
+ }
+ g_list_free_full (mounts_to_check, g_object_unref);
+
+ /* now do the actual string matching */
+ candidates = g_list_reverse (candidates);
+
+ for (l = candidates; l != NULL; l = l->next)
+ {
+ candidate = l->data;
+ match = nautilus_query_matches_string (search->query,
+ candidate->string_for_compare);
+
+ if (match > -1)
+ {
+ hit = nautilus_search_hit_new (candidate->uri);
+ nautilus_search_hit_set_fts_rank (hit, match);
+ nautilus_search_hit_compute_scores (hit, search->query);
+ g_hash_table_replace (search->hits, g_strdup (candidate->uri), hit);
+ }
+ }
+ g_list_free_full (candidates, (GDestroyNotify) search_hit_candidate_free);
+ g_object_unref (volume_monitor);
+}
+
+static NautilusQuery *
+shell_query_new (gchar **terms)
+{
+ NautilusQuery *query;
+ g_autoptr (GFile) home = NULL;
+ g_autofree gchar *terms_joined = NULL;
+
+ terms_joined = g_strjoinv (" ", terms);
+ home = g_file_new_for_path (g_get_home_dir ());
+
+ query = nautilus_query_new ();
+ nautilus_query_set_text (query, terms_joined);
+ nautilus_query_set_location (query, home);
+
+ return query;
+}
+
+static void
+execute_search (NautilusShellSearchProvider *self,
+ GDBusMethodInvocation *invocation,
+ gchar **terms)
+{
+ NautilusQuery *query;
+ PendingSearch *pending_search;
+
+ cancel_current_search (self);
+
+ /* don't attempt searches for a single character */
+ if (g_strv_length (terms) == 1 &&
+ g_utf8_strlen (terms[0], -1) == 1)
+ {
+ g_dbus_method_invocation_return_value (invocation, g_variant_new ("(as)", NULL));
+ return;
+ }
+
+ query = shell_query_new (terms);
+ nautilus_query_set_recursive (query, NAUTILUS_QUERY_RECURSIVE_INDEXED_ONLY);
+ nautilus_query_set_show_hidden_files (query, FALSE);
+
+ pending_search = g_slice_new0 (PendingSearch);
+ pending_search->invocation = g_object_ref (invocation);
+ pending_search->hits = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_object_unref);
+ pending_search->query = query;
+ pending_search->engine = nautilus_search_engine_new ();
+ pending_search->start_time = g_get_monotonic_time ();
+ pending_search->self = self;
+
+ g_signal_connect (pending_search->engine, "hits-added",
+ G_CALLBACK (search_hits_added_cb), pending_search);
+ g_signal_connect (pending_search->engine, "finished",
+ G_CALLBACK (search_finished_cb), pending_search);
+ g_signal_connect (pending_search->engine, "error",
+ G_CALLBACK (search_error_cb), pending_search);
+
+ self->current_search = pending_search;
+ g_application_hold (g_application_get_default ());
+
+ search_add_volumes_and_bookmarks (pending_search);
+
+ /* start searching */
+ g_debug ("*** Search engine search started");
+ nautilus_search_provider_set_query (NAUTILUS_SEARCH_PROVIDER (pending_search->engine),
+ query);
+ nautilus_search_provider_start (NAUTILUS_SEARCH_PROVIDER (pending_search->engine));
+}
+
+static gboolean
+handle_get_initial_result_set (NautilusShellSearchProvider2 *skeleton,
+ GDBusMethodInvocation *invocation,
+ gchar **terms,
+ gpointer user_data)
+{
+ NautilusShellSearchProvider *self = user_data;
+
+ g_debug ("****** GetInitialResultSet");
+ execute_search (self, invocation, terms);
+ return TRUE;
+}
+
+static gboolean
+handle_get_subsearch_result_set (NautilusShellSearchProvider2 *skeleton,
+ GDBusMethodInvocation *invocation,
+ gchar **previous_results,
+ gchar **terms,
+ gpointer user_data)
+{
+ NautilusShellSearchProvider *self = user_data;
+
+ g_debug ("****** GetSubSearchResultSet");
+ execute_search (self, invocation, terms);
+ return TRUE;
+}
+
+typedef struct
+{
+ NautilusShellSearchProvider *self;
+
+ gint64 start_time;
+ NautilusFileListHandle *handle;
+ GDBusMethodInvocation *invocation;
+
+ gchar **uris;
+} ResultMetasData;
+
+static void
+result_metas_data_free (ResultMetasData *data)
+{
+ g_clear_pointer (&data->handle, nautilus_file_list_cancel_call_when_ready);
+ g_clear_object (&data->self);
+ g_clear_object (&data->invocation);
+ g_strfreev (data->uris);
+
+ g_slice_free (ResultMetasData, data);
+}
+
+static void
+result_metas_return_from_cache (ResultMetasData *data)
+{
+ GVariantBuilder builder;
+ GVariant *meta;
+ gint64 current_time;
+ gint idx;
+
+ g_variant_builder_init (&builder, G_VARIANT_TYPE ("aa{sv}"));
+
+ if (data->uris)
+ {
+ for (idx = 0; data->uris[idx] != NULL; idx++)
+ {
+ meta = g_hash_table_lookup (data->self->metas_cache,
+ data->uris[idx]);
+ g_variant_builder_add_value (&builder, meta);
+ }
+ }
+
+ current_time = g_get_monotonic_time ();
+ g_debug ("*** GetResultMetas completed - time elapsed %dms",
+ (gint) ((current_time - data->start_time) / 1000));
+
+ g_dbus_method_invocation_return_value (data->invocation,
+ g_variant_new ("(aa{sv})", &builder));
+}
+
+static void
+result_metas_return_empty (ResultMetasData *data)
+{
+ g_clear_pointer (&data->uris, g_strfreev);
+ result_metas_return_from_cache (data);
+ result_metas_data_free (data);
+}
+
+static void
+cancel_result_meta_requests (NautilusShellSearchProvider *self)
+{
+ g_debug ("*** Cancel Results Meta requests");
+
+ g_list_free_full (self->metas_requests,
+ (GDestroyNotify) result_metas_return_empty);
+ self->metas_requests = NULL;
+}
+
+static void
+result_list_attributes_ready_cb (GList *file_list,
+ gpointer user_data)
+{
+ ResultMetasData *data = user_data;
+ GVariantBuilder meta;
+ NautilusFile *file;
+ GFile *file_location;
+ GList *l;
+ gchar *uri, *display_name;
+ gchar *path, *description;
+ gchar *thumbnail_path;
+ GIcon *gicon;
+ GFile *location;
+ GVariant *meta_variant;
+ gint icon_scale;
+
+ icon_scale = gdk_monitor_get_scale_factor (g_list_model_get_item (gdk_display_get_monitors (gdk_display_get_default ()), 0));
+
+ for (l = file_list; l != NULL; l = l->next)
+ {
+ file = l->data;
+ g_variant_builder_init (&meta, G_VARIANT_TYPE ("a{sv}"));
+
+ uri = nautilus_file_get_uri (file);
+ display_name = get_display_name (data->self, file);
+ file_location = nautilus_file_get_location (file);
+ path = g_file_get_path (file_location);
+ description = path ? g_path_get_dirname (path) : NULL;
+
+ g_variant_builder_add (&meta, "{sv}",
+ "id", g_variant_new_string (uri));
+ g_variant_builder_add (&meta, "{sv}",
+ "name", g_variant_new_string (display_name));
+ /* Some backends like trash:/// don't have a path, so we show the uri itself. */
+ g_variant_builder_add (&meta, "{sv}",
+ "description", g_variant_new_string (description ? description : uri));
+
+ gicon = NULL;
+ thumbnail_path = nautilus_file_get_thumbnail_path (file);
+
+ if (thumbnail_path != NULL)
+ {
+ location = g_file_new_for_path (thumbnail_path);
+ gicon = g_file_icon_new (location);
+
+ g_free (thumbnail_path);
+ g_object_unref (location);
+ }
+ else
+ {
+ gicon = get_gicon (data->self, file);
+ }
+
+ if (gicon == NULL)
+ {
+ gicon = G_ICON (nautilus_file_get_icon_texture (file, 128,
+ icon_scale,
+ NAUTILUS_FILE_ICON_FLAGS_USE_THUMBNAILS));
+ }
+
+ g_variant_builder_add (&meta, "{sv}",
+ "icon", g_icon_serialize (gicon));
+ g_object_unref (gicon);
+
+ meta_variant = g_variant_builder_end (&meta);
+ g_hash_table_insert (data->self->metas_cache,
+ g_strdup (uri), g_variant_ref_sink (meta_variant));
+
+ g_free (display_name);
+ g_free (path);
+ g_free (description);
+ g_free (uri);
+ }
+
+ data->handle = NULL;
+ data->self->metas_requests = g_list_remove (data->self->metas_requests, data);
+
+ result_metas_return_from_cache (data);
+ result_metas_data_free (data);
+}
+
+static gboolean
+handle_get_result_metas (NautilusShellSearchProvider2 *skeleton,
+ GDBusMethodInvocation *invocation,
+ gchar **results,
+ gpointer user_data)
+{
+ NautilusShellSearchProvider *self = user_data;
+ GList *missing_files = NULL;
+ const gchar *uri;
+ ResultMetasData *data;
+ gint idx;
+
+ g_debug ("****** GetResultMetas");
+
+ for (idx = 0; results[idx] != NULL; idx++)
+ {
+ uri = results[idx];
+
+ if (!g_hash_table_lookup (self->metas_cache, uri))
+ {
+ missing_files = g_list_prepend (missing_files, nautilus_file_get_by_uri (uri));
+ }
+ }
+
+ data = g_slice_new0 (ResultMetasData);
+ data->self = g_object_ref (self);
+ data->invocation = g_object_ref (invocation);
+ data->start_time = g_get_monotonic_time ();
+ data->uris = g_strdupv (results);
+
+ if (missing_files == NULL)
+ {
+ result_metas_return_from_cache (data);
+ result_metas_data_free (data);
+ return TRUE;
+ }
+
+ nautilus_file_list_call_when_ready (missing_files,
+ NAUTILUS_FILE_ATTRIBUTES_FOR_ICON,
+ &data->handle,
+ result_list_attributes_ready_cb,
+ data);
+ self->metas_requests = g_list_prepend (self->metas_requests, data);
+ nautilus_file_list_free (missing_files);
+ return TRUE;
+}
+
+typedef struct
+{
+ GFile *file;
+ NautilusShellSearchProvider2 *skeleton;
+ GDBusMethodInvocation *invocation;
+} ShowURIData;
+
+static void
+show_uri_callback (GObject *source_object,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ ShowURIData *data = user_data;
+
+ if (!gtk_show_uri_full_finish (NULL, result, NULL))
+ {
+ g_application_open (g_application_get_default (), &data->file, 1, "");
+ }
+
+ nautilus_shell_search_provider2_complete_activate_result (data->skeleton, data->invocation);
+
+ g_object_unref (data->file);
+ g_free (data);
+}
+
+static gboolean
+handle_activate_result (NautilusShellSearchProvider2 *skeleton,
+ GDBusMethodInvocation *invocation,
+ gchar *result,
+ gchar **terms,
+ guint32 timestamp,
+ gpointer user_data)
+{
+ ShowURIData *data;
+
+ data = g_new (ShowURIData, 1);
+ data->file = g_file_new_for_uri (result);
+ data->skeleton = skeleton;
+ data->invocation = invocation;
+
+ gtk_show_uri_full (NULL, result, timestamp, NULL, show_uri_callback, data);
+
+ return TRUE;
+}
+
+static gboolean
+handle_launch_search (NautilusShellSearchProvider2 *skeleton,
+ GDBusMethodInvocation *invocation,
+ gchar **terms,
+ guint32 timestamp,
+ gpointer user_data)
+{
+ GApplication *app = g_application_get_default ();
+ g_autoptr (NautilusQuery) query = shell_query_new (terms);
+ NautilusQueryRecursive recursive = location_settings_search_get_recursive ();
+
+ /*
+ * If no recursive search is enabled, we still want to be able to
+ * show the same results we presented in the overview when nautilus
+ * is explicitly launched to access to more results, and thus we perform
+ * a query showing results coming from index-based search engines.
+ * Otherwise we respect the global setting for recursivity.
+ */
+ if (recursive == NAUTILUS_QUERY_RECURSIVE_NEVER)
+ {
+ nautilus_query_set_recursive (query,
+ NAUTILUS_QUERY_RECURSIVE_INDEXED_ONLY);
+ }
+ else
+ {
+ nautilus_query_set_recursive (query, recursive);
+ }
+
+ nautilus_application_search (NAUTILUS_APPLICATION (app), query);
+
+ nautilus_shell_search_provider2_complete_launch_search (skeleton, invocation);
+ return TRUE;
+}
+
+static void
+search_provider_dispose (GObject *obj)
+{
+ NautilusShellSearchProvider *self = NAUTILUS_SHELL_SEARCH_PROVIDER (obj);
+
+ g_clear_object (&self->skeleton);
+ g_hash_table_destroy (self->metas_cache);
+ cancel_current_search_ignoring_partial_results (self);
+ cancel_result_meta_requests (self);
+
+ G_OBJECT_CLASS (nautilus_shell_search_provider_parent_class)->dispose (obj);
+}
+
+static void
+nautilus_shell_search_provider_init (NautilusShellSearchProvider *self)
+{
+ self->metas_cache = g_hash_table_new_full (g_str_hash, g_str_equal,
+ g_free, (GDestroyNotify) g_variant_unref);
+
+ self->skeleton = nautilus_shell_search_provider2_skeleton_new ();
+
+ g_signal_connect (self->skeleton, "handle-get-initial-result-set",
+ G_CALLBACK (handle_get_initial_result_set), self);
+ g_signal_connect (self->skeleton, "handle-get-subsearch-result-set",
+ G_CALLBACK (handle_get_subsearch_result_set), self);
+ g_signal_connect (self->skeleton, "handle-get-result-metas",
+ G_CALLBACK (handle_get_result_metas), self);
+ g_signal_connect (self->skeleton, "handle-activate-result",
+ G_CALLBACK (handle_activate_result), self);
+ g_signal_connect (self->skeleton, "handle-launch-search",
+ G_CALLBACK (handle_launch_search), self);
+}
+
+static void
+nautilus_shell_search_provider_class_init (NautilusShellSearchProviderClass *klass)
+{
+ GObjectClass *oclass = G_OBJECT_CLASS (klass);
+
+ oclass->dispose = search_provider_dispose;
+}
+
+NautilusShellSearchProvider *
+nautilus_shell_search_provider_new (void)
+{
+ return g_object_new (nautilus_shell_search_provider_get_type (),
+ NULL);
+}
+
+gboolean
+nautilus_shell_search_provider_register (NautilusShellSearchProvider *self,
+ GDBusConnection *connection,
+ GError **error)
+{
+ return g_dbus_interface_skeleton_export (G_DBUS_INTERFACE_SKELETON (self->skeleton),
+ connection,
+ "/org/gnome/Nautilus" PROFILE "/SearchProvider", error);
+}
+
+void
+nautilus_shell_search_provider_unregister (NautilusShellSearchProvider *self)
+{
+ g_dbus_interface_skeleton_unexport (G_DBUS_INTERFACE_SKELETON (self->skeleton));
+}
diff --git a/src/nautilus-shell-search-provider.h b/src/nautilus-shell-search-provider.h
new file mode 100644
index 0000000..8eaa675
--- /dev/null
+++ b/src/nautilus-shell-search-provider.h
@@ -0,0 +1,39 @@
+/*
+ * nautilus-shell-search-provider.h - Implementation of a GNOME Shell
+ * search provider
+ *
+ * Copyright (C) 2012 Red Hat, Inc.
+ *
+ * Nautilus is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * Nautilus is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Cosimo Cecchi <cosimoc@gnome.org>
+ *
+ */
+
+#pragma once
+
+#define NAUTILUS_TYPE_SHELL_SEARCH_PROVIDER nautilus_shell_search_provider_get_type()
+#define NAUTILUS_SHELL_SEARCH_PROVIDER(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST ((obj), NAUTILUS_TYPE_SHELL_SEARCH_PROVIDER, NautilusShellSearchProvider))
+
+typedef struct _NautilusShellSearchProvider NautilusShellSearchProvider;
+typedef GObjectClass NautilusShellSearchProviderClass;
+
+GType nautilus_shell_search_provider_get_type (void);
+NautilusShellSearchProvider * nautilus_shell_search_provider_new (void);
+
+gboolean nautilus_shell_search_provider_register (NautilusShellSearchProvider *self,
+ GDBusConnection *connection,
+ GError **error);
+void nautilus_shell_search_provider_unregister (NautilusShellSearchProvider *self); \ No newline at end of file
diff --git a/src/nautilus-signaller.c b/src/nautilus-signaller.c
new file mode 100644
index 0000000..4020cb2
--- /dev/null
+++ b/src/nautilus-signaller.c
@@ -0,0 +1,93 @@
+/*
+ * Nautilus
+ *
+ * Copyright (C) 1999, 2000 Eazel, Inc.
+ *
+ * Nautilus is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * 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/>.
+ *
+ * Author: John Sullivan <sullivan@eazel.com>
+ */
+
+/* nautilus-signaller.h: Class to manage nautilus-wide signals that don't
+ * correspond to any particular object.
+ */
+
+#include <config.h>
+#include "nautilus-signaller.h"
+
+#include <eel/eel-debug.h>
+
+typedef GObject NautilusSignaller;
+typedef GObjectClass NautilusSignallerClass;
+
+enum
+{
+ HISTORY_LIST_CHANGED,
+ POPUP_MENU_CHANGED,
+ MIME_DATA_CHANGED,
+ LAST_SIGNAL
+};
+
+static guint signals[LAST_SIGNAL];
+
+static GType nautilus_signaller_get_type (void);
+
+G_DEFINE_TYPE (NautilusSignaller, nautilus_signaller, G_TYPE_OBJECT);
+
+GObject *
+nautilus_signaller_get_current (void)
+{
+ static GObject *global_signaller = NULL;
+
+ if (global_signaller == NULL)
+ {
+ global_signaller = g_object_new (nautilus_signaller_get_type (), NULL);
+ }
+
+ return global_signaller;
+}
+
+static void
+nautilus_signaller_init (NautilusSignaller *signaller)
+{
+}
+
+static void
+nautilus_signaller_class_init (NautilusSignallerClass *class)
+{
+ signals[HISTORY_LIST_CHANGED] =
+ g_signal_new ("history-list-changed",
+ G_TYPE_FROM_CLASS (class),
+ G_SIGNAL_RUN_LAST,
+ 0,
+ NULL, NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+ signals[POPUP_MENU_CHANGED] =
+ g_signal_new ("popup-menu-changed",
+ G_TYPE_FROM_CLASS (class),
+ G_SIGNAL_RUN_LAST,
+ 0,
+ NULL, NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+ signals[MIME_DATA_CHANGED] =
+ g_signal_new ("mime-data-changed",
+ G_TYPE_FROM_CLASS (class),
+ G_SIGNAL_RUN_LAST,
+ 0,
+ NULL, NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+}
diff --git a/src/nautilus-signaller.h b/src/nautilus-signaller.h
new file mode 100644
index 0000000..df9aeea
--- /dev/null
+++ b/src/nautilus-signaller.h
@@ -0,0 +1,41 @@
+
+/*
+ * Nautilus
+ *
+ * Copyright (C) 1999, 2000 Eazel, Inc.
+ *
+ * Nautilus is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * 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/>.
+ *
+ * Author: John Sullivan <sullivan@eazel.com>
+ */
+
+/* nautilus-signaller.h: Class to manage nautilus-wide signals that don't
+ * correspond to any particular object.
+ */
+
+#pragma once
+
+#include <glib-object.h>
+
+/* NautilusSignaller is a class that manages signals between
+ disconnected Nautilus code. Nautilus objects connect to these signals
+ so that other objects can cause them to be emitted later, without
+ the connecting and emit-causing objects needing to know about each
+ other. It seems a shame to have to invent a subclass and a special
+ object just for this purpose. Perhaps there's a better way to do
+ this kind of thing.
+*/
+
+/* Get the one and only NautilusSignaller to connect with or emit signals for */
+GObject *nautilus_signaller_get_current (void); \ No newline at end of file
diff --git a/src/nautilus-special-location-bar.c b/src/nautilus-special-location-bar.c
new file mode 100644
index 0000000..ae97645
--- /dev/null
+++ b/src/nautilus-special-location-bar.c
@@ -0,0 +1,324 @@
+/*
+ * Copyright (C) 2012 Red Hat, 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/>.
+ *
+ */
+
+#include "config.h"
+
+#include <glib/gi18n.h>
+#include <gtk/gtk.h>
+#include <string.h>
+
+#include "nautilus-dbus-launcher.h"
+#include "nautilus-global-preferences.h"
+#include "nautilus-special-location-bar.h"
+#include "nautilus-enum-types.h"
+
+struct _NautilusSpecialLocationBar
+{
+ AdwBin parent_instance;
+
+ GtkWidget *label;
+ GtkWidget *learn_more_label;
+ GtkWidget *button;
+ int button_response;
+ NautilusSpecialLocation special_location;
+};
+
+enum
+{
+ PROP_0,
+ PROP_SPECIAL_LOCATION,
+};
+
+enum
+{
+ SPECIAL_LOCATION_SHARING_RESPONSE = 1,
+ SPECIAL_LOCATION_TRASH_RESPONSE = 2,
+};
+
+G_DEFINE_TYPE (NautilusSpecialLocationBar, nautilus_special_location_bar, ADW_TYPE_BIN)
+
+static void
+on_info_bar_response (GtkInfoBar *infobar,
+ gint response_id,
+ gpointer user_data)
+{
+ NautilusSpecialLocationBar *bar = user_data;
+ GtkRoot *window = gtk_widget_get_root (GTK_WIDGET (bar));
+
+ switch (bar->button_response)
+ {
+ case SPECIAL_LOCATION_SHARING_RESPONSE:
+ {
+ GVariant *parameters;
+
+ parameters = g_variant_new_parsed ("('launch-panel', [<('sharing', @av [])>], "
+ "@a{sv} {})");
+ nautilus_dbus_launcher_call (nautilus_dbus_launcher_get (),
+ NAUTILUS_DBUS_LAUNCHER_SETTINGS,
+ "Activate",
+ parameters, GTK_WINDOW (window));
+ }
+ break;
+
+ case SPECIAL_LOCATION_TRASH_RESPONSE:
+ {
+ GVariant *parameters;
+
+ parameters = g_variant_new_parsed ("('launch-panel', [<('usage', @av [])>], "
+ "@a{sv} {})");
+ nautilus_dbus_launcher_call (nautilus_dbus_launcher_get (),
+ NAUTILUS_DBUS_LAUNCHER_SETTINGS,
+ "Activate",
+ parameters, GTK_WINDOW (window));
+ }
+ break;
+
+ default:
+ {
+ g_assert_not_reached ();
+ }
+ }
+}
+
+static gchar *
+parse_old_files_age_preferences_value (void)
+{
+ guint old_files_age = g_settings_get_uint (gnome_privacy_preferences, "old-files-age");
+
+ switch (old_files_age)
+ {
+ case 0:
+ {
+ return g_strdup (_("Items in Trash older than 1 hour are automatically deleted"));
+ }
+
+ default:
+ {
+ return g_strdup_printf (ngettext ("Items in Trash older than %d day are automatically deleted",
+ "Items in Trash older than %d days are automatically deleted",
+ old_files_age),
+ old_files_age);
+ }
+ }
+}
+
+static void
+old_files_age_preferences_changed (GSettings *settings,
+ gchar *key,
+ gpointer user_data)
+{
+ NautilusSpecialLocationBar *bar;
+ g_autofree gchar *message = NULL;
+
+ g_assert (NAUTILUS_IS_SPECIAL_LOCATION_BAR (user_data));
+
+ bar = NAUTILUS_SPECIAL_LOCATION_BAR (user_data);
+
+ message = parse_old_files_age_preferences_value ();
+
+ gtk_label_set_text (GTK_LABEL (bar->label), message);
+}
+
+static void
+set_special_location (NautilusSpecialLocationBar *bar,
+ NautilusSpecialLocation location)
+{
+ char *message;
+ char *learn_more_markup = NULL;
+ char *button_label = NULL;
+
+ switch (location)
+ {
+ case NAUTILUS_SPECIAL_LOCATION_TEMPLATES:
+ {
+ message = g_strdup (_("Put files in this folder to use them as templates for new documents."));
+ learn_more_markup = g_strdup (_("<a href=\"help:gnome-help/files-templates\" title=\"GNOME help for templates\">Learn more…</a>"));
+ }
+ break;
+
+ case NAUTILUS_SPECIAL_LOCATION_SCRIPTS:
+ {
+ message = g_strdup (_("Executable files in this folder will appear in the Scripts menu."));
+ }
+ break;
+
+ case NAUTILUS_SPECIAL_LOCATION_SHARING:
+ {
+ message = g_strdup (_("Turn on File Sharing to share the contents of this folder over the network."));
+ button_label = _("Sharing Settings");
+ bar->button_response = SPECIAL_LOCATION_SHARING_RESPONSE;
+ }
+ break;
+
+ case NAUTILUS_SPECIAL_LOCATION_TRASH:
+ {
+ message = parse_old_files_age_preferences_value ();
+ button_label = _("_Settings");
+ bar->button_response = SPECIAL_LOCATION_TRASH_RESPONSE;
+
+ g_signal_connect_object (gnome_privacy_preferences,
+ "changed::old-files-age",
+ G_CALLBACK (old_files_age_preferences_changed),
+ bar, 0);
+ }
+ break;
+
+ default:
+ {
+ g_assert_not_reached ();
+ }
+ }
+
+ gtk_label_set_text (GTK_LABEL (bar->label), message);
+ g_free (message);
+
+ gtk_widget_show (bar->label);
+
+ if (learn_more_markup)
+ {
+ gtk_label_set_markup (GTK_LABEL (bar->learn_more_label),
+ learn_more_markup);
+ gtk_widget_show (bar->learn_more_label);
+ g_free (learn_more_markup);
+ }
+ else
+ {
+ gtk_widget_hide (bar->learn_more_label);
+ }
+
+ if (button_label)
+ {
+ gtk_button_set_label (GTK_BUTTON (bar->button), button_label);
+ gtk_widget_show (bar->button);
+ }
+ else
+ {
+ gtk_widget_hide (bar->button);
+ }
+}
+
+static void
+nautilus_special_location_bar_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ NautilusSpecialLocationBar *bar;
+
+ bar = NAUTILUS_SPECIAL_LOCATION_BAR (object);
+
+ switch (prop_id)
+ {
+ case PROP_SPECIAL_LOCATION:
+ {
+ set_special_location (bar, g_value_get_enum (value));
+ }
+ break;
+
+ default:
+ {
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+ break;
+ }
+}
+
+static void
+nautilus_special_location_bar_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ NautilusSpecialLocationBar *bar;
+
+ bar = NAUTILUS_SPECIAL_LOCATION_BAR (object);
+
+ switch (prop_id)
+ {
+ case PROP_SPECIAL_LOCATION:
+ {
+ g_value_set_enum (value, bar->special_location);
+ }
+ break;
+
+ default:
+ {
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+ break;
+ }
+}
+
+static void
+nautilus_special_location_bar_class_init (NautilusSpecialLocationBarClass *klass)
+{
+ GObjectClass *object_class;
+
+ object_class = G_OBJECT_CLASS (klass);
+ object_class->get_property = nautilus_special_location_bar_get_property;
+ object_class->set_property = nautilus_special_location_bar_set_property;
+
+ g_object_class_install_property (object_class,
+ PROP_SPECIAL_LOCATION,
+ g_param_spec_enum ("special-location",
+ "special-location",
+ "special-location",
+ NAUTILUS_TYPE_SPECIAL_LOCATION,
+ NAUTILUS_SPECIAL_LOCATION_TEMPLATES,
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT));
+}
+
+static void
+nautilus_special_location_bar_init (NautilusSpecialLocationBar *bar)
+{
+ GtkWidget *info_bar;
+ PangoAttrList *attrs;
+ GtkWidget *button;
+
+ info_bar = gtk_info_bar_new ();
+ gtk_info_bar_set_message_type (GTK_INFO_BAR (info_bar), GTK_MESSAGE_QUESTION);
+ gtk_widget_show (info_bar);
+ adw_bin_set_child (ADW_BIN (bar), info_bar);
+
+ attrs = pango_attr_list_new ();
+ pango_attr_list_insert (attrs, pango_attr_weight_new (PANGO_WEIGHT_BOLD));
+ bar->label = gtk_label_new (NULL);
+ gtk_label_set_attributes (GTK_LABEL (bar->label), attrs);
+ pango_attr_list_unref (attrs);
+
+ gtk_label_set_ellipsize (GTK_LABEL (bar->label), PANGO_ELLIPSIZE_END);
+ gtk_info_bar_add_child (GTK_INFO_BAR (info_bar), bar->label);
+
+ button = gtk_info_bar_add_button (GTK_INFO_BAR (info_bar), "", GTK_RESPONSE_OK);
+ bar->button = button;
+
+ bar->learn_more_label = gtk_label_new (NULL);
+ gtk_widget_set_hexpand (bar->learn_more_label, TRUE);
+ gtk_widget_set_halign (bar->learn_more_label, GTK_ALIGN_END);
+ gtk_info_bar_add_child (GTK_INFO_BAR (info_bar), bar->learn_more_label);
+
+ g_signal_connect (info_bar, "response", G_CALLBACK (on_info_bar_response), bar);
+}
+
+GtkWidget *
+nautilus_special_location_bar_new (NautilusSpecialLocation location)
+{
+ return g_object_new (NAUTILUS_TYPE_SPECIAL_LOCATION_BAR,
+ "special-location", location,
+ NULL);
+}
diff --git a/src/nautilus-special-location-bar.h b/src/nautilus-special-location-bar.h
new file mode 100644
index 0000000..52d67e3
--- /dev/null
+++ b/src/nautilus-special-location-bar.h
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2012 Red Hat, 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/>.
+ *
+ */
+
+#pragma once
+
+#include <gtk/gtk.h>
+#include <gio/gio.h>
+#include <libadwaita-1/adwaita.h>
+
+G_BEGIN_DECLS
+
+#define NAUTILUS_TYPE_SPECIAL_LOCATION_BAR (nautilus_special_location_bar_get_type ())
+G_DECLARE_FINAL_TYPE (NautilusSpecialLocationBar, nautilus_special_location_bar, NAUTILUS, SPECIAL_LOCATION_BAR, AdwBin)
+
+typedef enum {
+ NAUTILUS_SPECIAL_LOCATION_TEMPLATES,
+ NAUTILUS_SPECIAL_LOCATION_SCRIPTS,
+ NAUTILUS_SPECIAL_LOCATION_SHARING,
+ NAUTILUS_SPECIAL_LOCATION_TRASH,
+} NautilusSpecialLocation;
+
+GtkWidget *nautilus_special_location_bar_new (NautilusSpecialLocation location);
+
+G_END_DECLS
diff --git a/src/nautilus-star-cell.c b/src/nautilus-star-cell.c
new file mode 100644
index 0000000..9f21028
--- /dev/null
+++ b/src/nautilus-star-cell.c
@@ -0,0 +1,173 @@
+/*
+ * Copyright (C) 2022 António Fernandes <antoniof@gnome.org>
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#include "nautilus-star-cell.h"
+#include "nautilus-tag-manager.h"
+
+struct _NautilusStarCell
+{
+ NautilusViewCell parent_instance;
+
+ GSignalGroup *item_signal_group;
+
+ GtkImage *star;
+};
+
+G_DEFINE_TYPE (NautilusStarCell, nautilus_star_cell, NAUTILUS_TYPE_VIEW_CELL)
+
+static void
+on_star_click_released (GtkGestureClick *gesture,
+ gint n_press,
+ gdouble x,
+ gdouble y,
+ gpointer user_data)
+{
+ NautilusStarCell *self = user_data;
+ NautilusTagManager *tag_manager = nautilus_tag_manager_get ();
+ g_autoptr (NautilusViewItem) item = NULL;
+ NautilusFile *file;
+ g_autofree gchar *uri = NULL;
+
+ item = nautilus_view_cell_get_item (NAUTILUS_VIEW_CELL (self));
+ g_return_if_fail (item != NULL);
+ file = nautilus_view_item_get_file (item);
+ uri = nautilus_file_get_uri (file);
+
+ if (nautilus_tag_manager_file_is_starred (tag_manager, uri))
+ {
+ nautilus_tag_manager_unstar_files (tag_manager,
+ G_OBJECT (item),
+ &(GList){ file, NULL },
+ NULL,
+ NULL);
+ gtk_widget_remove_css_class (GTK_WIDGET (self->star), "added");
+ }
+ else
+ {
+ nautilus_tag_manager_star_files (tag_manager,
+ G_OBJECT (item),
+ &(GList){ file, NULL },
+ NULL,
+ NULL);
+ gtk_widget_add_css_class (GTK_WIDGET (self->star), "added");
+ }
+
+ gtk_gesture_set_state (GTK_GESTURE (gesture), GTK_EVENT_SEQUENCE_CLAIMED);
+}
+
+static void
+update_star (GtkImage *star,
+ NautilusFile *file)
+{
+ gboolean is_starred;
+ g_autofree gchar *file_uri = NULL;
+
+ g_return_if_fail (NAUTILUS_IS_FILE (file));
+
+ file_uri = nautilus_file_get_uri (file);
+ is_starred = nautilus_tag_manager_file_is_starred (nautilus_tag_manager_get (),
+ file_uri);
+
+ gtk_image_set_from_icon_name (star, is_starred ? "starred-symbolic" : "non-starred-symbolic");
+}
+
+static void
+on_file_changed (NautilusStarCell *self)
+{
+ g_autoptr (NautilusViewItem) item = NULL;
+ NautilusFile *file;
+ g_autofree gchar *string = NULL;
+
+ item = nautilus_view_cell_get_item (NAUTILUS_VIEW_CELL (self));
+ g_return_if_fail (item != NULL);
+ file = nautilus_view_item_get_file (item);
+
+ update_star (self->star, file);
+}
+
+static void
+on_starred_changed (NautilusTagManager *tag_manager,
+ GList *changed_files,
+ gpointer user_data)
+{
+ NautilusStarCell *self = user_data;
+ g_autoptr (NautilusViewItem) item = NULL;
+ NautilusFile *file;
+
+ item = nautilus_view_cell_get_item (NAUTILUS_VIEW_CELL (self));
+ if (item == NULL)
+ {
+ return;
+ }
+
+ file = nautilus_view_item_get_file (item);
+ if (g_list_find (changed_files, file))
+ {
+ update_star (self->star, file);
+ }
+}
+
+static void
+nautilus_star_cell_init (NautilusStarCell *self)
+{
+ GtkWidget *star;
+ GtkGesture *gesture;
+
+ /* Create star icon */
+ star = gtk_image_new ();
+ gtk_widget_set_halign (star, GTK_ALIGN_END);
+ gtk_widget_set_valign (star, GTK_ALIGN_CENTER);
+ gtk_widget_add_css_class (star, "dim-label");
+ gtk_widget_add_css_class (star, "star");
+ adw_bin_set_child (ADW_BIN (self), star);
+ self->star = GTK_IMAGE (star);
+
+ /* Make it clickable */
+ gesture = gtk_gesture_click_new ();
+ gtk_gesture_single_set_button (GTK_GESTURE_SINGLE (gesture), GDK_BUTTON_PRIMARY);
+ g_signal_connect (gesture, "released", G_CALLBACK (on_star_click_released), self);
+ gtk_widget_add_controller (star, GTK_EVENT_CONTROLLER (gesture));
+
+ /* Update on tag changes */
+ g_signal_connect_object (nautilus_tag_manager_get (), "starred-changed",
+ G_CALLBACK (on_starred_changed), self, 0);
+
+ /* Connect automatically to an item. */
+ self->item_signal_group = g_signal_group_new (NAUTILUS_TYPE_VIEW_ITEM);
+ g_signal_group_connect_swapped (self->item_signal_group, "file-changed",
+ (GCallback) on_file_changed, self);
+ g_signal_connect_object (self->item_signal_group, "bind",
+ (GCallback) on_file_changed, self,
+ G_CONNECT_SWAPPED);
+ g_object_bind_property (self, "item",
+ self->item_signal_group, "target",
+ G_BINDING_SYNC_CREATE);
+}
+
+static void
+nautilus_star_cell_finalize (GObject *object)
+{
+ NautilusStarCell *self = (NautilusStarCell *) object;
+
+ g_object_unref (self->item_signal_group);
+ G_OBJECT_CLASS (nautilus_star_cell_parent_class)->finalize (object);
+}
+
+static void
+nautilus_star_cell_class_init (NautilusStarCellClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->finalize = nautilus_star_cell_finalize;
+}
+
+NautilusViewCell *
+nautilus_star_cell_new (NautilusListBase *view)
+{
+ return NAUTILUS_VIEW_CELL (g_object_new (NAUTILUS_TYPE_STAR_CELL,
+ "view", view,
+ NULL));
+}
diff --git a/src/nautilus-star-cell.h b/src/nautilus-star-cell.h
new file mode 100644
index 0000000..415a745
--- /dev/null
+++ b/src/nautilus-star-cell.h
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2022 António Fernandes <antoniof@gnome.org>
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#pragma once
+
+#include "nautilus-view-cell.h"
+
+G_BEGIN_DECLS
+
+#define NAUTILUS_TYPE_STAR_CELL (nautilus_star_cell_get_type())
+
+G_DECLARE_FINAL_TYPE (NautilusStarCell, nautilus_star_cell, NAUTILUS, STAR_CELL, NautilusViewCell)
+
+NautilusViewCell * nautilus_star_cell_new (NautilusListBase *view);
+
+G_END_DECLS
diff --git a/src/nautilus-starred-directory.c b/src/nautilus-starred-directory.c
new file mode 100644
index 0000000..0fedbfb
--- /dev/null
+++ b/src/nautilus-starred-directory.c
@@ -0,0 +1,573 @@
+/* nautilus-starred-directory.c
+ *
+ * Copyright (C) 2017 Alexandru Pandelea <alexandru.pandelea@gmail.com>
+ *
+ * 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/>.
+ */
+
+#include "nautilus-starred-directory.h"
+#include "nautilus-tag-manager.h"
+#include "nautilus-file-utilities.h"
+#include "nautilus-directory-private.h"
+#include <glib/gi18n.h>
+
+struct _NautilusFavoriteDirectory
+{
+ NautilusDirectory parent_slot;
+
+ GList *files;
+
+ GList *monitor_list;
+ GList *callback_list;
+ GList *pending_callback_list;
+};
+
+typedef struct
+{
+ gboolean monitor_hidden_files;
+ NautilusFileAttributes monitor_attributes;
+
+ gconstpointer client;
+} FavoriteMonitor;
+
+typedef struct
+{
+ NautilusFavoriteDirectory *starred_directory;
+
+ NautilusDirectoryCallback callback;
+ gpointer callback_data;
+
+ NautilusFileAttributes wait_for_attributes;
+ gboolean wait_for_file_list;
+ GList *file_list;
+} FavoriteCallback;
+
+G_DEFINE_TYPE_WITH_CODE (NautilusFavoriteDirectory, nautilus_starred_directory, NAUTILUS_TYPE_DIRECTORY,
+ nautilus_ensure_extension_points ();
+ /* It looks like you’re implementing an extension point.
+ * Did you modify nautilus_ensure_extension_builtins() accordingly?
+ *
+ * • Yes
+ * • Doing it right now
+ */
+ g_io_extension_point_implement (NAUTILUS_DIRECTORY_PROVIDER_EXTENSION_POINT_NAME,
+ g_define_type_id,
+ NAUTILUS_STARRED_DIRECTORY_PROVIDER_NAME,
+ 0));
+
+static void
+file_changed (NautilusFile *file,
+ NautilusFavoriteDirectory *starred)
+{
+ GList list;
+
+ list.data = file;
+ list.next = NULL;
+
+ nautilus_directory_emit_files_changed (NAUTILUS_DIRECTORY (starred), &list);
+}
+
+static void
+disconnect_and_unmonitor_file (NautilusFile *file,
+ NautilusFavoriteDirectory *self)
+{
+ /* Disconnect change handler */
+ g_signal_handlers_disconnect_by_func (file, file_changed, self);
+
+ /* Remove monitors */
+ for (GList *m = self->monitor_list; m != NULL; m = m->next)
+ {
+ nautilus_file_monitor_remove (file, m->data);
+ }
+}
+
+static void
+nautilus_starred_directory_update_files (NautilusFavoriteDirectory *self)
+{
+ NautilusTagManager *tag_manager = nautilus_tag_manager_get ();
+ GList *l;
+ GList *tmp_l;
+ GList *new_starred_files;
+ GList *monitor_list;
+ FavoriteMonitor *monitor;
+ NautilusFile *file;
+ GHashTable *uri_table;
+ GList *files_added;
+ GList *files_removed;
+ gchar *uri;
+
+ files_added = NULL;
+ files_removed = NULL;
+
+ uri_table = g_hash_table_new_full (g_str_hash,
+ g_str_equal,
+ (GDestroyNotify) g_free,
+ NULL);
+
+ for (l = self->files; l != NULL; l = l->next)
+ {
+ g_hash_table_add (uri_table, nautilus_file_get_uri (NAUTILUS_FILE (l->data)));
+ }
+
+ new_starred_files = nautilus_tag_manager_get_starred_files (tag_manager);
+
+ for (l = new_starred_files; l != NULL; l = l->next)
+ {
+ if (!g_hash_table_contains (uri_table, l->data))
+ {
+ file = nautilus_file_get_by_uri ((gchar *) l->data);
+
+ for (monitor_list = self->monitor_list; monitor_list; monitor_list = monitor_list->next)
+ {
+ monitor = monitor_list->data;
+
+ /* Add monitors */
+ nautilus_file_monitor_add (file, monitor, monitor->monitor_attributes);
+ }
+
+ g_signal_connect (file, "changed", G_CALLBACK (file_changed), self);
+
+ files_added = g_list_prepend (files_added, file);
+ }
+ }
+
+ l = self->files;
+ while (l != NULL)
+ {
+ uri = nautilus_file_get_uri (NAUTILUS_FILE (l->data));
+
+ if (!nautilus_tag_manager_file_is_starred (tag_manager, uri))
+ {
+ files_removed = g_list_prepend (files_removed,
+ nautilus_file_ref (NAUTILUS_FILE (l->data)));
+
+ disconnect_and_unmonitor_file (NAUTILUS_FILE (l->data), self);
+
+ if (l == self->files)
+ {
+ self->files = g_list_delete_link (self->files, l);
+ l = self->files;
+ }
+ else
+ {
+ tmp_l = l->prev;
+ self->files = g_list_delete_link (self->files, l);
+ l = tmp_l->next;
+ }
+ }
+ else
+ {
+ l = l->next;
+ }
+
+ g_free (uri);
+ }
+
+ if (files_added)
+ {
+ nautilus_directory_emit_files_added (NAUTILUS_DIRECTORY (self), files_added);
+
+ for (l = files_added; l != NULL; l = l->next)
+ {
+ self->files = g_list_prepend (self->files, nautilus_file_ref (NAUTILUS_FILE (l->data)));
+ }
+ }
+
+ if (files_removed)
+ {
+ nautilus_directory_emit_files_changed (NAUTILUS_DIRECTORY (self), files_removed);
+ }
+
+ nautilus_file_list_free (files_added);
+ nautilus_file_list_free (files_removed);
+ g_hash_table_destroy (uri_table);
+}
+
+static void
+on_starred_files_changed (NautilusTagManager *tag_manager,
+ GList *changed_files,
+ gpointer user_data)
+{
+ NautilusFavoriteDirectory *self;
+
+ self = NAUTILUS_STARRED_DIRECTORY (user_data);
+
+ nautilus_starred_directory_update_files (self);
+}
+
+static gboolean
+real_contains_file (NautilusDirectory *directory,
+ NautilusFile *file)
+{
+ g_autofree gchar *uri = NULL;
+
+ uri = nautilus_file_get_uri (file);
+
+ return nautilus_tag_manager_file_is_starred (nautilus_tag_manager_get (), uri);
+}
+
+static gboolean
+real_is_editable (NautilusDirectory *directory)
+{
+ return FALSE;
+}
+
+static void
+real_call_when_ready (NautilusDirectory *directory,
+ NautilusFileAttributes file_attributes,
+ gboolean wait_for_file_list,
+ NautilusDirectoryCallback callback,
+ gpointer callback_data)
+{
+ GList *file_list;
+ NautilusFavoriteDirectory *starred;
+
+ starred = NAUTILUS_STARRED_DIRECTORY (directory);
+
+ file_list = nautilus_file_list_copy (starred->files);
+
+ callback (NAUTILUS_DIRECTORY (directory),
+ file_list,
+ callback_data);
+}
+
+static gboolean
+real_are_all_files_seen (NautilusDirectory *directory)
+{
+ return TRUE;
+}
+
+static void
+real_file_monitor_add (NautilusDirectory *directory,
+ gconstpointer client,
+ gboolean monitor_hidden_files,
+ NautilusFileAttributes file_attributes,
+ NautilusDirectoryCallback callback,
+ gpointer callback_data)
+{
+ GList *list;
+ FavoriteMonitor *monitor;
+ NautilusFavoriteDirectory *starred;
+ NautilusFile *file;
+
+ starred = NAUTILUS_STARRED_DIRECTORY (directory);
+
+ monitor = g_new0 (FavoriteMonitor, 1);
+ monitor->monitor_hidden_files = monitor_hidden_files;
+ monitor->monitor_attributes = file_attributes;
+ monitor->client = client;
+
+ starred->monitor_list = g_list_prepend (starred->monitor_list, monitor);
+
+ if (callback != NULL)
+ {
+ (*callback)(directory, starred->files, callback_data);
+ }
+
+ for (list = starred->files; list != NULL; list = list->next)
+ {
+ file = list->data;
+
+ /* Add monitors */
+ nautilus_file_monitor_add (file, monitor, file_attributes);
+ }
+}
+
+static void
+starred_monitor_destroy (FavoriteMonitor *monitor,
+ NautilusFavoriteDirectory *starred)
+{
+ GList *l;
+ NautilusFile *file;
+
+ for (l = starred->files; l != NULL; l = l->next)
+ {
+ file = l->data;
+
+ nautilus_file_monitor_remove (file, monitor);
+ }
+
+ g_free (monitor);
+}
+
+static void
+real_monitor_remove (NautilusDirectory *directory,
+ gconstpointer client)
+{
+ NautilusFavoriteDirectory *starred;
+ FavoriteMonitor *monitor;
+ GList *list;
+
+ starred = NAUTILUS_STARRED_DIRECTORY (directory);
+
+ for (list = starred->monitor_list; list != NULL; list = list->next)
+ {
+ monitor = list->data;
+
+ if (monitor->client != client)
+ {
+ continue;
+ }
+
+ starred->monitor_list = g_list_delete_link (starred->monitor_list, list);
+
+ starred_monitor_destroy (monitor, starred);
+
+ break;
+ }
+}
+
+static gboolean
+real_handles_location (GFile *location)
+{
+ g_autofree gchar *uri = NULL;
+
+ uri = g_file_get_uri (location);
+
+ if (eel_uri_is_starred (uri))
+ {
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static FavoriteCallback *
+starred_callback_find_pending (NautilusFavoriteDirectory *starred,
+ NautilusDirectoryCallback callback,
+ gpointer callback_data)
+{
+ FavoriteCallback *starred_callback;
+ GList *list;
+
+ for (list = starred->pending_callback_list; list != NULL; list = list->next)
+ {
+ starred_callback = list->data;
+
+ if (starred_callback->callback == callback &&
+ starred_callback->callback_data == callback_data)
+ {
+ return starred_callback;
+ }
+ }
+
+ return NULL;
+}
+
+static FavoriteCallback *
+starred_callback_find (NautilusFavoriteDirectory *starred,
+ NautilusDirectoryCallback callback,
+ gpointer callback_data)
+{
+ FavoriteCallback *starred_callback;
+ GList *list;
+
+ for (list = starred->callback_list; list != NULL; list = list->next)
+ {
+ starred_callback = list->data;
+
+ if (starred_callback->callback == callback &&
+ starred_callback->callback_data == callback_data)
+ {
+ return starred_callback;
+ }
+ }
+
+ return NULL;
+}
+
+static void
+starred_callback_destroy (FavoriteCallback *starred_callback)
+{
+ nautilus_file_list_free (starred_callback->file_list);
+
+ g_free (starred_callback);
+}
+
+static void
+real_cancel_callback (NautilusDirectory *directory,
+ NautilusDirectoryCallback callback,
+ gpointer callback_data)
+{
+ NautilusFavoriteDirectory *starred;
+ FavoriteCallback *starred_callback;
+
+ starred = NAUTILUS_STARRED_DIRECTORY (directory);
+ starred_callback = starred_callback_find (starred, callback, callback_data);
+
+ if (starred_callback)
+ {
+ starred->callback_list = g_list_remove (starred->callback_list, starred_callback);
+
+ starred_callback_destroy (starred_callback);
+
+ return;
+ }
+
+ /* Check for a pending callback */
+ starred_callback = starred_callback_find_pending (starred, callback, callback_data);
+
+ if (starred_callback)
+ {
+ starred->pending_callback_list = g_list_remove (starred->pending_callback_list, starred_callback);
+
+ starred_callback_destroy (starred_callback);
+ }
+}
+
+static GList *
+real_get_file_list (NautilusDirectory *directory)
+{
+ NautilusFavoriteDirectory *starred;
+
+ starred = NAUTILUS_STARRED_DIRECTORY (directory);
+
+ return nautilus_file_list_copy (starred->files);
+}
+
+static void
+nautilus_starred_directory_set_files (NautilusFavoriteDirectory *self)
+{
+ GList *starred_files;
+ NautilusFile *file;
+ GList *l;
+ GList *file_list;
+ FavoriteMonitor *monitor;
+ GList *monitor_list;
+
+ file_list = NULL;
+
+ starred_files = nautilus_tag_manager_get_starred_files (nautilus_tag_manager_get ());
+
+ for (l = starred_files; l != NULL; l = l->next)
+ {
+ file = nautilus_file_get_by_uri ((gchar *) l->data);
+
+ g_signal_connect (file, "changed", G_CALLBACK (file_changed), self);
+
+ for (monitor_list = self->monitor_list; monitor_list; monitor_list = monitor_list->next)
+ {
+ monitor = monitor_list->data;
+
+ /* Add monitors */
+ nautilus_file_monitor_add (file, monitor, monitor->monitor_attributes);
+ }
+
+ file_list = g_list_prepend (file_list, file);
+ }
+
+ nautilus_directory_emit_files_added (NAUTILUS_DIRECTORY (self), file_list);
+
+ self->files = file_list;
+}
+
+static void
+real_force_reload (NautilusDirectory *directory)
+{
+ NautilusFavoriteDirectory *self = NAUTILUS_STARRED_DIRECTORY (directory);
+
+ /* Unset current file list */
+ g_list_foreach (self->files, (GFunc) disconnect_and_unmonitor_file, self);
+ g_clear_list (&self->files, g_object_unref);
+
+ /* Set a fresh file list */
+ nautilus_starred_directory_set_files (self);
+}
+
+static void
+nautilus_starred_directory_finalize (GObject *object)
+{
+ NautilusFavoriteDirectory *self;
+
+ self = NAUTILUS_STARRED_DIRECTORY (object);
+
+ g_signal_handlers_disconnect_by_func (nautilus_tag_manager_get (),
+ on_starred_files_changed,
+ self);
+
+ nautilus_file_list_free (self->files);
+
+ G_OBJECT_CLASS (nautilus_starred_directory_parent_class)->finalize (object);
+}
+
+static void
+nautilus_starred_directory_dispose (GObject *object)
+{
+ NautilusFavoriteDirectory *starred;
+ GList *l;
+
+ starred = NAUTILUS_STARRED_DIRECTORY (object);
+
+ /* Remove file connections */
+ g_list_foreach (starred->files, (GFunc) disconnect_and_unmonitor_file, starred);
+
+ /* Remove search monitors */
+ if (starred->monitor_list)
+ {
+ for (l = starred->monitor_list; l != NULL; l = l->next)
+ {
+ starred_monitor_destroy ((FavoriteMonitor *) l->data, starred);
+ }
+
+ g_list_free (starred->monitor_list);
+ starred->monitor_list = NULL;
+ }
+
+ G_OBJECT_CLASS (nautilus_starred_directory_parent_class)->dispose (object);
+}
+
+static void
+nautilus_starred_directory_class_init (NautilusFavoriteDirectoryClass *klass)
+{
+ GObjectClass *oclass;
+ NautilusDirectoryClass *directory_class;
+
+ oclass = G_OBJECT_CLASS (klass);
+ directory_class = NAUTILUS_DIRECTORY_CLASS (klass);
+
+ oclass->finalize = nautilus_starred_directory_finalize;
+ oclass->dispose = nautilus_starred_directory_dispose;
+
+ directory_class->handles_location = real_handles_location;
+ directory_class->contains_file = real_contains_file;
+ directory_class->is_editable = real_is_editable;
+ directory_class->force_reload = real_force_reload;
+ directory_class->call_when_ready = real_call_when_ready;
+ directory_class->are_all_files_seen = real_are_all_files_seen;
+ directory_class->file_monitor_add = real_file_monitor_add;
+ directory_class->file_monitor_remove = real_monitor_remove;
+ directory_class->cancel_callback = real_cancel_callback;
+ directory_class->get_file_list = real_get_file_list;
+}
+
+NautilusFavoriteDirectory *
+nautilus_starred_directory_new (void)
+{
+ NautilusFavoriteDirectory *self;
+
+ self = g_object_new (NAUTILUS_TYPE_STARRED_DIRECTORY, NULL);
+
+ return self;
+}
+
+static void
+nautilus_starred_directory_init (NautilusFavoriteDirectory *self)
+{
+ g_signal_connect (nautilus_tag_manager_get (),
+ "starred-changed",
+ (GCallback) on_starred_files_changed,
+ self);
+
+ nautilus_starred_directory_set_files (self);
+}
diff --git a/src/nautilus-starred-directory.h b/src/nautilus-starred-directory.h
new file mode 100644
index 0000000..0ced846
--- /dev/null
+++ b/src/nautilus-starred-directory.h
@@ -0,0 +1,33 @@
+/* nautilus-starred-directory.h
+ *
+ * Copyright (C) 2017 Alexandru Pandelea <alexandru.pandelea@gmail.com>
+ *
+ * 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/>.
+ */
+
+#pragma once
+
+#include "nautilus-directory.h"
+
+G_BEGIN_DECLS
+
+#define NAUTILUS_STARRED_DIRECTORY_PROVIDER_NAME "starred-directory-provider"
+
+#define NAUTILUS_TYPE_STARRED_DIRECTORY (nautilus_starred_directory_get_type ())
+
+G_DECLARE_FINAL_TYPE (NautilusFavoriteDirectory, nautilus_starred_directory, NAUTILUS, STARRED_DIRECTORY, NautilusDirectory);
+
+NautilusFavoriteDirectory* nautilus_starred_directory_new (void);
+
+G_END_DECLS \ No newline at end of file
diff --git a/src/nautilus-tag-manager.c b/src/nautilus-tag-manager.c
new file mode 100644
index 0000000..959eee8
--- /dev/null
+++ b/src/nautilus-tag-manager.c
@@ -0,0 +1,1019 @@
+/* nautilus-tag-manager.c
+ *
+ * Copyright (C) 2017 Alexandru Pandelea <alexandru.pandelea@gmail.com>
+ * Copyright (C) 2020 Sam Thursfield <sam@afuera.me.uk>
+ *
+ * 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/>.
+ */
+
+#include "nautilus-tag-manager.h"
+#include "nautilus-file.h"
+#include "nautilus-file-undo-operations.h"
+#include "nautilus-file-undo-manager.h"
+#include "nautilus-tracker-utilities.h"
+#define DEBUG_FLAG NAUTILUS_DEBUG_TAG_MANAGER
+#include "nautilus-debug.h"
+
+#include <gio/gunixinputstream.h>
+#include <tracker-sparql.h>
+
+#include "config.h"
+
+struct _NautilusTagManager
+{
+ GObject object;
+
+ gboolean database_ok;
+ TrackerSparqlConnection *db;
+ TrackerNotifier *notifier;
+
+ TrackerSparqlStatement *query_starred_files;
+ TrackerSparqlStatement *query_file_is_starred;
+
+ GHashTable *starred_file_uris;
+ GFile *home;
+
+ GCancellable *cancellable;
+};
+
+G_DEFINE_TYPE (NautilusTagManager, nautilus_tag_manager, G_TYPE_OBJECT);
+
+static NautilusTagManager *tag_manager = NULL;
+
+/* See nautilus_tag_manager_new_dummy() documentation for details. */
+static gboolean make_dummy_instance = FALSE;
+
+typedef struct
+{
+ NautilusTagManager *tag_manager;
+ GTask *task;
+ GList *selection;
+ gboolean star;
+} UpdateData;
+
+enum
+{
+ STARRED_CHANGED,
+ LAST_SIGNAL
+};
+
+#define QUERY_STARRED_FILES \
+ "SELECT ?file " \
+ "{ " \
+ " ?file a nautilus:File ; " \
+ " nautilus:starred true . " \
+ "}"
+
+#define QUERY_FILE_IS_STARRED \
+ "ASK " \
+ "{ " \
+ " ~file a nautilus:File ; " \
+ " nautilus:starred true . " \
+ "}"
+
+static guint signals[LAST_SIGNAL];
+
+/* Limit to 10MB output from Tracker -- surely, nobody has over a million starred files. */
+#define TRACKER2_MAX_IMPORT_BYTES 10 * 1024 * 1024
+
+static gchar *
+tracker2_migration_stamp (void)
+{
+ return g_build_filename (g_get_user_data_dir (), "nautilus", "tracker2-migration-complete", NULL);
+}
+
+static void
+start_query_or_update (TrackerSparqlConnection *db,
+ GString *query,
+ GAsyncReadyCallback callback,
+ gpointer user_data,
+ gboolean is_query,
+ GCancellable *cancellable)
+{
+ g_autoptr (GError) error = NULL;
+
+ if (!db)
+ {
+ g_message ("nautilus-tag-manager: No Tracker connection");
+ return;
+ }
+
+ if (is_query)
+ {
+ tracker_sparql_connection_query_async (db,
+ query->str,
+ cancellable,
+ callback,
+ user_data);
+ }
+ else
+ {
+ tracker_sparql_connection_update_async (db,
+ query->str,
+ cancellable,
+ callback,
+ user_data);
+ }
+}
+
+static void
+on_update_callback (GObject *object,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ TrackerSparqlConnection *db;
+ GError *error;
+ UpdateData *data;
+
+ data = user_data;
+
+ error = NULL;
+
+ db = TRACKER_SPARQL_CONNECTION (object);
+
+ tracker_sparql_connection_update_finish (db, result, &error);
+
+ if (error == NULL)
+ {
+ /* FIXME: make sure data->tag_manager->starred_file_uris is up to date */
+
+ if (!nautilus_file_undo_manager_is_operating ())
+ {
+ NautilusFileUndoInfo *undo_info;
+
+ undo_info = nautilus_file_undo_info_starred_new (data->selection, data->star);
+ nautilus_file_undo_manager_set_action (undo_info);
+
+ g_object_unref (undo_info);
+ }
+
+ g_signal_emit_by_name (data->tag_manager, "starred-changed", nautilus_file_list_copy (data->selection));
+
+ g_task_return_boolean (data->task, TRUE);
+ g_object_unref (data->task);
+ }
+ else if (error && error->code == G_IO_ERROR_CANCELLED)
+ {
+ g_error_free (error);
+ }
+ else
+ {
+ g_warning ("error updating tags: %s", error->message);
+ g_task_return_error (data->task, error);
+ g_object_unref (data->task);
+ }
+
+ nautilus_file_list_free (data->selection);
+ g_free (data);
+}
+
+/**
+ * nautilus_tag_manager_get_starred_files:
+ * @self: The tag manager singleton
+ *
+ * Returns: (element-type gchar*) (transfer container): A list of the starred urls.
+ */
+GList *
+nautilus_tag_manager_get_starred_files (NautilusTagManager *self)
+{
+ GHashTableIter starred_iter;
+ gchar *starred_uri;
+ GList *starred_file_uris = NULL;
+
+ g_hash_table_iter_init (&starred_iter, self->starred_file_uris);
+ while (g_hash_table_iter_next (&starred_iter, (gpointer *) &starred_uri, NULL))
+ {
+ g_autoptr (GFile) file = g_file_new_for_uri (starred_uri);
+
+ /* Skip files ouside $HOME, because we don't support starring these yet.
+ * See comment on nautilus_tag_manager_can_star_contents() */
+ if (g_file_has_prefix (file, self->home))
+ {
+ starred_file_uris = g_list_prepend (starred_file_uris, starred_uri);
+ }
+ }
+
+ return starred_file_uris;
+}
+
+static void
+on_get_starred_files_cursor_callback (GObject *object,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ TrackerSparqlCursor *cursor;
+ g_autoptr (GError) error = NULL;
+ const gchar *url;
+ gboolean success;
+ NautilusTagManager *self;
+ GList *changed_files;
+ NautilusFile *file;
+
+ cursor = TRACKER_SPARQL_CURSOR (object);
+
+ self = NAUTILUS_TAG_MANAGER (user_data);
+
+ success = tracker_sparql_cursor_next_finish (cursor, result, &error);
+
+ if (!success)
+ {
+ if (error != NULL)
+ {
+ g_warning ("Error on getting all tags cursor callback: %s", error->message);
+ }
+
+ g_clear_object (&cursor);
+ return;
+ }
+
+ url = tracker_sparql_cursor_get_string (cursor, 0, NULL);
+
+ g_hash_table_add (self->starred_file_uris, g_strdup (url));
+
+ file = nautilus_file_get_by_uri (url);
+
+ if (file)
+ {
+ changed_files = g_list_prepend (NULL, file);
+
+ g_signal_emit_by_name (self, "starred-changed", changed_files);
+
+ nautilus_file_list_free (changed_files);
+ }
+ else
+ {
+ DEBUG ("File %s is starred but not found", url);
+ }
+
+ tracker_sparql_cursor_next_async (cursor,
+ self->cancellable,
+ on_get_starred_files_cursor_callback,
+ self);
+}
+
+static void
+on_get_starred_files_query_callback (GObject *object,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ TrackerSparqlCursor *cursor;
+ g_autoptr (GError) error = NULL;
+ TrackerSparqlStatement *statement;
+ NautilusTagManager *self;
+
+ self = NAUTILUS_TAG_MANAGER (user_data);
+ statement = TRACKER_SPARQL_STATEMENT (object);
+
+ cursor = tracker_sparql_statement_execute_finish (statement,
+ result,
+ &error);
+
+ if (error != NULL)
+ {
+ if (error->code != G_IO_ERROR_CANCELLED)
+ {
+ g_warning ("Error on getting starred files: %s", error->message);
+ }
+ }
+ else
+ {
+ tracker_sparql_cursor_next_async (cursor,
+ self->cancellable,
+ on_get_starred_files_cursor_callback,
+ user_data);
+ }
+}
+
+static void
+nautilus_tag_manager_query_starred_files (NautilusTagManager *self,
+ GCancellable *cancellable)
+{
+ if (!self->database_ok)
+ {
+ g_message ("nautilus-tag-manager: No Tracker connection");
+ return;
+ }
+
+ tracker_sparql_statement_execute_async (self->query_starred_files,
+ cancellable,
+ on_get_starred_files_query_callback,
+ self);
+}
+
+static GString *
+nautilus_tag_manager_delete_tag (NautilusTagManager *self,
+ GList *selection)
+{
+ GString *query;
+ NautilusFile *file;
+ GList *l;
+
+ query = g_string_new ("DELETE DATA {");
+
+ for (l = selection; l != NULL; l = l->next)
+ {
+ g_autofree gchar *uri = NULL;
+
+ file = l->data;
+
+ uri = nautilus_file_get_uri (file);
+ g_string_append_printf (query,
+ " <%s> a nautilus:File ; "
+ " nautilus:starred true . ",
+ uri);
+ }
+
+ g_string_append (query, "}");
+
+ return query;
+}
+
+static GString *
+nautilus_tag_manager_insert_tag (NautilusTagManager *self,
+ GList *selection)
+{
+ GString *query;
+ NautilusFile *file;
+ GList *l;
+
+ query = g_string_new ("INSERT DATA {");
+
+ for (l = selection; l != NULL; l = l->next)
+ {
+ g_autofree gchar *uri = NULL;
+
+ file = l->data;
+
+ uri = nautilus_file_get_uri (file);
+ g_string_append_printf (query,
+ " <%s> a nautilus:File ; "
+ " nautilus:starred true . ",
+ uri);
+ }
+
+ g_string_append (query, "}");
+
+ return query;
+}
+
+gboolean
+nautilus_tag_manager_file_is_starred (NautilusTagManager *self,
+ const gchar *file_uri)
+{
+ return g_hash_table_contains (self->starred_file_uris, file_uri);
+}
+
+void
+nautilus_tag_manager_star_files (NautilusTagManager *self,
+ GObject *object,
+ GList *selection,
+ GAsyncReadyCallback callback,
+ GCancellable *cancellable)
+{
+ GString *query;
+ g_autoptr (GError) error = NULL;
+ GTask *task;
+ UpdateData *update_data;
+
+ DEBUG ("Starring %i files", g_list_length (selection));
+
+ task = g_task_new (object, cancellable, callback, NULL);
+
+ query = nautilus_tag_manager_insert_tag (self, selection);
+
+ update_data = g_new0 (UpdateData, 1);
+ update_data->task = task;
+ update_data->tag_manager = self;
+ update_data->selection = nautilus_file_list_copy (selection);
+ update_data->star = TRUE;
+
+ start_query_or_update (self->db,
+ query,
+ on_update_callback,
+ update_data,
+ FALSE,
+ cancellable);
+
+ g_string_free (query, TRUE);
+}
+
+void
+nautilus_tag_manager_unstar_files (NautilusTagManager *self,
+ GObject *object,
+ GList *selection,
+ GAsyncReadyCallback callback,
+ GCancellable *cancellable)
+{
+ GString *query;
+ GTask *task;
+ UpdateData *update_data;
+
+ DEBUG ("Unstarring %i files", g_list_length (selection));
+
+ task = g_task_new (object, cancellable, callback, NULL);
+
+ query = nautilus_tag_manager_delete_tag (self, selection);
+
+ update_data = g_new0 (UpdateData, 1);
+ update_data->task = task;
+ update_data->tag_manager = self;
+ update_data->selection = nautilus_file_list_copy (selection);
+ update_data->star = FALSE;
+
+ start_query_or_update (self->db,
+ query,
+ on_update_callback,
+ update_data,
+ FALSE,
+ cancellable);
+
+ g_string_free (query, TRUE);
+}
+
+static void
+on_tracker_notifier_events (TrackerNotifier *notifier,
+ gchar *service,
+ gchar *graph,
+ GPtrArray *events,
+ gpointer user_data)
+{
+ TrackerNotifierEvent *event;
+ NautilusTagManager *self;
+ int i;
+ const gchar *file_url;
+ GError *error = NULL;
+ TrackerSparqlCursor *cursor;
+ gboolean query_has_results = FALSE;
+ gboolean starred;
+ GList *changed_files;
+ NautilusFile *changed_file;
+
+ self = NAUTILUS_TAG_MANAGER (user_data);
+
+ for (i = 0; i < events->len; i++)
+ {
+ event = g_ptr_array_index (events, i);
+
+ file_url = tracker_notifier_event_get_urn (event);
+ changed_file = NULL;
+
+ DEBUG ("Got event for file %s", file_url);
+
+ tracker_sparql_statement_bind_string (self->query_file_is_starred, "file", file_url);
+ cursor = tracker_sparql_statement_execute (self->query_file_is_starred,
+ NULL,
+ &error);
+
+ if (cursor)
+ {
+ query_has_results = tracker_sparql_cursor_next (cursor, NULL, &error);
+ }
+
+ if (error || !cursor || !query_has_results)
+ {
+ g_warning ("Couldn't query the starred files database: '%s'", error ? error->message : "(null error)");
+ g_clear_error (&error);
+ return;
+ }
+
+ starred = tracker_sparql_cursor_get_boolean (cursor, 0);
+ if (starred)
+ {
+ gboolean inserted = g_hash_table_add (self->starred_file_uris, g_strdup (file_url));
+
+ if (inserted)
+ {
+ DEBUG ("Added %s to starred files list", file_url);
+ changed_file = nautilus_file_get_by_uri (file_url);
+ }
+ }
+ else
+ {
+ gboolean removed = g_hash_table_remove (self->starred_file_uris, file_url);
+
+ if (removed)
+ {
+ DEBUG ("Removed %s from starred files list", file_url);
+ changed_file = nautilus_file_get_by_uri (file_url);
+ }
+ }
+
+ if (changed_file)
+ {
+ changed_files = g_list_prepend (NULL, changed_file);
+
+ g_signal_emit_by_name (self, "starred-changed", changed_files);
+
+ nautilus_file_list_free (changed_files);
+ }
+
+ g_object_unref (cursor);
+ }
+}
+
+static void
+nautilus_tag_manager_finalize (GObject *object)
+{
+ NautilusTagManager *self;
+
+ self = NAUTILUS_TAG_MANAGER (object);
+
+ if (self->notifier != NULL)
+ {
+ g_signal_handlers_disconnect_by_func (self->notifier,
+ G_CALLBACK (on_tracker_notifier_events),
+ self);
+ }
+
+ g_cancellable_cancel (self->cancellable);
+ g_clear_object (&self->cancellable);
+ g_clear_object (&self->notifier);
+ g_clear_object (&self->db);
+ g_clear_object (&self->query_file_is_starred);
+ g_clear_object (&self->query_starred_files);
+
+ g_hash_table_destroy (self->starred_file_uris);
+ g_clear_object (&self->home);
+
+ G_OBJECT_CLASS (nautilus_tag_manager_parent_class)->finalize (object);
+}
+
+static void
+nautilus_tag_manager_class_init (NautilusTagManagerClass *klass)
+{
+ GObjectClass *oclass;
+
+ oclass = G_OBJECT_CLASS (klass);
+
+ oclass->finalize = nautilus_tag_manager_finalize;
+
+ signals[STARRED_CHANGED] = g_signal_new ("starred-changed",
+ NAUTILUS_TYPE_TAG_MANAGER,
+ G_SIGNAL_RUN_LAST,
+ 0,
+ NULL,
+ NULL,
+ g_cclosure_marshal_VOID__POINTER,
+ G_TYPE_NONE,
+ 1,
+ G_TYPE_POINTER);
+}
+
+/**
+ * nautilus_tag_manager_new:
+ *
+ * Returns: (transfer full): the #NautilusTagManager singleton object.
+ */
+NautilusTagManager *
+nautilus_tag_manager_new (void)
+{
+ if (tag_manager != NULL)
+ {
+ return g_object_ref (tag_manager);
+ }
+
+ tag_manager = g_object_new (NAUTILUS_TYPE_TAG_MANAGER, NULL);
+ g_object_add_weak_pointer (G_OBJECT (tag_manager), (gpointer) & tag_manager);
+
+ return tag_manager;
+}
+
+/**
+ * nautilus_tag_manager_new_dummy:
+ *
+ * Creates a dummy tag manager without database.
+ *
+ * Useful only for tests where the tag manager is needed but not being tested
+ * and we don't want to fail the tests due to irrelevant D-Bus failures.
+ *
+ * Returns: (transfer full): the #NautilusTagManager singleton object.
+ */
+NautilusTagManager *
+nautilus_tag_manager_new_dummy (void)
+{
+ make_dummy_instance = TRUE;
+ return nautilus_tag_manager_new ();
+}
+
+/**
+ * nautilus_tag_manager_get:
+ *
+ * Returns: (transfer none): the #NautilusTagManager singleton object.
+ */
+NautilusTagManager *
+nautilus_tag_manager_get (void)
+{
+ return tag_manager;
+}
+
+static gboolean
+setup_database (NautilusTagManager *self,
+ GCancellable *cancellable,
+ GError **error)
+{
+ const gchar *datadir;
+ g_autofree gchar *store_path = NULL;
+ g_autofree gchar *ontology_path = NULL;
+ g_autoptr (GFile) store = NULL;
+ g_autoptr (GFile) ontology = NULL;
+
+ /* Open private database to store nautilus:starred property. */
+
+ datadir = NAUTILUS_DATADIR;
+
+ store_path = g_build_filename (g_get_user_data_dir (), "nautilus", "tags", NULL);
+ ontology_path = g_build_filename (datadir, "ontology", NULL);
+
+ store = g_file_new_for_path (store_path);
+ ontology = g_file_new_for_path (ontology_path);
+
+ self->db = tracker_sparql_connection_new (TRACKER_SPARQL_CONNECTION_FLAGS_NONE,
+ store,
+ ontology,
+ cancellable,
+ error);
+
+ if (*error)
+ {
+ return FALSE;
+ }
+
+ /* Prepare reusable queries. */
+ self->query_file_is_starred = tracker_sparql_connection_query_statement (self->db,
+ QUERY_FILE_IS_STARRED,
+ cancellable,
+ error);
+
+ if (*error)
+ {
+ return FALSE;
+ }
+
+ self->query_starred_files = tracker_sparql_connection_query_statement (self->db,
+ QUERY_STARRED_FILES,
+ cancellable,
+ error);
+
+ if (*error)
+ {
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static void
+nautilus_tag_manager_init (NautilusTagManager *self)
+{
+ g_autoptr (GError) error = NULL;
+
+ self->starred_file_uris = g_hash_table_new_full (g_str_hash,
+ g_str_equal,
+ (GDestroyNotify) g_free,
+ /* values are keys */
+ NULL);
+ self->home = g_file_new_for_path (g_get_home_dir ());
+
+ if (make_dummy_instance)
+ {
+ /* Skip database initiation for nautilus_tag_manager_new_dummy(). */
+ return;
+ }
+
+ self->cancellable = g_cancellable_new ();
+ self->database_ok = setup_database (self, self->cancellable, &error);
+ if (error)
+ {
+ g_warning ("Unable to initialize tag manager: %s", error->message);
+ return;
+ }
+
+ self->notifier = tracker_sparql_connection_create_notifier (self->db);
+
+ nautilus_tag_manager_query_starred_files (self, self->cancellable);
+
+ g_signal_connect (self->notifier,
+ "events",
+ G_CALLBACK (on_tracker_notifier_events),
+ self);
+}
+
+gboolean
+nautilus_tag_manager_can_star_contents (NautilusTagManager *self,
+ GFile *directory)
+{
+ /* We only allow files to be starred inside the home directory for now.
+ * This avoids the starred files database growing too big.
+ * See https://gitlab.gnome.org/GNOME/nautilus/-/merge_requests/553#note_903108
+ */
+ return g_file_has_prefix (directory, self->home) || g_file_equal (directory, self->home);
+}
+
+static void
+update_moved_uris_callback (GObject *object,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ g_autoptr (GError) error = NULL;
+ g_autoptr (GPtrArray) new_uris = user_data;
+
+ tracker_sparql_connection_update_finish (TRACKER_SPARQL_CONNECTION (object),
+ result,
+ &error);
+
+ if (error != NULL && error->code != G_IO_ERROR_CANCELLED)
+ {
+ g_warning ("Error updating moved uris: %s", error->message);
+ }
+ else
+ {
+ g_autolist (NautilusFile) updated_files = NULL;
+
+ for (guint i = 0; i < new_uris->len; i++)
+ {
+ gchar *new_uri = g_ptr_array_index (new_uris, i);
+
+ updated_files = g_list_prepend (updated_files, nautilus_file_get_by_uri (new_uri));
+ }
+
+ g_signal_emit_by_name (tag_manager, "starred-changed", updated_files);
+ }
+}
+
+/**
+ * nautilus_tag_manager_update_moved_uris:
+ * @self: The tag manager singleton
+ * @src: The original location as a #GFile
+ * @dest: The new location as a #GFile
+ *
+ * Checks whether the rename/move operation (@src to @dest) has modified
+ * the URIs of any starred files, and updates the database accordingly.
+ */
+void
+nautilus_tag_manager_update_moved_uris (NautilusTagManager *self,
+ GFile *src,
+ GFile *dest)
+{
+ GHashTableIter starred_iter;
+ gchar *starred_uri;
+ g_autoptr (GPtrArray) old_uris = NULL;
+ g_autoptr (GPtrArray) new_uris = NULL;
+ g_autoptr (GString) query = NULL;
+
+ if (!self->database_ok)
+ {
+ g_message ("nautilus-tag-manager: No Tracker connection");
+ return;
+ }
+
+ old_uris = g_ptr_array_new ();
+ new_uris = g_ptr_array_new_with_free_func (g_free);
+
+ g_hash_table_iter_init (&starred_iter, self->starred_file_uris);
+ while (g_hash_table_iter_next (&starred_iter, (gpointer *) &starred_uri, NULL))
+ {
+ g_autoptr (GFile) starred_location = NULL;
+ g_autofree gchar *relative_path = NULL;
+
+ starred_location = g_file_new_for_uri (starred_uri);
+
+ if (g_file_equal (starred_location, src))
+ {
+ /* The moved file/folder is starred */
+ g_ptr_array_add (old_uris, starred_uri);
+ g_ptr_array_add (new_uris, g_file_get_uri (dest));
+ continue;
+ }
+
+ relative_path = g_file_get_relative_path (src, starred_location);
+ if (relative_path != NULL)
+ {
+ /* The starred file/folder is descendant of the moved/renamed directory */
+ g_autoptr (GFile) new_location = NULL;
+
+ new_location = g_file_resolve_relative_path (dest, relative_path);
+
+ g_ptr_array_add (old_uris, starred_uri);
+ g_ptr_array_add (new_uris, g_file_get_uri (new_location));
+ }
+ }
+
+ if (new_uris->len == 0)
+ {
+ /* No starred files are affected by this move/rename */
+ return;
+ }
+
+ DEBUG ("Updating moved URI for %i starred files", new_uris->len);
+
+ query = g_string_new ("DELETE DATA {");
+
+ for (guint i = 0; i < old_uris->len; i++)
+ {
+ gchar *old_uri = g_ptr_array_index (old_uris, i);
+ g_string_append_printf (query,
+ " <%s> a nautilus:File ; "
+ " nautilus:starred true . ",
+ old_uri);
+ }
+
+ g_string_append (query, "} ; INSERT DATA {");
+
+ for (guint i = 0; i < new_uris->len; i++)
+ {
+ gchar *new_uri = g_ptr_array_index (new_uris, i);
+ g_string_append_printf (query,
+ " <%s> a nautilus:File ; "
+ " nautilus:starred true . ",
+ new_uri);
+ }
+
+ g_string_append (query, "}");
+
+ /* Forward the new_uris list to later pass in the ::files-changed signal.
+ * There is no need to pass the old_uris because the file model is updated
+ * independently; we need only inform the view where to display stars now.
+ */
+ tracker_sparql_connection_update_async (self->db,
+ query->str,
+ self->cancellable,
+ update_moved_uris_callback,
+ g_steal_pointer (&new_uris));
+}
+
+static void
+process_tracker2_data_cb (GObject *source_object,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ NautilusTagManager *self = NAUTILUS_TAG_MANAGER (source_object);
+ g_autofree gchar *path = tracker2_migration_stamp ();
+ g_autoptr (GError) error = NULL;
+
+ tracker_sparql_connection_update_finish (self->db, res, &error);
+
+ if (!error)
+ {
+ DEBUG ("Data migration was successful. Creating stamp %s", path);
+
+ g_file_set_contents (path, "", -1, &error);
+ if (error)
+ {
+ g_warning ("Failed to create %s after migration: %s", path, error->message);
+ }
+ }
+ else
+ {
+ g_warning ("Error during data migration: %s", error->message);
+ }
+}
+
+static void
+process_tracker2_data (NautilusTagManager *self,
+ GBytes *key_file_data)
+{
+ g_autoptr (GKeyFile) key_file = NULL;
+ g_autoptr (GError) error = NULL;
+ gchar **groups, **group;
+ GList *selection = NULL;
+ NautilusFile *file;
+
+ key_file = g_key_file_new ();
+ g_key_file_load_from_bytes (key_file,
+ key_file_data,
+ G_KEY_FILE_NONE,
+ &error);
+ g_bytes_unref (key_file_data);
+
+ if (error)
+ {
+ g_warning ("Tracker 2 migration: Failed to parse key file data: %s", error->message);
+ return;
+ }
+
+ groups = g_key_file_get_groups (key_file, NULL);
+
+ for (group = groups; *group != NULL; group++)
+ {
+ file = nautilus_file_get_by_uri (*group);
+
+ if (file)
+ {
+ DEBUG ("Tracker 2 migration: starring %s", *group);
+ selection = g_list_prepend (selection, file);
+ }
+ else
+ {
+ DEBUG ("Tracker 2 migration: couldn't get NautilusFile for %s", *group);
+ }
+ }
+
+ nautilus_tag_manager_star_files (self,
+ G_OBJECT (self),
+ selection,
+ process_tracker2_data_cb,
+ self->cancellable);
+
+ g_free (groups);
+}
+
+static void
+export_tracker2_data_cb (GObject *source_object,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ GInputStream *stream = G_INPUT_STREAM (source_object);
+ NautilusTagManager *self = NAUTILUS_TAG_MANAGER (user_data);
+ g_autoptr (GError) error = NULL;
+ GBytes *key_file_data;
+
+ key_file_data = g_input_stream_read_bytes_finish (stream, res, &error);
+
+ if (key_file_data)
+ {
+ process_tracker2_data (self, key_file_data);
+ }
+ else
+ {
+ g_warning ("Tracker2 migration: Failed to read data from pipe: %s", error->message);
+ }
+}
+
+static void
+child_watch_cb (GPid pid,
+ gint status,
+ gpointer user_data)
+{
+ DEBUG ("Child %" G_PID_FORMAT " exited %s", pid,
+ g_spawn_check_exit_status (status, NULL) ? "normally" : "abnormally");
+ g_spawn_close_pid (pid);
+}
+
+static void
+export_tracker2_data (NautilusTagManager *self)
+{
+ gchar *argv[] = {"tracker3", "export", "--2to3", "files-starred", "--keyfile", NULL};
+ gint stdout_fd;
+ GPid child_pid;
+ g_autoptr (GError) error = NULL;
+ gboolean success;
+ g_autoptr (GInputStream) stream = NULL;
+ GSpawnFlags flags;
+
+ flags = G_SPAWN_DO_NOT_REAP_CHILD |
+ G_SPAWN_STDERR_TO_DEV_NULL |
+ G_SPAWN_SEARCH_PATH;
+ success = g_spawn_async_with_pipes (NULL,
+ argv,
+ NULL,
+ flags,
+ NULL,
+ NULL,
+ &child_pid,
+ NULL,
+ &stdout_fd,
+ NULL,
+ &error);
+ if (!success)
+ {
+ g_warning ("Tracker 2 migration: Couldn't run `tracker3`: %s", error->message);
+ return;
+ }
+
+ g_child_watch_add (child_pid, child_watch_cb, NULL);
+
+ stream = g_unix_input_stream_new (stdout_fd, TRUE);
+ g_input_stream_read_bytes_async (stream,
+ TRACKER2_MAX_IMPORT_BYTES,
+ G_PRIORITY_LOW,
+ self->cancellable,
+ export_tracker2_data_cb,
+ self);
+}
+
+void
+nautilus_tag_manager_maybe_migrate_tracker2_data (NautilusTagManager *self)
+{
+ g_autofree gchar *path = tracker2_migration_stamp ();
+
+ if (g_file_test (path, G_FILE_TEST_EXISTS))
+ {
+ DEBUG ("Tracker 2 migration: already completed.");
+ }
+ else
+ {
+ DEBUG ("Tracker 2 migration: starting.");
+ export_tracker2_data (self);
+ }
+}
diff --git a/src/nautilus-tag-manager.h b/src/nautilus-tag-manager.h
new file mode 100644
index 0000000..5bfc3c7
--- /dev/null
+++ b/src/nautilus-tag-manager.h
@@ -0,0 +1,61 @@
+/* nautilus-tag-manager.h
+ *
+ * Copyright (C) 2017 Alexandru Pandelea <alexandru.pandelea@gmail.com>
+ *
+ * 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/>.
+ */
+
+#pragma once
+
+#include <glib.h>
+#include <glib-object.h>
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+#define NAUTILUS_TYPE_TAG_MANAGER (nautilus_tag_manager_get_type ())
+
+G_DECLARE_FINAL_TYPE (NautilusTagManager, nautilus_tag_manager, NAUTILUS, TAG_MANAGER, GObject);
+
+NautilusTagManager* nautilus_tag_manager_new (void);
+NautilusTagManager* nautilus_tag_manager_new_dummy (void);
+NautilusTagManager* nautilus_tag_manager_get (void);
+
+GList* nautilus_tag_manager_get_starred_files (NautilusTagManager *self);
+
+void nautilus_tag_manager_star_files (NautilusTagManager *self,
+ GObject *object,
+ GList *selection,
+ GAsyncReadyCallback callback,
+ GCancellable *cancellable);
+
+void nautilus_tag_manager_unstar_files (NautilusTagManager *self,
+ GObject *object,
+ GList *selection,
+ GAsyncReadyCallback callback,
+ GCancellable *cancellable);
+
+
+gboolean nautilus_tag_manager_file_is_starred (NautilusTagManager *self,
+ const gchar *file_uri);
+
+gboolean nautilus_tag_manager_can_star_contents (NautilusTagManager *self,
+ GFile *directory);
+void nautilus_tag_manager_update_moved_uris (NautilusTagManager *tag_manager,
+ GFile *src,
+ GFile *dest);
+
+void nautilus_tag_manager_maybe_migrate_tracker2_data (NautilusTagManager *self);
+
+G_END_DECLS
diff --git a/src/nautilus-thumbnails.c b/src/nautilus-thumbnails.c
new file mode 100644
index 0000000..790b4e3
--- /dev/null
+++ b/src/nautilus-thumbnails.c
@@ -0,0 +1,598 @@
+/*
+ * nautilus-thumbnails.h: Thumbnail code for icon factory.
+ *
+ * Copyright (C) 2000, 2001 Eazel, Inc.
+ * Copyright (C) 2002, 2003 Red Hat, 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/>.
+ *
+ * Author: Andy Hertzfeld <andy@eazel.com>
+ */
+
+#include <config.h>
+#include "nautilus-thumbnails.h"
+
+#define GNOME_DESKTOP_USE_UNSTABLE_API
+
+#include "nautilus-directory-notify.h"
+#include "nautilus-global-preferences.h"
+#include "nautilus-file-utilities.h"
+#include <math.h>
+#include <eel/eel-graphic-effects.h>
+#include <eel/eel-string.h>
+#include <eel/eel-debug.h>
+#include <eel/eel-vfs-extensions.h>
+#include <gtk/gtk.h>
+#include <errno.h>
+#include <stdio.h>
+#include <string.h>
+#include <sys/wait.h>
+#include <unistd.h>
+#include <signal.h>
+#include <libgnome-desktop/gnome-desktop-thumbnail.h>
+#define DEBUG_FLAG NAUTILUS_DEBUG_THUMBNAILS
+#include "nautilus-debug.h"
+
+#include "nautilus-file-private.h"
+
+/* Should never be a reasonable actual mtime */
+#define INVALID_MTIME 0
+
+/* Cool-off period between last file modification time and thumbnail creation */
+#define THUMBNAIL_CREATION_DELAY_SECS 3
+
+static void thumbnail_thread_func (GTask *task,
+ gpointer source_object,
+ gpointer task_data,
+ GCancellable *cancellable);
+
+/* structure used for making thumbnails, associating a uri with where the thumbnail is to be stored */
+
+typedef struct
+{
+ char *image_uri;
+ char *mime_type;
+ time_t original_file_mtime;
+} NautilusThumbnailInfo;
+
+/*
+ * Thumbnail thread state.
+ */
+
+/* The id of the idle handler used to start the thumbnail thread, or 0 if no
+ * idle handler is currently registered. */
+static guint thumbnail_thread_starter_id = 0;
+
+/* Our mutex used when accessing data shared between the main thread and the
+ * thumbnail thread, i.e. the thumbnail_thread_is_running flag and the
+ * thumbnails_to_make list. */
+static GMutex thumbnails_mutex;
+
+/* A flag to indicate whether a thumbnail thread is running, so we don't
+ * start more than one. Lock thumbnails_mutex when accessing this. */
+static volatile gboolean thumbnail_thread_is_running = FALSE;
+
+/* The list of NautilusThumbnailInfo structs containing information about the
+ * thumbnails we are making. Lock thumbnails_mutex when accessing this. */
+static volatile GQueue thumbnails_to_make = G_QUEUE_INIT;
+
+/* Quickly check if uri is in thumbnails_to_make list */
+static GHashTable *thumbnails_to_make_hash = NULL;
+
+/* The currently thumbnailed icon. it also exists in the thumbnails_to_make list
+ * to avoid adding it again. Lock thumbnails_mutex when accessing this. */
+static NautilusThumbnailInfo *currently_thumbnailing = NULL;
+
+static gboolean
+get_file_mtime (const char *file_uri,
+ time_t *mtime)
+{
+ GFile *file;
+ GFileInfo *info;
+ gboolean ret;
+
+ ret = FALSE;
+ *mtime = INVALID_MTIME;
+
+ file = g_file_new_for_uri (file_uri);
+ info = g_file_query_info (file, G_FILE_ATTRIBUTE_TIME_MODIFIED, 0, NULL, NULL);
+ if (info)
+ {
+ if (g_file_info_has_attribute (info, G_FILE_ATTRIBUTE_TIME_MODIFIED))
+ {
+ *mtime = g_file_info_get_attribute_uint64 (info, G_FILE_ATTRIBUTE_TIME_MODIFIED);
+ ret = TRUE;
+ }
+
+ g_object_unref (info);
+ }
+ g_object_unref (file);
+
+ return ret;
+}
+
+static void
+free_thumbnail_info (NautilusThumbnailInfo *info)
+{
+ g_free (info->image_uri);
+ g_free (info->mime_type);
+ g_free (info);
+}
+
+static GnomeDesktopThumbnailFactory *
+get_thumbnail_factory (void)
+{
+ static GnomeDesktopThumbnailFactory *thumbnail_factory = NULL;
+
+ if (thumbnail_factory == NULL)
+ {
+ GdkDisplay *display = gdk_display_get_default ();
+ GListModel *monitors = gdk_display_get_monitors (display);
+ gint max_scale = 1;
+ GnomeDesktopThumbnailSize size;
+
+ for (guint i = 0; i < g_list_model_get_n_items (monitors); i++)
+ {
+ g_autoptr (GdkMonitor) monitor = g_list_model_get_item (monitors, i);
+
+ max_scale = MAX (max_scale, gdk_monitor_get_scale_factor (monitor));
+ }
+
+ if (max_scale <= 1)
+ {
+ size = GNOME_DESKTOP_THUMBNAIL_SIZE_LARGE;
+ }
+ else if (max_scale <= 2)
+ {
+ size = GNOME_DESKTOP_THUMBNAIL_SIZE_XLARGE;
+ }
+ else
+ {
+ size = GNOME_DESKTOP_THUMBNAIL_SIZE_XXLARGE;
+ }
+
+ thumbnail_factory = gnome_desktop_thumbnail_factory_new (size);
+ }
+
+ return thumbnail_factory;
+}
+
+
+/* This function is added as a very low priority idle function to start the
+ * thread to create any needed thumbnails. It is added with a very low priority
+ * so that it doesn't delay showing the directory in the icon/list views.
+ * We want to show the files in the directory as quickly as possible. */
+static gboolean
+thumbnail_thread_starter_cb (gpointer data)
+{
+ GTask *task;
+
+ DEBUG ("(Main Thread) Creating thumbnails thread\n");
+
+ /* We set a flag to indicate the thread is running, so we don't create
+ * a new one. We don't need to lock a mutex here, as the thumbnail
+ * thread isn't running yet. And we know we won't create the thread
+ * twice, as we also check thumbnail_thread_starter_id before
+ * scheduling this idle function. */
+ thumbnail_thread_is_running = TRUE;
+ task = g_task_new (NULL, NULL, NULL, NULL);
+ g_task_run_in_thread (task, thumbnail_thread_func);
+ thumbnail_thread_starter_id = 0;
+
+ g_object_unref (task);
+
+ return FALSE;
+}
+
+void
+nautilus_thumbnail_remove_from_queue (const char *file_uri)
+{
+ GList *node;
+
+ DEBUG ("(Remove from queue) Locking mutex\n");
+
+ g_mutex_lock (&thumbnails_mutex);
+
+ /*********************************
+ * MUTEX LOCKED
+ *********************************/
+
+ if (thumbnails_to_make_hash)
+ {
+ node = g_hash_table_lookup (thumbnails_to_make_hash, file_uri);
+
+ if (node && node->data != currently_thumbnailing)
+ {
+ g_hash_table_remove (thumbnails_to_make_hash, file_uri);
+ free_thumbnail_info (node->data);
+ g_queue_delete_link ((GQueue *) &thumbnails_to_make, node);
+ }
+ }
+
+ /*********************************
+ * MUTEX UNLOCKED
+ *********************************/
+
+ DEBUG ("(Remove from queue) Unlocking mutex\n");
+
+ g_mutex_unlock (&thumbnails_mutex);
+}
+
+void
+nautilus_thumbnail_prioritize (const char *file_uri)
+{
+ GList *node;
+
+ DEBUG ("(Prioritize) Locking mutex\n");
+
+ g_mutex_lock (&thumbnails_mutex);
+
+ /*********************************
+ * MUTEX LOCKED
+ *********************************/
+
+ if (thumbnails_to_make_hash)
+ {
+ node = g_hash_table_lookup (thumbnails_to_make_hash, file_uri);
+
+ if (node && node->data != currently_thumbnailing)
+ {
+ g_queue_unlink ((GQueue *) &thumbnails_to_make, node);
+ g_queue_push_head_link ((GQueue *) &thumbnails_to_make, node);
+ }
+ }
+
+ /*********************************
+ * MUTEX UNLOCKED
+ *********************************/
+
+ DEBUG ("(Prioritize) Unlocking mutex\n");
+
+ g_mutex_unlock (&thumbnails_mutex);
+}
+
+
+/***************************************************************************
+ * Thumbnail Thread Functions.
+ ***************************************************************************/
+
+
+/* This is a one-shot idle callback called from the main loop to call
+ * notify_file_changed() for a thumbnail. It frees the uri afterwards.
+ * We do this in an idle callback as I don't think nautilus_file_changed() is
+ * thread-safe. */
+static gboolean
+thumbnail_thread_notify_file_changed (gpointer image_uri)
+{
+ NautilusFile *file;
+
+ file = nautilus_file_get_by_uri ((char *) image_uri);
+
+ DEBUG ("(Thumbnail Thread) Notifying file changed file:%p uri: %s\n", file, (char *) image_uri);
+
+ if (file != NULL)
+ {
+ nautilus_file_set_is_thumbnailing (file, FALSE);
+ nautilus_file_invalidate_attributes (file,
+ NAUTILUS_FILE_ATTRIBUTE_THUMBNAIL |
+ NAUTILUS_FILE_ATTRIBUTE_INFO);
+ nautilus_file_unref (file);
+ }
+ g_free (image_uri);
+
+ return FALSE;
+}
+
+static GHashTable *
+get_types_table (void)
+{
+ static GHashTable *image_mime_types = NULL;
+ GSList *format_list, *l;
+ char **types;
+ int i;
+
+ if (image_mime_types == NULL)
+ {
+ image_mime_types =
+ g_hash_table_new_full (g_str_hash, g_str_equal,
+ g_free, NULL);
+
+ format_list = gdk_pixbuf_get_formats ();
+ for (l = format_list; l; l = l->next)
+ {
+ types = gdk_pixbuf_format_get_mime_types (l->data);
+
+ for (i = 0; types[i] != NULL; i++)
+ {
+ g_hash_table_insert (image_mime_types,
+ types [i],
+ GUINT_TO_POINTER (1));
+ }
+
+ g_free (types);
+ }
+
+ g_slist_free (format_list);
+ }
+
+ return image_mime_types;
+}
+
+static gboolean
+pixbuf_can_load_type (const char *mime_type)
+{
+ GHashTable *image_mime_types;
+
+ image_mime_types = get_types_table ();
+ if (g_hash_table_lookup (image_mime_types, mime_type))
+ {
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+gboolean
+nautilus_thumbnail_is_mimetype_limited_by_size (const char *mime_type)
+{
+ return pixbuf_can_load_type (mime_type);
+}
+
+gboolean
+nautilus_can_thumbnail (NautilusFile *file)
+{
+ GnomeDesktopThumbnailFactory *factory;
+ gboolean res;
+ char *uri;
+ time_t mtime;
+ char *mime_type;
+
+ uri = nautilus_file_get_uri (file);
+ mime_type = nautilus_file_get_mime_type (file);
+ mtime = nautilus_file_get_mtime (file);
+
+ factory = get_thumbnail_factory ();
+ res = gnome_desktop_thumbnail_factory_can_thumbnail (factory,
+ uri,
+ mime_type,
+ mtime);
+ g_free (mime_type);
+ g_free (uri);
+
+ return res;
+}
+
+void
+nautilus_create_thumbnail (NautilusFile *file)
+{
+ time_t file_mtime = 0;
+ NautilusThumbnailInfo *info;
+ NautilusThumbnailInfo *existing_info;
+ GList *existing, *node;
+
+ nautilus_file_set_is_thumbnailing (file, TRUE);
+
+ info = g_new0 (NautilusThumbnailInfo, 1);
+ info->image_uri = nautilus_file_get_uri (file);
+ info->mime_type = nautilus_file_get_mime_type (file);
+
+ /* Hopefully the NautilusFile will already have the image file mtime,
+ * so we can just use that. Otherwise we have to get it ourselves. */
+ if (file->details->got_file_info &&
+ file->details->file_info_is_up_to_date &&
+ file->details->mtime != 0)
+ {
+ file_mtime = file->details->mtime;
+ }
+ else
+ {
+ get_file_mtime (info->image_uri, &file_mtime);
+ }
+
+ info->original_file_mtime = file_mtime;
+
+
+ DEBUG ("(Main Thread) Locking mutex\n");
+
+ g_mutex_lock (&thumbnails_mutex);
+
+ /*********************************
+ * MUTEX LOCKED
+ *********************************/
+
+ if (thumbnails_to_make_hash == NULL)
+ {
+ thumbnails_to_make_hash = g_hash_table_new (g_str_hash,
+ g_str_equal);
+ }
+
+ /* Check if it is already in the list of thumbnails to make. */
+ existing = g_hash_table_lookup (thumbnails_to_make_hash, info->image_uri);
+ if (existing == NULL)
+ {
+ /* Add the thumbnail to the list. */
+ DEBUG ("(Main Thread) Adding thumbnail: %s\n",
+ info->image_uri);
+ g_queue_push_tail ((GQueue *) &thumbnails_to_make, info);
+ node = g_queue_peek_tail_link ((GQueue *) &thumbnails_to_make);
+ g_hash_table_insert (thumbnails_to_make_hash,
+ info->image_uri,
+ node);
+ /* If the thumbnail thread isn't running, and we haven't
+ * scheduled an idle function to start it up, do that now.
+ * We don't want to start it until all the other work is done,
+ * so the GUI will be updated as quickly as possible.*/
+ if (thumbnail_thread_is_running == FALSE &&
+ thumbnail_thread_starter_id == 0)
+ {
+ thumbnail_thread_starter_id = g_idle_add_full (G_PRIORITY_LOW, thumbnail_thread_starter_cb, NULL, NULL);
+ }
+ }
+ else
+ {
+ DEBUG ("(Main Thread) Updating non-current mtime: %s\n",
+ info->image_uri);
+
+ /* The file in the queue might need a new original mtime */
+ existing_info = existing->data;
+ existing_info->original_file_mtime = info->original_file_mtime;
+ free_thumbnail_info (info);
+ }
+
+ /*********************************
+ * MUTEX UNLOCKED
+ *********************************/
+
+ DEBUG ("(Main Thread) Unlocking mutex\n");
+
+ g_mutex_unlock (&thumbnails_mutex);
+}
+
+/* thumbnail_thread is invoked as a separate thread to to make thumbnails. */
+static void
+thumbnail_thread_func (GTask *task,
+ gpointer source_object,
+ gpointer task_data,
+ GCancellable *cancellable)
+{
+ GnomeDesktopThumbnailFactory *thumbnail_factory;
+ NautilusThumbnailInfo *info = NULL;
+ GdkPixbuf *pixbuf;
+ time_t current_orig_mtime = 0;
+ time_t current_time;
+ GList *node;
+ GError *error = NULL;
+
+ thumbnail_factory = get_thumbnail_factory ();
+
+ /* We loop until there are no more thumbails to make, at which point
+ * we exit the thread. */
+ for (;;)
+ {
+ DEBUG ("(Thumbnail Thread) Locking mutex\n");
+
+ g_mutex_lock (&thumbnails_mutex);
+
+ /*********************************
+ * MUTEX LOCKED
+ *********************************/
+
+ /* Pop the last thumbnail we just made off the head of the
+ * list and free it. I did this here so we only have to lock
+ * the mutex once per thumbnail, rather than once before
+ * creating it and once after.
+ * Don't pop the thumbnail off the queue if the original file
+ * mtime of the request changed. Then we need to redo the thumbnail.
+ */
+ if (currently_thumbnailing &&
+ currently_thumbnailing->original_file_mtime == current_orig_mtime)
+ {
+ g_assert (info == currently_thumbnailing);
+ node = g_hash_table_lookup (thumbnails_to_make_hash, info->image_uri);
+ g_assert (node != NULL);
+ g_hash_table_remove (thumbnails_to_make_hash, info->image_uri);
+ free_thumbnail_info (info);
+ g_queue_delete_link ((GQueue *) &thumbnails_to_make, node);
+ }
+ currently_thumbnailing = NULL;
+
+ /* If there are no more thumbnails to make, reset the
+ * thumbnail_thread_is_running flag, unlock the mutex, and
+ * exit the thread. */
+ if (g_queue_is_empty ((GQueue *) &thumbnails_to_make))
+ {
+ DEBUG ("(Thumbnail Thread) Exiting\n");
+
+ thumbnail_thread_is_running = FALSE;
+ g_mutex_unlock (&thumbnails_mutex);
+ return;
+ }
+
+ /* Get the next one to make. We leave it on the list until it
+ * is created so the main thread doesn't add it again while we
+ * are creating it. */
+ info = g_queue_peek_head ((GQueue *) &thumbnails_to_make);
+ currently_thumbnailing = info;
+ current_orig_mtime = info->original_file_mtime;
+ /*********************************
+ * MUTEX UNLOCKED
+ *********************************/
+
+ DEBUG ("(Thumbnail Thread) Unlocking mutex\n");
+
+ g_mutex_unlock (&thumbnails_mutex);
+
+ time (&current_time);
+
+ /* Don't try to create a thumbnail if the file was modified recently.
+ * This prevents constant re-thumbnailing of changing files. */
+ if (current_time < current_orig_mtime + THUMBNAIL_CREATION_DELAY_SECS &&
+ current_time >= current_orig_mtime)
+ {
+ DEBUG ("(Thumbnail Thread) Skipping: %s\n",
+ info->image_uri);
+
+ /* Reschedule thumbnailing via a change notification */
+ g_timeout_add_seconds (1, thumbnail_thread_notify_file_changed,
+ g_strdup (info->image_uri));
+ continue;
+ }
+
+ /* Create the thumbnail. */
+ DEBUG ("(Thumbnail Thread) Creating thumbnail: %s\n",
+ info->image_uri);
+
+ pixbuf = gnome_desktop_thumbnail_factory_generate_thumbnail (thumbnail_factory,
+ info->image_uri,
+ info->mime_type,
+ NULL,
+ &error);
+
+ if (pixbuf)
+ {
+ DEBUG ("(Thumbnail Thread) Saving thumbnail: %s\n",
+ info->image_uri);
+
+ gnome_desktop_thumbnail_factory_save_thumbnail (thumbnail_factory,
+ pixbuf,
+ info->image_uri,
+ current_orig_mtime,
+ NULL,
+ &error);
+ if (error)
+ {
+ DEBUG ("(Thumbnail Thread) Saving thumbnail failed: %s (%s)\n",
+ info->image_uri, error->message);
+ g_clear_error (&error);
+ }
+ g_object_unref (pixbuf);
+ }
+ else
+ {
+ DEBUG ("(Thumbnail Thread) Thumbnail failed: %s (%s)\n",
+ info->image_uri, error->message);
+ g_clear_error (&error);
+
+ gnome_desktop_thumbnail_factory_create_failed_thumbnail (thumbnail_factory,
+ info->image_uri,
+ current_orig_mtime,
+ NULL, NULL);
+ }
+ /* We need to call nautilus_file_changed(), but I don't think that is
+ * thread safe. So add an idle handler and do it from the main loop. */
+ g_idle_add_full (G_PRIORITY_HIGH_IDLE,
+ thumbnail_thread_notify_file_changed,
+ g_strdup (info->image_uri), NULL);
+ }
+}
diff --git a/src/nautilus-thumbnails.h b/src/nautilus-thumbnails.h
new file mode 100644
index 0000000..6babe68
--- /dev/null
+++ b/src/nautilus-thumbnails.h
@@ -0,0 +1,35 @@
+/*
+ nautilus-thumbnails.h: Thumbnail code for icon factory.
+
+ Copyright (C) 2000 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/>.
+
+ Author: Andy Hertzfeld <andy@eazel.com>
+*/
+
+#pragma once
+
+#include <gdk-pixbuf/gdk-pixbuf.h>
+#include "nautilus-file.h"
+
+/* Returns NULL if there's no thumbnail yet. */
+void nautilus_create_thumbnail (NautilusFile *file);
+gboolean nautilus_can_thumbnail (NautilusFile *file);
+gboolean nautilus_thumbnail_is_mimetype_limited_by_size
+ (const char *mime_type);
+
+/* Queue handling: */
+void nautilus_thumbnail_remove_from_queue (const char *file_uri);
+void nautilus_thumbnail_prioritize (const char *file_uri); \ No newline at end of file
diff --git a/src/nautilus-toolbar-menu-sections.h b/src/nautilus-toolbar-menu-sections.h
new file mode 100644
index 0000000..8f5a986
--- /dev/null
+++ b/src/nautilus-toolbar-menu-sections.h
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2016 Neil Herald <neil.herald@gmail.com>
+ *
+ * Nautilus is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * Nautilus is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include <glib.h>
+
+G_BEGIN_DECLS
+
+typedef struct _NautilusToolbarMenuSections NautilusToolbarMenuSections;
+
+struct _NautilusToolbarMenuSections {
+ GMenuModel *sort_section;
+};
+
+G_END_DECLS
diff --git a/src/nautilus-toolbar.c b/src/nautilus-toolbar.c
new file mode 100644
index 0000000..aad0fd3
--- /dev/null
+++ b/src/nautilus-toolbar.c
@@ -0,0 +1,644 @@
+/*
+ * Nautilus
+ *
+ * Copyright (C) 2011, Red Hat, Inc.
+ *
+ * Nautilus is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * Nautilus is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ *
+ * Author: Cosimo Cecchi <cosimoc@redhat.com>
+ *
+ */
+
+#include "nautilus-toolbar.h"
+
+#include <glib/gi18n.h>
+#include <math.h>
+
+#include "nautilus-application.h"
+#include "nautilus-bookmark.h"
+#include "nautilus-file-undo-manager.h"
+#include "nautilus-global-preferences.h"
+#include "nautilus-history-controls.h"
+#include "nautilus-location-entry.h"
+#include "nautilus-pathbar.h"
+#include "nautilus-progress-indicator.h"
+#include "nautilus-view-controls.h"
+#include "nautilus-ui-utilities.h"
+#include "nautilus-window.h"
+
+struct _NautilusToolbar
+{
+ AdwBin parent_instance;
+
+ NautilusWindow *window;
+
+ GtkWidget *path_bar_container;
+ GtkWidget *location_entry_container;
+ GtkWidget *search_container;
+ GtkWidget *toolbar_switcher;
+ GtkWidget *path_bar;
+ GtkWidget *location_entry;
+
+ gboolean show_location_entry;
+
+ GtkWidget *app_button;
+ GMenuModel *undo_redo_section;
+
+ GtkWidget *sidebar_button;
+ gboolean show_sidebar_button;
+ gboolean sidebar_button_active;
+
+ gboolean show_toolbar_children;
+
+ GtkWidget *search_button;
+
+ GtkWidget *location_entry_close_button;
+
+ /* active slot & bindings */
+ NautilusWindowSlot *window_slot;
+ GBinding *search_binding;
+};
+
+enum
+{
+ PROP_0,
+ PROP_SHOW_LOCATION_ENTRY,
+ PROP_WINDOW_SLOT,
+ PROP_SEARCHING,
+ PROP_SHOW_SIDEBAR_BUTTON,
+ PROP_SIDEBAR_BUTTON_ACTIVE,
+ PROP_SHOW_TOOLBAR_CHILDREN,
+ NUM_PROPERTIES
+};
+
+static GParamSpec *properties[NUM_PROPERTIES] = { NULL, };
+
+G_DEFINE_TYPE (NautilusToolbar, nautilus_toolbar, ADW_TYPE_BIN);
+
+static void nautilus_toolbar_set_window_slot_real (NautilusToolbar *self,
+ NautilusWindowSlot *slot);
+static void
+toolbar_update_appearance (NautilusToolbar *self)
+{
+ gboolean show_location_entry;
+
+ show_location_entry = self->show_location_entry ||
+ g_settings_get_boolean (nautilus_preferences,
+ NAUTILUS_PREFERENCES_ALWAYS_USE_LOCATION_ENTRY);
+
+ if (self->window_slot != NULL &&
+ nautilus_window_slot_get_searching (self->window_slot))
+ {
+ gtk_stack_set_visible_child_name (GTK_STACK (self->toolbar_switcher), "search");
+ }
+ else if (show_location_entry)
+ {
+ gtk_stack_set_visible_child_name (GTK_STACK (self->toolbar_switcher), "location");
+ }
+ else
+ {
+ gtk_stack_set_visible_child_name (GTK_STACK (self->toolbar_switcher), "pathbar");
+ }
+}
+
+static void
+update_action (NautilusToolbar *self,
+ const char *action_name,
+ gboolean enabled)
+{
+ GtkWidget *window;
+ GAction *action;
+
+ window = gtk_widget_get_ancestor (GTK_WIDGET (self), NAUTILUS_TYPE_WINDOW);
+
+ /* Activate/deactivate */
+ action = g_action_map_lookup_action (G_ACTION_MAP (window), action_name);
+ g_simple_action_set_enabled (G_SIMPLE_ACTION (action), enabled);
+}
+
+static void
+undo_manager_changed (NautilusToolbar *self)
+{
+ NautilusFileUndoInfo *info;
+ NautilusFileUndoManagerState undo_state;
+ gboolean undo_active;
+ gboolean redo_active;
+ g_autofree gchar *undo_label = NULL;
+ g_autofree gchar *redo_label = NULL;
+ g_autofree gchar *undo_description = NULL;
+ g_autofree gchar *redo_description = NULL;
+ gboolean is_undo;
+ g_autoptr (GMenu) updated_section = g_menu_new ();
+ g_autoptr (GMenuItem) undo_menu_item = NULL;
+ g_autoptr (GMenuItem) redo_menu_item = NULL;
+
+ /* Look up the last action from the undo manager, and get the text that
+ * describes it, e.g. "Undo Create Folder"/"Redo Create Folder"
+ */
+ info = nautilus_file_undo_manager_get_action ();
+ undo_state = nautilus_file_undo_manager_get_state ();
+ undo_active = redo_active = FALSE;
+ if (info != NULL && undo_state > NAUTILUS_FILE_UNDO_MANAGER_STATE_NONE)
+ {
+ is_undo = undo_state == NAUTILUS_FILE_UNDO_MANAGER_STATE_UNDO;
+
+ /* The last action can either be undone/redone. Activate the corresponding
+ * menu item and deactivate the other
+ */
+ undo_active = is_undo;
+ redo_active = !is_undo;
+ nautilus_file_undo_info_get_strings (info, &undo_label, &undo_description,
+ &redo_label, &redo_description);
+ }
+
+ /* Set the label of the undo and redo menu items, and activate them appropriately
+ */
+ if (!undo_active || undo_label == NULL)
+ {
+ g_free (undo_label);
+ undo_label = g_strdup (_("_Undo"));
+ }
+ undo_menu_item = g_menu_item_new (undo_label, "win.undo");
+ g_menu_append_item (updated_section, undo_menu_item);
+ update_action (self, "undo", undo_active);
+
+ if (!redo_active || redo_label == NULL)
+ {
+ g_free (redo_label);
+ redo_label = g_strdup (_("_Redo"));
+ }
+ redo_menu_item = g_menu_item_new (redo_label, "win.redo");
+ g_menu_append_item (updated_section, redo_menu_item);
+ update_action (self, "redo", redo_active);
+
+ nautilus_gmenu_set_from_model (G_MENU (self->undo_redo_section),
+ G_MENU_MODEL (updated_section));
+}
+
+static void
+on_location_entry_close (GtkWidget *close_button,
+ NautilusToolbar *self)
+{
+ nautilus_toolbar_set_show_location_entry (self, FALSE);
+}
+
+static void
+on_location_entry_focus_leave (GtkEventControllerFocus *controller,
+ gpointer user_data)
+{
+ NautilusToolbar *toolbar;
+ GtkWidget *focus_widget;
+
+ toolbar = NAUTILUS_TOOLBAR (user_data);
+
+ /* The location entry is a transient: it should hide when it loses focus.
+ *
+ * However, if we lose focus because the window itself lost focus, then the
+ * location entry should persist, because this may happen due to the user
+ * switching keyboard layout/input method; or they may want to copy/drop
+ * an path from another window/app. We detect this case by looking at the
+ * focus widget of the window (GtkRoot).
+ */
+
+ focus_widget = gtk_root_get_focus (gtk_widget_get_root (GTK_WIDGET (toolbar)));
+ if (focus_widget != NULL &&
+ gtk_widget_is_ancestor (focus_widget, GTK_WIDGET (toolbar->location_entry)))
+ {
+ return;
+ }
+
+ nautilus_toolbar_set_show_location_entry (toolbar, FALSE);
+}
+
+static void
+nautilus_toolbar_constructed (GObject *object)
+{
+ NautilusToolbar *self = NAUTILUS_TOOLBAR (object);
+ GtkEventController *controller;
+
+ self->path_bar = GTK_WIDGET (g_object_new (NAUTILUS_TYPE_PATH_BAR, NULL));
+ gtk_box_append (GTK_BOX (self->path_bar_container),
+ self->path_bar);
+
+ self->location_entry = nautilus_location_entry_new ();
+ gtk_box_append (GTK_BOX (self->location_entry_container),
+ self->location_entry);
+ self->location_entry_close_button = gtk_button_new_from_icon_name ("window-close-symbolic");
+ gtk_box_append (GTK_BOX (self->location_entry_container),
+ self->location_entry_close_button);
+ g_signal_connect (self->location_entry_close_button, "clicked",
+ G_CALLBACK (on_location_entry_close), self);
+
+ controller = gtk_event_controller_focus_new ();
+ gtk_widget_add_controller (self->location_entry, controller);
+ g_signal_connect (controller, "leave",
+ G_CALLBACK (on_location_entry_focus_leave), self);
+
+ /* Setting a max width on one entry to effectively set a max expansion for
+ * the whole title widget. */
+ gtk_editable_set_max_width_chars (GTK_EDITABLE (self->location_entry), 88);
+
+ gtk_widget_show (GTK_WIDGET (self));
+ toolbar_update_appearance (self);
+}
+
+static void
+nautilus_toolbar_init (NautilusToolbar *self)
+{
+ g_type_ensure (NAUTILUS_TYPE_HISTORY_CONTROLS);
+ g_type_ensure (NAUTILUS_TYPE_PROGRESS_INDICATOR);
+ g_type_ensure (NAUTILUS_TYPE_VIEW_CONTROLS);
+
+ gtk_widget_init_template (GTK_WIDGET (self));
+}
+
+void
+nautilus_toolbar_on_window_constructed (NautilusToolbar *self)
+{
+ /* undo_manager_changed manipulates the window actions, so set it up
+ * after the window and it's actions have been constructed
+ */
+ g_signal_connect_object (nautilus_file_undo_manager_get (),
+ "undo-changed",
+ G_CALLBACK (undo_manager_changed),
+ self,
+ G_CONNECT_SWAPPED);
+
+ undo_manager_changed (self);
+}
+
+static void
+nautilus_toolbar_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ NautilusToolbar *self = NAUTILUS_TOOLBAR (object);
+
+ switch (property_id)
+ {
+ case PROP_SHOW_LOCATION_ENTRY:
+ {
+ g_value_set_boolean (value, self->show_location_entry);
+ }
+ break;
+
+ case PROP_WINDOW_SLOT:
+ {
+ g_value_set_object (value, self->window_slot);
+ }
+ break;
+
+ case PROP_SEARCHING:
+ {
+ g_value_set_boolean (value, gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (self->search_button)));
+ }
+ break;
+
+ case PROP_SHOW_SIDEBAR_BUTTON:
+ {
+ g_value_set_boolean (value, self->show_sidebar_button);
+ }
+ break;
+
+ case PROP_SIDEBAR_BUTTON_ACTIVE:
+ {
+ g_value_set_boolean (value, self->sidebar_button_active);
+ }
+ break;
+
+ case PROP_SHOW_TOOLBAR_CHILDREN:
+ {
+ g_value_set_boolean (value, self->show_toolbar_children);
+ }
+ break;
+
+ default:
+ {
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ }
+ break;
+ }
+}
+
+static void
+on_window_slot_destroyed (gpointer data,
+ GObject *where_the_object_was)
+{
+ NautilusToolbar *self;
+
+ self = NAUTILUS_TOOLBAR (data);
+
+ /* The window slot was finalized, and the binding has already been removed.
+ * Null it here, so that dispose() does not trip over itself when removing it.
+ */
+ self->search_binding = NULL;
+
+ nautilus_toolbar_set_window_slot_real (self, NULL);
+}
+
+static void
+nautilus_toolbar_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ NautilusToolbar *self = NAUTILUS_TOOLBAR (object);
+
+ switch (property_id)
+ {
+ case PROP_SHOW_LOCATION_ENTRY:
+ {
+ nautilus_toolbar_set_show_location_entry (self, g_value_get_boolean (value));
+ }
+ break;
+
+ case PROP_WINDOW_SLOT:
+ {
+ nautilus_toolbar_set_window_slot (self, g_value_get_object (value));
+ }
+ break;
+
+ case PROP_SEARCHING:
+ {
+ gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (self->search_button),
+ g_value_get_boolean (value));
+ }
+ break;
+
+ case PROP_SHOW_SIDEBAR_BUTTON:
+ {
+ self->show_sidebar_button = g_value_get_boolean (value);
+ }
+ break;
+
+ case PROP_SIDEBAR_BUTTON_ACTIVE:
+ {
+ self->sidebar_button_active = g_value_get_boolean (value);
+ }
+ break;
+
+ case PROP_SHOW_TOOLBAR_CHILDREN:
+ {
+ self->show_toolbar_children = g_value_get_boolean (value);
+ }
+ break;
+
+ default:
+ {
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ }
+ break;
+ }
+}
+
+static void
+nautilus_toolbar_dispose (GObject *object)
+{
+ NautilusToolbar *self;
+
+ self = NAUTILUS_TOOLBAR (object);
+
+ g_clear_pointer (&self->search_binding, g_binding_unbind);
+
+ G_OBJECT_CLASS (nautilus_toolbar_parent_class)->dispose (object);
+}
+
+static void
+nautilus_toolbar_finalize (GObject *obj)
+{
+ NautilusToolbar *self = NAUTILUS_TOOLBAR (obj);
+
+ g_signal_handlers_disconnect_by_func (nautilus_preferences,
+ toolbar_update_appearance, self);
+
+ if (self->window_slot != NULL)
+ {
+ g_signal_handlers_disconnect_by_data (self->window_slot, self);
+ g_object_weak_unref (G_OBJECT (self->window_slot),
+ on_window_slot_destroyed, self);
+ self->window_slot = NULL;
+ }
+
+ G_OBJECT_CLASS (nautilus_toolbar_parent_class)->finalize (obj);
+}
+
+static void
+nautilus_toolbar_class_init (NautilusToolbarClass *klass)
+{
+ GObjectClass *oclass;
+ GtkWidgetClass *widget_class;
+
+ widget_class = GTK_WIDGET_CLASS (klass);
+ oclass = G_OBJECT_CLASS (klass);
+ oclass->get_property = nautilus_toolbar_get_property;
+ oclass->set_property = nautilus_toolbar_set_property;
+ oclass->dispose = nautilus_toolbar_dispose;
+ oclass->finalize = nautilus_toolbar_finalize;
+ oclass->constructed = nautilus_toolbar_constructed;
+
+ properties[PROP_SHOW_LOCATION_ENTRY] =
+ g_param_spec_boolean ("show-location-entry",
+ "Whether to show the location entry",
+ "Whether to show the location entry instead of the pathbar",
+ FALSE,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+
+ properties [PROP_WINDOW_SLOT] =
+ g_param_spec_object ("window-slot",
+ "Window slot currently active",
+ "Window slot currently acive",
+ NAUTILUS_TYPE_WINDOW_SLOT,
+ (G_PARAM_READWRITE |
+ G_PARAM_STATIC_STRINGS));
+
+ properties [PROP_SEARCHING] =
+ g_param_spec_boolean ("searching",
+ "Current view is searching",
+ "Whether the current view is searching or not",
+ FALSE,
+ G_PARAM_READWRITE);
+
+ properties[PROP_SHOW_SIDEBAR_BUTTON] =
+ g_param_spec_boolean ("show-sidebar-button", NULL, NULL, FALSE,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+ properties[PROP_SIDEBAR_BUTTON_ACTIVE] =
+ g_param_spec_boolean ("sidebar-button-active", NULL, NULL, FALSE,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+ properties[PROP_SHOW_TOOLBAR_CHILDREN] =
+ g_param_spec_boolean ("show-toolbar-children", NULL, NULL, TRUE,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+
+ g_object_class_install_properties (oclass, NUM_PROPERTIES, properties);
+
+ gtk_widget_class_set_template_from_resource (widget_class,
+ "/org/gnome/nautilus/ui/nautilus-toolbar.ui");
+
+ gtk_widget_class_bind_template_child (widget_class, NautilusToolbar, app_button);
+ gtk_widget_class_bind_template_child (widget_class, NautilusToolbar, undo_redo_section);
+ gtk_widget_class_bind_template_child (widget_class, NautilusToolbar, toolbar_switcher);
+ gtk_widget_class_bind_template_child (widget_class, NautilusToolbar, search_container);
+ gtk_widget_class_bind_template_child (widget_class, NautilusToolbar, path_bar_container);
+ gtk_widget_class_bind_template_child (widget_class, NautilusToolbar, location_entry_container);
+
+ gtk_widget_class_bind_template_child (widget_class, NautilusToolbar, search_button);
+
+ gtk_widget_class_set_accessible_role (widget_class, GTK_ACCESSIBLE_ROLE_TOOLBAR);
+}
+
+GtkWidget *
+nautilus_toolbar_new (void)
+{
+ return g_object_new (NAUTILUS_TYPE_TOOLBAR,
+ NULL);
+}
+
+GtkWidget *
+nautilus_toolbar_get_path_bar (NautilusToolbar *self)
+{
+ return self->path_bar;
+}
+
+GtkWidget *
+nautilus_toolbar_get_location_entry (NautilusToolbar *self)
+{
+ return self->location_entry;
+}
+
+void
+nautilus_toolbar_set_show_location_entry (NautilusToolbar *self,
+ gboolean show_location_entry)
+{
+ if (show_location_entry != self->show_location_entry)
+ {
+ self->show_location_entry = show_location_entry;
+ toolbar_update_appearance (self);
+
+ g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_SHOW_LOCATION_ENTRY]);
+ }
+}
+
+static void
+box_remove_all_children (GtkBox *box)
+{
+ GtkWidget *child;
+ while ((child = gtk_widget_get_first_child (GTK_WIDGET (box))) != NULL)
+ {
+ gtk_box_remove (GTK_BOX (box), child);
+ }
+}
+
+static void
+slot_on_extensions_background_menu_changed (NautilusToolbar *self,
+ GParamSpec *param,
+ NautilusWindowSlot *slot)
+{
+ g_autoptr (GMenuModel) menu = NULL;
+
+ menu = nautilus_window_slot_get_extensions_background_menu (slot);
+ nautilus_path_bar_set_extensions_background_menu (NAUTILUS_PATH_BAR (self->path_bar),
+ menu);
+}
+
+static void
+slot_on_templates_menu_changed (NautilusToolbar *self,
+ GParamSpec *param,
+ NautilusWindowSlot *slot)
+{
+ g_autoptr (GMenuModel) menu = NULL;
+
+ menu = nautilus_window_slot_get_templates_menu (slot);
+ nautilus_path_bar_set_templates_menu (NAUTILUS_PATH_BAR (self->path_bar),
+ menu);
+}
+
+/* Called from on_window_slot_destroyed(), since bindings and signal handlers
+ * are automatically removed once the slot goes away.
+ */
+static void
+nautilus_toolbar_set_window_slot_real (NautilusToolbar *self,
+ NautilusWindowSlot *slot)
+{
+ g_autoptr (GList) children = NULL;
+
+ self->window_slot = slot;
+
+ if (self->window_slot != NULL)
+ {
+ g_object_weak_ref (G_OBJECT (self->window_slot),
+ on_window_slot_destroyed,
+ self);
+
+ self->search_binding = g_object_bind_property (self->window_slot, "searching",
+ self, "searching",
+ G_BINDING_DEFAULT | G_BINDING_SYNC_CREATE);
+
+ g_signal_connect_swapped (self->window_slot, "notify::extensions-background-menu",
+ G_CALLBACK (slot_on_extensions_background_menu_changed), self);
+ g_signal_connect_swapped (self->window_slot, "notify::templates-menu",
+ G_CALLBACK (slot_on_templates_menu_changed), self);
+ g_signal_connect_swapped (self->window_slot, "notify::searching",
+ G_CALLBACK (toolbar_update_appearance), self);
+ }
+
+ box_remove_all_children (GTK_BOX (self->search_container));
+
+ if (self->window_slot != NULL)
+ {
+ gtk_box_append (GTK_BOX (self->search_container),
+ GTK_WIDGET (nautilus_window_slot_get_query_editor (self->window_slot)));
+ }
+
+ toolbar_update_appearance (self);
+
+ g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_WINDOW_SLOT]);
+ g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_SEARCHING]);
+}
+
+void
+nautilus_toolbar_set_window_slot (NautilusToolbar *self,
+ NautilusWindowSlot *window_slot)
+{
+ g_return_if_fail (NAUTILUS_IS_TOOLBAR (self));
+ g_return_if_fail (window_slot == NULL || NAUTILUS_IS_WINDOW_SLOT (window_slot));
+
+ if (self->window_slot == window_slot)
+ {
+ return;
+ }
+
+ g_clear_pointer (&self->search_binding, g_binding_unbind);
+
+ if (self->window_slot != NULL)
+ {
+ g_signal_handlers_disconnect_by_data (self->window_slot, self);
+ g_object_weak_unref (G_OBJECT (self->window_slot),
+ on_window_slot_destroyed, self);
+ }
+
+ nautilus_toolbar_set_window_slot_real (self, window_slot);
+}
+
+gboolean
+nautilus_toolbar_is_menu_visible (NautilusToolbar *self)
+{
+ GtkWidget *menu;
+
+ g_return_val_if_fail (NAUTILUS_IS_TOOLBAR (self), FALSE);
+
+ menu = GTK_WIDGET (gtk_menu_button_get_popover (GTK_MENU_BUTTON (self->app_button)));
+ g_return_val_if_fail (menu != NULL, FALSE);
+
+ return gtk_widget_is_visible (menu);
+}
diff --git a/src/nautilus-toolbar.h b/src/nautilus-toolbar.h
new file mode 100644
index 0000000..a61a3cb
--- /dev/null
+++ b/src/nautilus-toolbar.h
@@ -0,0 +1,54 @@
+
+/*
+ * Nautilus
+ *
+ * Copyright (C) 2011, Red Hat, Inc.
+ *
+ * Nautilus is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * Nautilus is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ *
+ * Author: Cosimo Cecchi <cosimoc@redhat.com>
+ *
+ */
+
+#pragma once
+
+#include <gtk/gtk.h>
+#include <libadwaita-1/adwaita.h>
+
+#include "nautilus-window-slot.h"
+
+G_BEGIN_DECLS
+
+#define NAUTILUS_TYPE_TOOLBAR nautilus_toolbar_get_type()
+
+G_DECLARE_FINAL_TYPE (NautilusToolbar, nautilus_toolbar, NAUTILUS, TOOLBAR, AdwBin)
+
+GtkWidget *nautilus_toolbar_new (void);
+
+GtkWidget *nautilus_toolbar_get_path_bar (NautilusToolbar *self);
+GtkWidget *nautilus_toolbar_get_location_entry (NautilusToolbar *self);
+
+void nautilus_toolbar_set_show_location_entry (NautilusToolbar *self,
+ gboolean show_location_entry);
+
+void nautilus_toolbar_set_active_slot (NautilusToolbar *toolbar,
+ NautilusWindowSlot *slot);
+
+gboolean nautilus_toolbar_is_menu_visible (NautilusToolbar *toolbar);
+
+void nautilus_toolbar_on_window_constructed (NautilusToolbar *toolbar);
+
+void nautilus_toolbar_set_window_slot (NautilusToolbar *self,
+ NautilusWindowSlot *window_slot);
+G_END_DECLS
diff --git a/src/nautilus-tracker-utilities.c b/src/nautilus-tracker-utilities.c
new file mode 100644
index 0000000..b2e894a
--- /dev/null
+++ b/src/nautilus-tracker-utilities.c
@@ -0,0 +1,148 @@
+/* nautilus-tracker-utilities.c
+ *
+ * Copyright 2019 Carlos Soriano <csoriano@redhat.com>
+ * Copyright 2020 Sam Thursfield <sam@afuera.me.uk>
+ *
+ * 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 3 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/>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#include "config.h"
+#include "nautilus-tracker-utilities.h"
+
+/* Shared global connection to Tracker Miner FS */
+static const gchar *tracker_miner_fs_busname = NULL;
+static TrackerSparqlConnection *tracker_miner_fs_connection = NULL;
+static GError *tracker_miner_fs_error = NULL;
+
+static void
+local_tracker_miner_fs_ready (GObject *source,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ tracker_miner_fs_connection = tracker_sparql_connection_new_finish (res, &tracker_miner_fs_error);
+ if (tracker_miner_fs_error != NULL)
+ {
+ g_critical ("Could not start local Tracker indexer at %s: %s", tracker_miner_fs_busname, tracker_miner_fs_error->message);
+ }
+}
+
+static void
+start_local_tracker_miner_fs (void)
+{
+ const gchar *busname = APPLICATION_ID ".Tracker3.Miner.Files";
+
+ g_message ("Starting %s", busname);
+ tracker_sparql_connection_bus_new_async (busname, NULL, NULL, NULL, local_tracker_miner_fs_ready, NULL);
+
+ tracker_miner_fs_busname = busname;
+}
+
+static gboolean
+inside_flatpak (void)
+{
+ return g_file_test ("/.flatpak-info", G_FILE_TEST_EXISTS);
+}
+
+static void
+host_tracker_miner_fs_ready (GObject *source,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ tracker_miner_fs_connection = tracker_sparql_connection_bus_new_finish (res, &tracker_miner_fs_error);
+ if (tracker_miner_fs_error)
+ {
+ g_warning ("Unable to create connection for session-wide Tracker indexer: %s", (tracker_miner_fs_error)->message);
+ if (inside_flatpak ())
+ {
+ g_clear_error (&tracker_miner_fs_error);
+ start_local_tracker_miner_fs ();
+ }
+ }
+}
+
+void
+nautilus_tracker_setup_miner_fs_connection (void)
+{
+ static gsize tried_tracker_init = FALSE;
+
+ if (tracker_miner_fs_connection != NULL)
+ {
+ /* The connection was already established */
+ return;
+ }
+
+ if (g_once_init_enter (&tried_tracker_init))
+ {
+ const gchar *busname = "org.freedesktop.Tracker3.Miner.Files";
+
+ g_message ("Connecting to %s", busname);
+ tracker_sparql_connection_bus_new_async (busname, NULL, NULL, NULL, host_tracker_miner_fs_ready, NULL);
+
+ tracker_miner_fs_busname = busname;
+
+ g_once_init_leave (&tried_tracker_init, TRUE);
+ }
+}
+
+/**
+ * nautilus_tracker_setup_host_miner_fs_connection_sync:
+ *
+ * This function is only meant to be used within tests.
+ * This version of this setup function intentionally blocks to help with tests.
+ *
+ */
+void
+nautilus_tracker_setup_host_miner_fs_connection_sync (void)
+{
+ g_autoptr (GError) error = NULL;
+ const gchar *busname = "org.freedesktop.Tracker3.Miner.Files";
+
+ g_message ("Starting %s", busname);
+ tracker_miner_fs_connection = tracker_sparql_connection_bus_new (busname, NULL, NULL, &error);
+ if (error != NULL)
+ {
+ g_critical ("Could not start local Tracker indexer at %s: %s", busname, error->message);
+ return;
+ }
+
+ tracker_miner_fs_busname = busname;
+}
+
+/**
+ * nautilus_tracker_get_miner_fs_connection:
+ * @error: return location for a #GError
+ *
+ * This function returns a global singleton #TrackerSparqlConnection, or %NULL
+ * if either we couldn't connect to Tracker Miner FS or the connection is still
+ * pending.
+ *
+ * The returned object is a globally shared singleton which should NOT be
+ * unreffed.
+ *
+ * Returns: a #TrackerSparqlConnection, or %NULL
+ */
+TrackerSparqlConnection *
+nautilus_tracker_get_miner_fs_connection (GError **error)
+{
+ nautilus_tracker_setup_miner_fs_connection ();
+
+ if (tracker_miner_fs_error && error)
+ {
+ *error = g_error_copy (tracker_miner_fs_error);
+ }
+
+ return tracker_miner_fs_connection;
+}
diff --git a/src/nautilus-tracker-utilities.h b/src/nautilus-tracker-utilities.h
new file mode 100644
index 0000000..d79c2b6
--- /dev/null
+++ b/src/nautilus-tracker-utilities.h
@@ -0,0 +1,31 @@
+/* nautilus-tracker-utilities.h
+ *
+ * Copyright 2019 Carlos Soriano <csoriano@redhat.com>
+ *
+ * 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 3 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/>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+
+#pragma once
+
+#include <gio/gio.h>
+#include <libtracker-sparql/tracker-sparql.h>
+
+TrackerSparqlConnection * nautilus_tracker_get_miner_fs_connection (GError **error);
+void nautilus_tracker_setup_miner_fs_connection (void);
+
+/* nautilus_tracker_setup_host_miner_fs_connection_sync() is for testing purposes only */
+void nautilus_tracker_setup_host_miner_fs_connection_sync (void);
diff --git a/src/nautilus-trash-monitor.c b/src/nautilus-trash-monitor.c
new file mode 100644
index 0000000..366116f
--- /dev/null
+++ b/src/nautilus-trash-monitor.c
@@ -0,0 +1,262 @@
+/*
+ * nautilus-trash-monitor.c: Nautilus trash state watcher.
+ *
+ * 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/>.
+ *
+ * Author: Pavel Cisler <pavel@eazel.com>
+ */
+
+#include "nautilus-trash-monitor.h"
+
+#include <eel/eel-debug.h>
+
+#define UPDATE_RATE_SECONDS 1
+
+struct _NautilusTrashMonitor
+{
+ GObject object;
+
+ gboolean empty;
+ GFileMonitor *file_monitor;
+ gboolean pending;
+ gint timeout_id;
+};
+
+enum
+{
+ TRASH_STATE_CHANGED,
+ LAST_SIGNAL
+};
+
+static guint signals[LAST_SIGNAL];
+static NautilusTrashMonitor *nautilus_trash_monitor = NULL;
+
+G_DEFINE_TYPE (NautilusTrashMonitor, nautilus_trash_monitor, G_TYPE_OBJECT)
+
+static void
+nautilus_trash_monitor_finalize (GObject *object)
+{
+ NautilusTrashMonitor *trash_monitor;
+
+ trash_monitor = NAUTILUS_TRASH_MONITOR (object);
+
+ if (trash_monitor->timeout_id > 0)
+ {
+ g_source_remove (trash_monitor->timeout_id);
+ trash_monitor->timeout_id = 0;
+ }
+
+ if (trash_monitor->file_monitor)
+ {
+ g_object_unref (trash_monitor->file_monitor);
+ }
+
+ G_OBJECT_CLASS (nautilus_trash_monitor_parent_class)->finalize (object);
+}
+
+static void
+nautilus_trash_monitor_class_init (NautilusTrashMonitorClass *klass)
+{
+ GObjectClass *object_class;
+
+ object_class = G_OBJECT_CLASS (klass);
+
+ object_class->finalize = nautilus_trash_monitor_finalize;
+
+ signals[TRASH_STATE_CHANGED] = g_signal_new
+ ("trash-state-changed",
+ G_TYPE_FROM_CLASS (object_class),
+ G_SIGNAL_RUN_LAST,
+ 0,
+ NULL, NULL,
+ g_cclosure_marshal_VOID__BOOLEAN,
+ G_TYPE_NONE, 1,
+ G_TYPE_BOOLEAN);
+}
+
+static void
+update_empty_info (NautilusTrashMonitor *trash_monitor,
+ gboolean is_empty)
+{
+ if (trash_monitor->empty == is_empty)
+ {
+ return;
+ }
+
+ trash_monitor->empty = is_empty;
+
+ /* trash got empty or full, notify everyone who cares */
+ g_signal_emit (trash_monitor,
+ signals[TRASH_STATE_CHANGED], 0,
+ trash_monitor->empty);
+}
+
+/* Use G_FILE_ATTRIBUTE_TRASH_ITEM_COUNT since we only want to know whether the
+ * trash is empty or not, not access its children. This is available for the
+ * trash backend since it uses a cache. In this way we prevent flooding the
+ * trash backend with enumeration requests when trashing > 1000 files
+ */
+static void
+trash_query_info_cb (GObject *source,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ NautilusTrashMonitor *trash_monitor = user_data;
+ GFileInfo *info;
+ guint32 item_count;
+ gboolean is_empty = TRUE;
+
+ info = g_file_query_info_finish (G_FILE (source), res, NULL);
+
+ if (info != NULL)
+ {
+ item_count = g_file_info_get_attribute_uint32 (info,
+ G_FILE_ATTRIBUTE_TRASH_ITEM_COUNT);
+ is_empty = item_count == 0;
+
+ g_object_unref (info);
+ }
+
+ update_empty_info (trash_monitor, is_empty);
+
+ g_object_unref (trash_monitor);
+}
+
+static void schedule_update_info (NautilusTrashMonitor *trash_monitor);
+
+static gboolean
+schedule_update_info_cb (gpointer data)
+{
+ NautilusTrashMonitor *trash_monitor = data;
+
+ trash_monitor->timeout_id = 0;
+ if (trash_monitor->pending)
+ {
+ trash_monitor->pending = FALSE;
+ schedule_update_info (trash_monitor);
+ }
+
+ return G_SOURCE_REMOVE;
+}
+
+static void
+schedule_update_info (NautilusTrashMonitor *trash_monitor)
+{
+ GFile *location;
+
+ /* Rate limit the updates to not flood the gvfsd-trash when too many changes
+ * happended in a short time.
+ */
+ if (trash_monitor->timeout_id > 0)
+ {
+ trash_monitor->pending = TRUE;
+ return;
+ }
+
+ location = g_file_new_for_uri ("trash:///");
+ g_file_query_info_async (location,
+ G_FILE_ATTRIBUTE_TRASH_ITEM_COUNT,
+ G_FILE_QUERY_INFO_NONE,
+ G_PRIORITY_DEFAULT, NULL,
+ trash_query_info_cb, g_object_ref (trash_monitor));
+
+ trash_monitor->timeout_id = g_timeout_add_seconds (UPDATE_RATE_SECONDS,
+ schedule_update_info_cb,
+ trash_monitor);
+ g_object_unref (location);
+}
+
+static void
+file_changed (GFileMonitor *monitor,
+ GFile *child,
+ GFile *other_file,
+ GFileMonitorEvent event_type,
+ gpointer user_data)
+{
+ NautilusTrashMonitor *trash_monitor;
+
+ trash_monitor = NAUTILUS_TRASH_MONITOR (user_data);
+
+ schedule_update_info (trash_monitor);
+}
+
+static void
+nautilus_trash_monitor_init (NautilusTrashMonitor *trash_monitor)
+{
+ GFile *location;
+
+ trash_monitor->empty = TRUE;
+
+ location = g_file_new_for_uri ("trash:///");
+
+ trash_monitor->file_monitor = g_file_monitor_file (location, 0, NULL, NULL);
+ trash_monitor->pending = FALSE;
+ trash_monitor->timeout_id = 0;
+
+ g_signal_connect (trash_monitor->file_monitor, "changed",
+ (GCallback) file_changed, trash_monitor);
+
+ g_object_unref (location);
+
+ schedule_update_info (trash_monitor);
+}
+
+static void
+clear_trash_monitor_on_shutdown (void)
+{
+ g_clear_object (&nautilus_trash_monitor);
+}
+
+NautilusTrashMonitor *
+nautilus_trash_monitor_get (void)
+{
+ if (nautilus_trash_monitor == NULL)
+ {
+ /* not running yet, start it up */
+
+ nautilus_trash_monitor = NAUTILUS_TRASH_MONITOR
+ (g_object_new (NAUTILUS_TYPE_TRASH_MONITOR, NULL));
+ eel_debug_call_at_shutdown (clear_trash_monitor_on_shutdown);
+ }
+
+ return nautilus_trash_monitor;
+}
+
+gboolean
+nautilus_trash_monitor_is_empty (void)
+{
+ NautilusTrashMonitor *monitor;
+
+ monitor = nautilus_trash_monitor_get ();
+ return monitor->empty;
+}
+
+GIcon *
+nautilus_trash_monitor_get_symbolic_icon (void)
+{
+ gboolean empty;
+
+ empty = nautilus_trash_monitor_is_empty ();
+
+ if (empty)
+ {
+ return g_themed_icon_new ("user-trash-symbolic");
+ }
+ else
+ {
+ return g_themed_icon_new ("user-trash-full-symbolic");
+ }
+}
diff --git a/src/nautilus-trash-monitor.h b/src/nautilus-trash-monitor.h
new file mode 100644
index 0000000..1b81a4b
--- /dev/null
+++ b/src/nautilus-trash-monitor.h
@@ -0,0 +1,35 @@
+
+/*
+ nautilus-trash-monitor.h: Nautilus trash state watcher.
+
+ Copyright (C) 2000 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/>.
+
+ Author: Pavel Cisler <pavel@eazel.com>
+*/
+
+#pragma once
+
+#include <gio/gio.h>
+
+#define NAUTILUS_TYPE_TRASH_MONITOR (nautilus_trash_monitor_get_type ())
+
+G_DECLARE_FINAL_TYPE (NautilusTrashMonitor, nautilus_trash_monitor,
+ NAUTILUS, TRASH_MONITOR,
+ GObject)
+
+NautilusTrashMonitor *nautilus_trash_monitor_get (void);
+gboolean nautilus_trash_monitor_is_empty (void);
+GIcon *nautilus_trash_monitor_get_symbolic_icon (void);
diff --git a/src/nautilus-types.h b/src/nautilus-types.h
new file mode 100644
index 0000000..6a13ebb
--- /dev/null
+++ b/src/nautilus-types.h
@@ -0,0 +1,47 @@
+/* Copyright (C) 2018 Ernestas Kulik <ernestask@gnome.org>
+ *
+ * This file is part of Nautilus.
+ *
+ * Nautilus is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Nautilus is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Nautilus. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+/* This here header contains Nautilus type definitions.
+ *
+ * It is advisable to include this in headers or when you
+ * only need the name of a type (i.e. not calling any functions from the
+ * associated header). Doing so will help avoid circular inclusions and
+ * pointless rebuilds should the header ever change.
+ */
+
+#pragma once
+
+#include "nautilus-enums.h"
+
+/* Keep sorted alphabetically. */
+
+typedef struct _NautilusBookmark NautilusBookmark;
+typedef struct _NautilusBookmarkList NautilusBookmarkList;
+typedef struct _NautilusClipboard NautilusClipboard;
+typedef struct _NautilusDirectory NautilusDirectory;
+typedef struct NautilusFile NautilusFile;
+typedef struct NautilusFileQueue NautilusFileQueue;
+typedef struct _NautilusIconInfo NautilusIconInfo;
+typedef struct _NautilusListBase NautilusListBase;
+typedef struct NautilusMonitor NautilusMonitor;
+typedef struct _NautilusQuery NautilusQuery;
+typedef struct _NautilusQueryEditor NautilusQueryEditor;
+typedef struct _NautilusToolbarMenuSections NautilusToolbarMenuSections;
+typedef struct _NautilusView NautilusView;
+typedef struct _NautilusWindow NautilusWindow;
+typedef struct _NautilusWindowSlot NautilusWindowSlot;
diff --git a/src/nautilus-ui-utilities.c b/src/nautilus-ui-utilities.c
new file mode 100644
index 0000000..d874224
--- /dev/null
+++ b/src/nautilus-ui-utilities.c
@@ -0,0 +1,420 @@
+/* nautilus-ui-utilities.c - helper functions for GtkUIManager stuff
+ *
+ * Copyright (C) 2004 Red Hat, Inc.
+ *
+ * The Gnome Library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * The Gnome Library 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
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with the Gnome Library; see the file COPYING.LIB. If not,
+ * see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Alexander Larsson <alexl@redhat.com>
+ */
+
+#include <config.h>
+
+#include "nautilus-ui-utilities.h"
+#include "nautilus-icon-info.h"
+#include "nautilus-application.h"
+#include <eel/eel-graphic-effects.h>
+
+#include <gio/gio.h>
+#include <gtk/gtk.h>
+#include <string.h>
+#include <glib/gi18n.h>
+
+/**
+ * nautilus_gmenu_set_from_model:
+ * @target_menu: the #GMenu to be filled
+ * @source_model: (nullable): a #GMenuModel to copy items from
+ *
+ * This will replace the content of @target_menu with a copy of all items from
+ * @source_model.
+ *
+ * If the @source_model is empty (i.e., its item count is 0), or if it is %NULL,
+ * then the @target_menu is left empty.
+ */
+void
+nautilus_gmenu_set_from_model (GMenu *target_menu,
+ GMenuModel *source_model)
+{
+ g_return_if_fail (G_IS_MENU (target_menu));
+ g_return_if_fail (source_model == NULL || G_IS_MENU_MODEL (source_model));
+
+ /* First, empty the menu... */
+ g_menu_remove_all (target_menu);
+
+ /* ...then, repopulate it (maybe). */
+ if (source_model != NULL)
+ {
+ gint n_items;
+
+ n_items = g_menu_model_get_n_items (source_model);
+ for (gint i = 0; i < n_items; i++)
+ {
+ g_autoptr (GMenuItem) item = NULL;
+ item = g_menu_item_new_from_model (source_model, i);
+ g_menu_append_item (target_menu, item);
+ }
+ }
+}
+
+/**
+ * nautilus_g_menu_model_find_by_string:
+ * @model: the #GMenuModel with items to search
+ * @attribute: the menu item attribute to compare with
+ * @string: the string to match the value of @attribute
+ *
+ * This will search for an item in the model which has got the @attribute and
+ * whose value is equal to @string.
+ *
+ * It is assumed that @attribute has the a GVariant format string "s".
+ *
+ * Returns: The index of the first match in the model, or -1 if no item matches.
+ */
+gint
+nautilus_g_menu_model_find_by_string (GMenuModel *model,
+ const gchar *attribute,
+ const gchar *string)
+{
+ gint item_index = -1;
+ gint n_items;
+
+ n_items = g_menu_model_get_n_items (model);
+ for (gint i = 0; i < n_items; i++)
+ {
+ g_autofree gchar *value = NULL;
+ if (g_menu_model_get_item_attribute (model, i, attribute, "s", &value) &&
+ g_strcmp0 (value, string) == 0)
+ {
+ item_index = i;
+ break;
+ }
+ }
+ return item_index;
+}
+
+/**
+ * nautilus_g_menu_replace_string_in_item:
+ * @menu: the #GMenu to modify
+ * @i: the position of the item to change
+ * @attribute: the menu item attribute to change
+ * @string: the string to change the value of @attribute to
+ *
+ * This will replace the item at @position with a new item which is identical
+ * except that it has @attribute set to @string.
+ *
+ * This is useful e.g. when want to change the menu model of a #GtkPopover and
+ * you have a pointer to its menu model but not to the popover itself, so you
+ * can't just set a new model. With this method, the GtkPopover is notified of
+ * changes in its model and updates its contents accordingly.
+ *
+ * It is assumed that @attribute has the a GVariant format string "s".
+ */
+void
+nautilus_g_menu_replace_string_in_item (GMenu *menu,
+ gint i,
+ const gchar *attribute,
+ const gchar *string)
+{
+ g_autoptr (GMenuItem) item = NULL;
+
+ g_return_if_fail (i != -1);
+ item = g_menu_item_new_from_model (G_MENU_MODEL (menu), i);
+ g_return_if_fail (item != NULL);
+
+ if (string != NULL)
+ {
+ g_menu_item_set_attribute (item, attribute, "s", string);
+ }
+ else
+ {
+ g_menu_item_set_attribute (item, attribute, NULL);
+ }
+
+ g_menu_remove (menu, i);
+ g_menu_insert_item (menu, i, item);
+}
+
+static GdkPixbuf *filmholes_left = NULL;
+static GdkPixbuf *filmholes_right = NULL;
+
+static gboolean
+ensure_filmholes (void)
+{
+ if (filmholes_left == NULL)
+ {
+ filmholes_left = gdk_pixbuf_new_from_resource ("/org/gnome/nautilus/icons/filmholes.png", NULL);
+ }
+ if (filmholes_right == NULL &&
+ filmholes_left != NULL)
+ {
+ filmholes_right = gdk_pixbuf_flip (filmholes_left, TRUE);
+ }
+
+ return (filmholes_left && filmholes_right);
+}
+
+void
+nautilus_ui_frame_video (GtkSnapshot *snapshot,
+ gdouble width,
+ gdouble height)
+{
+ g_autoptr (GdkTexture) left_texture = NULL;
+ g_autoptr (GdkTexture) right_texture = NULL;
+ int holes_width, holes_height;
+
+ if (!ensure_filmholes ())
+ {
+ return;
+ }
+
+ holes_width = gdk_pixbuf_get_width (filmholes_left);
+ holes_height = gdk_pixbuf_get_height (filmholes_left);
+
+ /* Left */
+ gtk_snapshot_push_repeat (snapshot,
+ &GRAPHENE_RECT_INIT (0, 0, holes_width, height),
+ NULL);
+ left_texture = gdk_texture_new_for_pixbuf (filmholes_left);
+ gtk_snapshot_append_texture (snapshot,
+ left_texture,
+ &GRAPHENE_RECT_INIT (0, 0, holes_width, holes_height));
+ gtk_snapshot_pop (snapshot);
+
+ /* Right */
+ gtk_snapshot_push_repeat (snapshot,
+ &GRAPHENE_RECT_INIT (width - holes_width, 0, holes_width, height),
+ NULL);
+ right_texture = gdk_texture_new_for_pixbuf (filmholes_right);
+ gtk_snapshot_append_texture (snapshot,
+ right_texture,
+ &GRAPHENE_RECT_INIT (width - holes_width, 0, holes_width, holes_height));
+ gtk_snapshot_pop (snapshot);
+}
+
+gboolean
+nautilus_date_time_is_between_dates (GDateTime *date,
+ GDateTime *initial_date,
+ GDateTime *end_date)
+{
+ gboolean in_between;
+
+ /* Silently ignore errors */
+ if (date == NULL || g_date_time_to_unix (date) == 0)
+ {
+ return FALSE;
+ }
+
+ /* For the end date, we want to make end_date inclusive,
+ * for that the difference between the start of the day and the in_between
+ * has to be more than -1 day
+ */
+ in_between = g_date_time_difference (date, initial_date) > 0 &&
+ g_date_time_difference (end_date, date) / G_TIME_SPAN_DAY > -1;
+
+ return in_between;
+}
+
+static const gchar *
+get_text_for_days_ago (gint days,
+ gboolean prefix_with_since)
+{
+ if (days < 7)
+ {
+ /* days */
+ return prefix_with_since ?
+ ngettext ("Since %d day ago", "Since %d days ago", days) :
+ ngettext ("%d day ago", "%d days ago", days);
+ }
+ if (days < 30)
+ {
+ /* weeks */
+ return prefix_with_since ?
+ ngettext ("Since last week", "Since %d weeks ago", days / 7) :
+ ngettext ("Last week", "%d weeks ago", days / 7);
+ }
+ if (days < 365)
+ {
+ /* months */
+ return prefix_with_since ?
+ ngettext ("Since last month", "Since %d months ago", days / 30) :
+ ngettext ("Last month", "%d months ago", days / 30);
+ }
+
+ /* years */
+ return prefix_with_since ?
+ ngettext ("Since last year", "Since %d years ago", days / 365) :
+ ngettext ("Last year", "%d years ago", days / 365);
+}
+
+gchar *
+get_text_for_date_range (GPtrArray *date_range,
+ gboolean prefix_with_since)
+{
+ gint days;
+ gint normalized;
+ GDateTime *initial_date;
+ GDateTime *end_date;
+ gchar *formatted_date;
+ gchar *label;
+
+ if (!date_range)
+ {
+ return NULL;
+ }
+
+ initial_date = g_ptr_array_index (date_range, 0);
+ end_date = g_ptr_array_index (date_range, 1);
+ days = g_date_time_difference (end_date, initial_date) / G_TIME_SPAN_DAY;
+ formatted_date = g_date_time_format (initial_date, "%x");
+
+ if (days < 1)
+ {
+ label = g_strdup (formatted_date);
+ }
+ else
+ {
+ if (days < 7)
+ {
+ /* days */
+ normalized = days;
+ }
+ else if (days < 30)
+ {
+ /* weeks */
+ normalized = days / 7;
+ }
+ else if (days < 365)
+ {
+ /* months */
+ normalized = days / 30;
+ }
+ else
+ {
+ /* years */
+ normalized = days / 365;
+ }
+
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wformat-nonliteral"
+ label = g_strdup_printf (get_text_for_days_ago (days,
+ prefix_with_since),
+ normalized);
+#pragma GCC diagnostic pop
+ }
+
+ g_free (formatted_date);
+
+ return label;
+}
+
+AdwMessageDialog *
+show_dialog (const gchar *primary_text,
+ const gchar *secondary_text,
+ GtkWindow *parent,
+ GtkMessageType type)
+{
+ GtkWidget *dialog;
+
+ g_return_val_if_fail (parent != NULL, NULL);
+
+ dialog = adw_message_dialog_new (parent, primary_text, secondary_text);
+ adw_message_dialog_add_response (ADW_MESSAGE_DIALOG (dialog), "ok", _("_OK"));
+ adw_message_dialog_set_default_response (ADW_MESSAGE_DIALOG (dialog), "ok");
+
+ gtk_window_present (GTK_WINDOW (dialog));
+
+ return ADW_MESSAGE_DIALOG (dialog);
+}
+
+static void
+notify_unmount_done (GMountOperation *op,
+ const gchar *message)
+{
+ NautilusApplication *application;
+ gchar *notification_id;
+
+ application = nautilus_application_get_default ();
+ notification_id = g_strdup_printf ("nautilus-mount-operation-%p", op);
+ nautilus_application_withdraw_notification (application, notification_id);
+
+ if (message != NULL)
+ {
+ GNotification *unplug;
+ GIcon *icon;
+ gchar **strings;
+
+ strings = g_strsplit (message, "\n", 0);
+ icon = g_themed_icon_new ("media-removable-symbolic");
+ unplug = g_notification_new (strings[0]);
+ g_notification_set_body (unplug, strings[1]);
+ g_notification_set_icon (unplug, icon);
+
+ nautilus_application_send_notification (application, notification_id, unplug);
+ g_object_unref (unplug);
+ g_object_unref (icon);
+ g_strfreev (strings);
+ }
+
+ g_free (notification_id);
+}
+
+static void
+notify_unmount_show (GMountOperation *op,
+ const gchar *message)
+{
+ NautilusApplication *application;
+ GNotification *unmount;
+ gchar *notification_id;
+ GIcon *icon;
+ gchar **strings;
+
+ application = nautilus_application_get_default ();
+ strings = g_strsplit (message, "\n", 0);
+ icon = g_themed_icon_new ("media-removable");
+
+ unmount = g_notification_new (strings[0]);
+ g_notification_set_body (unmount, strings[1]);
+ g_notification_set_icon (unmount, icon);
+ g_notification_set_priority (unmount, G_NOTIFICATION_PRIORITY_URGENT);
+
+ notification_id = g_strdup_printf ("nautilus-mount-operation-%p", op);
+ nautilus_application_send_notification (application, notification_id, unmount);
+ g_object_unref (unmount);
+ g_object_unref (icon);
+ g_strfreev (strings);
+ g_free (notification_id);
+}
+
+void
+show_unmount_progress_cb (GMountOperation *op,
+ const gchar *message,
+ gint64 time_left,
+ gint64 bytes_left,
+ gpointer user_data)
+{
+ if (bytes_left == 0)
+ {
+ notify_unmount_done (op, message);
+ }
+ else
+ {
+ notify_unmount_show (op, message);
+ }
+}
+
+void
+show_unmount_progress_aborted_cb (GMountOperation *op,
+ gpointer user_data)
+{
+ notify_unmount_done (op, NULL);
+}
diff --git a/src/nautilus-ui-utilities.h b/src/nautilus-ui-utilities.h
new file mode 100644
index 0000000..f3df67f
--- /dev/null
+++ b/src/nautilus-ui-utilities.h
@@ -0,0 +1,59 @@
+
+/* nautilus-ui-utilities.h - helper functions for GtkUIManager stuff
+
+ Copyright (C) 2004 Red Hat, Inc.
+
+ The Gnome Library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Library General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version.
+
+ The Gnome Library 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
+ Library General Public License for more details.
+
+ You should have received a copy of the GNU Library General Public
+ License along with the Gnome Library; see the file COPYING.LIB. If not,
+ see <http://www.gnu.org/licenses/>.
+
+ Authors: Alexander Larsson <alexl@redhat.com>
+*/
+
+#pragma once
+
+#include <gtk/gtk.h>
+#include <libadwaita-1/adwaita.h>
+
+void nautilus_gmenu_set_from_model (GMenu *target_menu,
+ GMenuModel *source_model);
+gint nautilus_g_menu_model_find_by_string (GMenuModel *model,
+ const gchar *attribute,
+ const gchar *string);
+void nautilus_g_menu_replace_string_in_item (GMenu *menu,
+ gint i,
+ const gchar *attribute,
+ const gchar *string);
+
+void nautilus_ui_frame_video (GtkSnapshot *snapshot,
+ gdouble width,
+ gdouble height);
+
+gboolean nautilus_date_time_is_between_dates (GDateTime *date,
+ GDateTime *initial_date,
+ GDateTime *end_date);
+gchar * get_text_for_date_range (GPtrArray *date_range,
+ gboolean prefix_with_since);
+
+AdwMessageDialog * show_dialog (const gchar *primary_text,
+ const gchar *secondary_text,
+ GtkWindow *parent,
+ GtkMessageType type);
+
+void show_unmount_progress_cb (GMountOperation *op,
+ const gchar *message,
+ gint64 time_left,
+ gint64 bytes_left,
+ gpointer user_data);
+void show_unmount_progress_aborted_cb (GMountOperation *op,
+ gpointer user_data);
diff --git a/src/nautilus-undo-private.h b/src/nautilus-undo-private.h
new file mode 100644
index 0000000..2a9be28
--- /dev/null
+++ b/src/nautilus-undo-private.h
@@ -0,0 +1,30 @@
+
+/* xxx
+ *
+ * Copyright (C) 2000 Eazel, Inc.
+ *
+ * Author: Gene Z. Ragan <gzr@eazel.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include "nautilus-undo.h"
+#include "nautilus-undo-manager.h"
+#include <glib-object.h>
+
+NautilusUndoManager * nautilus_undo_get_undo_manager (GObject *attached_object);
+void nautilus_undo_attach_undo_manager (GObject *object,
+ NautilusUndoManager *manager); \ No newline at end of file
diff --git a/src/nautilus-vfs-directory.c b/src/nautilus-vfs-directory.c
new file mode 100644
index 0000000..c2bdb11
--- /dev/null
+++ b/src/nautilus-vfs-directory.c
@@ -0,0 +1,121 @@
+/*
+ * nautilus-vfs-directory.c: Subclass of NautilusDirectory to help implement the
+ * virtual trash directory.
+ *
+ * Copyright (C) 1999, 2000 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/>.
+ *
+ * Author: Darin Adler <darin@bentspoon.com>
+ */
+
+#include <config.h>
+#include "nautilus-vfs-directory.h"
+
+#include "nautilus-directory-private.h"
+#include "nautilus-file-private.h"
+
+G_DEFINE_TYPE (NautilusVFSDirectory, nautilus_vfs_directory, NAUTILUS_TYPE_DIRECTORY);
+
+static void
+nautilus_vfs_directory_init (NautilusVFSDirectory *directory)
+{
+}
+
+static void
+vfs_call_when_ready (NautilusDirectory *directory,
+ NautilusFileAttributes file_attributes,
+ gboolean wait_for_file_list,
+ NautilusDirectoryCallback callback,
+ gpointer callback_data)
+{
+ g_assert (NAUTILUS_IS_VFS_DIRECTORY (directory));
+
+ nautilus_directory_call_when_ready_internal
+ (directory,
+ NULL,
+ file_attributes,
+ wait_for_file_list,
+ callback,
+ NULL,
+ callback_data);
+}
+
+static void
+vfs_cancel_callback (NautilusDirectory *directory,
+ NautilusDirectoryCallback callback,
+ gpointer callback_data)
+{
+ g_assert (NAUTILUS_IS_VFS_DIRECTORY (directory));
+
+ nautilus_directory_cancel_callback_internal
+ (directory,
+ NULL,
+ callback,
+ NULL,
+ callback_data);
+}
+
+static void
+vfs_file_monitor_add (NautilusDirectory *directory,
+ gconstpointer client,
+ gboolean monitor_hidden_files,
+ NautilusFileAttributes file_attributes,
+ NautilusDirectoryCallback callback,
+ gpointer callback_data)
+{
+ g_assert (NAUTILUS_IS_VFS_DIRECTORY (directory));
+ g_assert (client != NULL);
+
+ nautilus_directory_monitor_add_internal
+ (directory, NULL,
+ client,
+ monitor_hidden_files,
+ file_attributes,
+ callback, callback_data);
+}
+
+static void
+vfs_file_monitor_remove (NautilusDirectory *directory,
+ gconstpointer client)
+{
+ g_assert (NAUTILUS_IS_VFS_DIRECTORY (directory));
+ g_assert (client != NULL);
+
+ nautilus_directory_monitor_remove_internal (directory, NULL, client);
+}
+
+static void
+vfs_force_reload (NautilusDirectory *directory)
+{
+ NautilusFileAttributes all_attributes;
+
+ g_assert (NAUTILUS_IS_DIRECTORY (directory));
+
+ all_attributes = nautilus_file_get_all_attributes ();
+ nautilus_directory_force_reload_internal (directory,
+ all_attributes);
+}
+
+static void
+nautilus_vfs_directory_class_init (NautilusVFSDirectoryClass *klass)
+{
+ NautilusDirectoryClass *directory_class = NAUTILUS_DIRECTORY_CLASS (klass);
+
+ directory_class->call_when_ready = vfs_call_when_ready;
+ directory_class->cancel_callback = vfs_cancel_callback;
+ directory_class->file_monitor_add = vfs_file_monitor_add;
+ directory_class->file_monitor_remove = vfs_file_monitor_remove;
+ directory_class->force_reload = vfs_force_reload;
+}
diff --git a/src/nautilus-vfs-directory.h b/src/nautilus-vfs-directory.h
new file mode 100644
index 0000000..4bdd6ff
--- /dev/null
+++ b/src/nautilus-vfs-directory.h
@@ -0,0 +1,49 @@
+/*
+ nautilus-vfs-directory.h: Subclass of NautilusDirectory to implement the
+ the case of a VFS directory.
+
+ Copyright (C) 1999, 2000 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/>.
+
+ Author: Darin Adler <darin@bentspoon.com>
+*/
+
+#pragma once
+
+#include "nautilus-directory.h"
+
+#define NAUTILUS_TYPE_VFS_DIRECTORY nautilus_vfs_directory_get_type()
+#define NAUTILUS_VFS_DIRECTORY(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST ((obj), NAUTILUS_TYPE_VFS_DIRECTORY, NautilusVFSDirectory))
+#define NAUTILUS_VFS_DIRECTORY_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST ((klass), NAUTILUS_TYPE_VFS_DIRECTORY, NautilusVFSDirectoryClass))
+#define NAUTILUS_IS_VFS_DIRECTORY(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE ((obj), NAUTILUS_TYPE_VFS_DIRECTORY))
+#define NAUTILUS_IS_VFS_DIRECTORY_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE ((klass), NAUTILUS_TYPE_VFS_DIRECTORY))
+#define NAUTILUS_VFS_DIRECTORY_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), NAUTILUS_TYPE_VFS_DIRECTORY, NautilusVFSDirectoryClass))
+
+typedef struct NautilusVFSDirectoryDetails NautilusVFSDirectoryDetails;
+
+typedef struct {
+ NautilusDirectory parent_slot;
+} NautilusVFSDirectory;
+
+typedef struct {
+ NautilusDirectoryClass parent_slot;
+} NautilusVFSDirectoryClass;
+
+GType nautilus_vfs_directory_get_type (void); \ No newline at end of file
diff --git a/src/nautilus-vfs-file.c b/src/nautilus-vfs-file.c
new file mode 100644
index 0000000..9e6038c
--- /dev/null
+++ b/src/nautilus-vfs-file.c
@@ -0,0 +1,734 @@
+/*
+ * nautilus-vfs-file.c: Subclass of NautilusFile to help implement the
+ * virtual trash directory.
+ *
+ * Copyright (C) 1999, 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/>.
+ *
+ * Author: Darin Adler <darin@bentspoon.com>
+ */
+
+#include <config.h>
+#include "nautilus-vfs-file.h"
+
+#include "nautilus-directory-notify.h"
+#include "nautilus-directory-private.h"
+#include "nautilus-file-private.h"
+#include <glib/gi18n.h>
+
+G_DEFINE_TYPE (NautilusVFSFile, nautilus_vfs_file, NAUTILUS_TYPE_FILE);
+
+static void
+vfs_file_monitor_add (NautilusFile *file,
+ gconstpointer client,
+ NautilusFileAttributes attributes)
+{
+ NautilusDirectory *directory;
+
+ directory = nautilus_file_get_directory (file);
+
+ nautilus_directory_monitor_add_internal (directory, file, client, TRUE,
+ attributes, NULL, NULL);
+}
+
+static void
+vfs_file_monitor_remove (NautilusFile *file,
+ gconstpointer client)
+{
+ NautilusDirectory *directory;
+
+ directory = nautilus_file_get_directory (file);
+
+ nautilus_directory_monitor_remove_internal (directory, file, client);
+}
+
+static void
+vfs_file_call_when_ready (NautilusFile *file,
+ NautilusFileAttributes file_attributes,
+ NautilusFileCallback callback,
+ gpointer callback_data)
+{
+ NautilusDirectory *directory;
+
+ directory = nautilus_file_get_directory (file);
+
+ nautilus_directory_call_when_ready_internal (directory, file, file_attributes,
+ FALSE, NULL, callback, callback_data);
+}
+
+static void
+vfs_file_cancel_call_when_ready (NautilusFile *file,
+ NautilusFileCallback callback,
+ gpointer callback_data)
+{
+ NautilusDirectory *directory;
+
+ directory = nautilus_file_get_directory (file);
+
+ nautilus_directory_cancel_callback_internal (directory, file, NULL,
+ callback, callback_data);
+}
+
+static gboolean
+vfs_file_check_if_ready (NautilusFile *file,
+ NautilusFileAttributes file_attributes)
+{
+ NautilusDirectory *directory;
+
+ directory = nautilus_file_get_directory (file);
+
+ return nautilus_directory_check_if_ready_internal (directory, file,
+ file_attributes);
+}
+
+static void
+set_metadata_get_info_callback (GObject *source_object,
+ GAsyncResult *res,
+ gpointer callback_data)
+{
+ NautilusFile *file;
+ GFileInfo *new_info;
+ GError *error;
+
+ file = callback_data;
+
+ error = NULL;
+ new_info = g_file_query_info_finish (G_FILE (source_object), res, &error);
+ if (new_info != NULL)
+ {
+ if (nautilus_file_update_info (file, new_info))
+ {
+ nautilus_file_changed (file);
+ }
+ g_object_unref (new_info);
+ }
+ nautilus_file_unref (file);
+ if (error)
+ {
+ g_error_free (error);
+ }
+}
+
+static void
+set_metadata_callback (GObject *source_object,
+ GAsyncResult *result,
+ gpointer callback_data)
+{
+ NautilusFile *file;
+ GError *error;
+ gboolean res;
+
+ file = callback_data;
+
+ error = NULL;
+ res = g_file_set_attributes_finish (G_FILE (source_object),
+ result,
+ NULL,
+ &error);
+
+ if (res)
+ {
+ g_file_query_info_async (G_FILE (source_object),
+ NAUTILUS_FILE_DEFAULT_ATTRIBUTES,
+ 0,
+ G_PRIORITY_DEFAULT,
+ NULL,
+ set_metadata_get_info_callback, file);
+ }
+ else
+ {
+ nautilus_file_unref (file);
+ g_error_free (error);
+ }
+}
+
+static void
+vfs_file_set_metadata (NautilusFile *file,
+ const char *key,
+ const char *value)
+{
+ GFileInfo *info;
+ GFile *location;
+ char *gio_key;
+
+ info = g_file_info_new ();
+
+ gio_key = g_strconcat ("metadata::", key, NULL);
+ if (value != NULL)
+ {
+ g_file_info_set_attribute_string (info, gio_key, value);
+ }
+ else
+ {
+ /* Unset the key */
+ g_file_info_set_attribute (info, gio_key,
+ G_FILE_ATTRIBUTE_TYPE_INVALID,
+ NULL);
+ }
+ g_free (gio_key);
+
+ location = nautilus_file_get_location (file);
+ g_file_set_attributes_async (location,
+ info,
+ 0,
+ G_PRIORITY_DEFAULT,
+ NULL,
+ set_metadata_callback,
+ nautilus_file_ref (file));
+ g_object_unref (location);
+ g_object_unref (info);
+}
+
+static void
+vfs_file_set_metadata_as_list (NautilusFile *file,
+ const char *key,
+ char **value)
+{
+ GFile *location;
+ GFileInfo *info;
+ char *gio_key;
+
+ info = g_file_info_new ();
+
+ gio_key = g_strconcat ("metadata::", key, NULL);
+ if (value == NULL)
+ {
+ g_file_info_set_attribute (info, gio_key, G_FILE_ATTRIBUTE_TYPE_INVALID, NULL);
+ }
+ else
+ {
+ g_file_info_set_attribute_stringv (info, gio_key, value);
+ }
+ g_free (gio_key);
+
+ location = nautilus_file_get_location (file);
+ g_file_set_attributes_async (location,
+ info,
+ 0,
+ G_PRIORITY_DEFAULT,
+ NULL,
+ set_metadata_callback,
+ nautilus_file_ref (file));
+ g_object_unref (info);
+ g_object_unref (location);
+}
+
+static gboolean
+vfs_file_get_date (NautilusFile *file,
+ NautilusDateType date_type,
+ time_t *date)
+{
+ time_t atime;
+ time_t mtime;
+ time_t btime;
+ time_t recency;
+ time_t trash_time;
+
+ atime = nautilus_file_get_atime (file);
+ mtime = nautilus_file_get_mtime (file);
+ btime = nautilus_file_get_btime (file);
+ recency = nautilus_file_get_recency (file);
+ trash_time = nautilus_file_get_trash_time (file);
+
+ switch (date_type)
+ {
+ case NAUTILUS_DATE_TYPE_ACCESSED:
+ {
+ /* Before we have info on a file, the date is unknown. */
+ if (atime == 0)
+ {
+ return FALSE;
+ }
+ if (date != NULL)
+ {
+ *date = atime;
+ }
+ return TRUE;
+ }
+
+ case NAUTILUS_DATE_TYPE_MODIFIED:
+ {
+ /* Before we have info on a file, the date is unknown. */
+ if (mtime == 0)
+ {
+ return FALSE;
+ }
+ if (date != NULL)
+ {
+ *date = mtime;
+ }
+ return TRUE;
+ }
+
+ case NAUTILUS_DATE_TYPE_CREATED:
+ {
+ /* Before we have info on a file, the date is unknown. */
+ if (btime == 0)
+ {
+ return FALSE;
+ }
+ if (date != NULL)
+ {
+ *date = btime;
+ }
+ return TRUE;
+ }
+
+ case NAUTILUS_DATE_TYPE_TRASHED:
+ {
+ /* Before we have info on a file, the date is unknown. */
+ if (trash_time == 0)
+ {
+ return FALSE;
+ }
+ if (date != NULL)
+ {
+ *date = trash_time;
+ }
+ return TRUE;
+ }
+
+ case NAUTILUS_DATE_TYPE_RECENCY:
+ {
+ /* Before we have info on a file, the date is unknown. */
+ if (recency == 0)
+ {
+ return FALSE;
+ }
+ if (date != NULL)
+ {
+ *date = recency;
+ }
+ return TRUE;
+ }
+ }
+ return FALSE;
+}
+
+static char *
+vfs_file_get_where_string (NautilusFile *file)
+{
+ GFile *activation_location;
+ NautilusFile *location;
+ char *where_string;
+
+ if (!nautilus_file_is_in_recent (file))
+ {
+ location = nautilus_file_ref (file);
+ }
+ else
+ {
+ activation_location = nautilus_file_get_activation_location (file);
+ location = nautilus_file_get (activation_location);
+ g_object_unref (activation_location);
+ }
+
+ where_string = nautilus_file_get_parent_uri_for_display (location);
+
+ nautilus_file_unref (location);
+ return where_string;
+}
+
+static void
+vfs_file_mount_callback (GObject *source_object,
+ GAsyncResult *res,
+ gpointer callback_data)
+{
+ NautilusFileOperation *op;
+ GFile *mounted_on;
+ GError *error;
+
+ op = callback_data;
+
+ error = NULL;
+ mounted_on = g_file_mount_mountable_finish (G_FILE (source_object),
+ res, &error);
+ nautilus_file_operation_complete (op, mounted_on, error);
+ if (mounted_on)
+ {
+ g_object_unref (mounted_on);
+ }
+ if (error)
+ {
+ g_error_free (error);
+ }
+}
+
+
+static void
+vfs_file_mount (NautilusFile *file,
+ GMountOperation *mount_op,
+ GCancellable *cancellable,
+ NautilusFileOperationCallback callback,
+ gpointer callback_data)
+{
+ GFileType type;
+ NautilusFileOperation *op;
+ GError *error;
+ GFile *location;
+
+ type = nautilus_file_get_file_type (file);
+ if (type != G_FILE_TYPE_MOUNTABLE)
+ {
+ if (callback)
+ {
+ error = NULL;
+ g_set_error_literal (&error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED,
+ _("This file cannot be mounted"));
+ callback (file, NULL, error, callback_data);
+ g_error_free (error);
+ }
+ return;
+ }
+
+ op = nautilus_file_operation_new (file, callback, callback_data);
+ if (cancellable)
+ {
+ g_object_unref (op->cancellable);
+ op->cancellable = g_object_ref (cancellable);
+ }
+
+ location = nautilus_file_get_location (file);
+ g_file_mount_mountable (location,
+ 0,
+ mount_op,
+ op->cancellable,
+ vfs_file_mount_callback,
+ op);
+ g_object_unref (location);
+}
+
+static void
+vfs_file_unmount_callback (GObject *source_object,
+ GAsyncResult *res,
+ gpointer callback_data)
+{
+ NautilusFileOperation *op;
+ gboolean unmounted;
+ GError *error;
+
+ op = callback_data;
+
+ error = NULL;
+ unmounted = g_file_unmount_mountable_with_operation_finish (G_FILE (source_object),
+ res, &error);
+
+ if (!unmounted &&
+ error->domain == G_IO_ERROR &&
+ (error->code == G_IO_ERROR_FAILED_HANDLED ||
+ error->code == G_IO_ERROR_CANCELLED))
+ {
+ g_error_free (error);
+ error = NULL;
+ }
+
+ nautilus_file_operation_complete (op, G_FILE (source_object), error);
+ if (error)
+ {
+ g_error_free (error);
+ }
+}
+
+static void
+vfs_file_unmount (NautilusFile *file,
+ GMountOperation *mount_op,
+ GCancellable *cancellable,
+ NautilusFileOperationCallback callback,
+ gpointer callback_data)
+{
+ NautilusFileOperation *op;
+ GFile *location;
+
+ op = nautilus_file_operation_new (file, callback, callback_data);
+ if (cancellable)
+ {
+ g_object_unref (op->cancellable);
+ op->cancellable = g_object_ref (cancellable);
+ }
+
+ location = nautilus_file_get_location (file);
+ g_file_unmount_mountable_with_operation (location,
+ G_MOUNT_UNMOUNT_NONE,
+ mount_op,
+ op->cancellable,
+ vfs_file_unmount_callback,
+ op);
+ g_object_unref (location);
+}
+
+static void
+vfs_file_eject_callback (GObject *source_object,
+ GAsyncResult *res,
+ gpointer callback_data)
+{
+ NautilusFileOperation *op;
+ gboolean ejected;
+ GError *error;
+
+ op = callback_data;
+
+ error = NULL;
+ ejected = g_file_eject_mountable_with_operation_finish (G_FILE (source_object),
+ res, &error);
+
+ if (!ejected &&
+ error->domain == G_IO_ERROR &&
+ (error->code == G_IO_ERROR_FAILED_HANDLED ||
+ error->code == G_IO_ERROR_CANCELLED))
+ {
+ g_error_free (error);
+ error = NULL;
+ }
+
+ nautilus_file_operation_complete (op, G_FILE (source_object), error);
+ if (error)
+ {
+ g_error_free (error);
+ }
+}
+
+static void
+vfs_file_eject (NautilusFile *file,
+ GMountOperation *mount_op,
+ GCancellable *cancellable,
+ NautilusFileOperationCallback callback,
+ gpointer callback_data)
+{
+ NautilusFileOperation *op;
+ GFile *location;
+
+ op = nautilus_file_operation_new (file, callback, callback_data);
+ if (cancellable)
+ {
+ g_object_unref (op->cancellable);
+ op->cancellable = g_object_ref (cancellable);
+ }
+
+ location = nautilus_file_get_location (file);
+ g_file_eject_mountable_with_operation (location,
+ G_MOUNT_UNMOUNT_NONE,
+ mount_op,
+ op->cancellable,
+ vfs_file_eject_callback,
+ op);
+ g_object_unref (location);
+}
+
+static void
+vfs_file_start_callback (GObject *source_object,
+ GAsyncResult *res,
+ gpointer callback_data)
+{
+ NautilusFileOperation *op;
+ gboolean started;
+ GError *error;
+
+ op = callback_data;
+
+ error = NULL;
+ started = g_file_start_mountable_finish (G_FILE (source_object),
+ res, &error);
+
+ if (!started &&
+ error->domain == G_IO_ERROR &&
+ (error->code == G_IO_ERROR_FAILED_HANDLED ||
+ error->code == G_IO_ERROR_CANCELLED))
+ {
+ g_error_free (error);
+ error = NULL;
+ }
+
+ nautilus_file_operation_complete (op, G_FILE (source_object), error);
+ if (error)
+ {
+ g_error_free (error);
+ }
+}
+
+
+static void
+vfs_file_start (NautilusFile *file,
+ GMountOperation *mount_op,
+ GCancellable *cancellable,
+ NautilusFileOperationCallback callback,
+ gpointer callback_data)
+{
+ GFileType type;
+ NautilusFileOperation *op;
+ GError *error;
+ GFile *location;
+
+ type = nautilus_file_get_file_type (file);
+ if (type != G_FILE_TYPE_MOUNTABLE)
+ {
+ if (callback)
+ {
+ error = NULL;
+ g_set_error_literal (&error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED,
+ _("This file cannot be started"));
+ callback (file, NULL, error, callback_data);
+ g_error_free (error);
+ }
+ return;
+ }
+
+ op = nautilus_file_operation_new (file, callback, callback_data);
+ if (cancellable)
+ {
+ g_object_unref (op->cancellable);
+ op->cancellable = g_object_ref (cancellable);
+ }
+
+ location = nautilus_file_get_location (file);
+ g_file_start_mountable (location,
+ 0,
+ mount_op,
+ op->cancellable,
+ vfs_file_start_callback,
+ op);
+ g_object_unref (location);
+}
+
+static void
+vfs_file_stop_callback (GObject *source_object,
+ GAsyncResult *res,
+ gpointer callback_data)
+{
+ NautilusFileOperation *op;
+ gboolean stopped;
+ GError *error;
+
+ op = callback_data;
+
+ error = NULL;
+ stopped = g_file_stop_mountable_finish (G_FILE (source_object),
+ res, &error);
+
+ if (!stopped &&
+ error->domain == G_IO_ERROR &&
+ (error->code == G_IO_ERROR_FAILED_HANDLED ||
+ error->code == G_IO_ERROR_CANCELLED))
+ {
+ g_error_free (error);
+ error = NULL;
+ }
+
+ nautilus_file_operation_complete (op, G_FILE (source_object), error);
+ if (error)
+ {
+ g_error_free (error);
+ }
+}
+
+static void
+vfs_file_stop (NautilusFile *file,
+ GMountOperation *mount_op,
+ GCancellable *cancellable,
+ NautilusFileOperationCallback callback,
+ gpointer callback_data)
+{
+ NautilusFileOperation *op;
+ GFile *location;
+
+ op = nautilus_file_operation_new (file, callback, callback_data);
+ if (cancellable)
+ {
+ g_object_unref (op->cancellable);
+ op->cancellable = g_object_ref (cancellable);
+ }
+
+ location = nautilus_file_get_location (file);
+ g_file_stop_mountable (location,
+ G_MOUNT_UNMOUNT_NONE,
+ mount_op,
+ op->cancellable,
+ vfs_file_stop_callback,
+ op);
+ g_object_unref (location);
+}
+
+static void
+vfs_file_poll_callback (GObject *source_object,
+ GAsyncResult *res,
+ gpointer callback_data)
+{
+ NautilusFileOperation *op;
+ gboolean stopped;
+ GError *error;
+
+ op = callback_data;
+
+ error = NULL;
+ stopped = g_file_poll_mountable_finish (G_FILE (source_object),
+ res, &error);
+
+ if (!stopped &&
+ error->domain == G_IO_ERROR &&
+ (error->code == G_IO_ERROR_FAILED_HANDLED ||
+ error->code == G_IO_ERROR_CANCELLED))
+ {
+ g_error_free (error);
+ error = NULL;
+ }
+
+ nautilus_file_operation_complete (op, G_FILE (source_object), error);
+ if (error)
+ {
+ g_error_free (error);
+ }
+}
+
+static void
+vfs_file_poll_for_media (NautilusFile *file)
+{
+ NautilusFileOperation *op;
+ GFile *location;
+
+ op = nautilus_file_operation_new (file, NULL, NULL);
+
+ location = nautilus_file_get_location (file);
+ g_file_poll_mountable (location,
+ op->cancellable,
+ vfs_file_poll_callback,
+ op);
+ g_object_unref (location);
+}
+
+static void
+nautilus_vfs_file_init (NautilusVFSFile *file)
+{
+}
+
+static void
+nautilus_vfs_file_class_init (NautilusVFSFileClass *klass)
+{
+ NautilusFileClass *file_class = NAUTILUS_FILE_CLASS (klass);
+
+ file_class->monitor_add = vfs_file_monitor_add;
+ file_class->monitor_remove = vfs_file_monitor_remove;
+ file_class->call_when_ready = vfs_file_call_when_ready;
+ file_class->cancel_call_when_ready = vfs_file_cancel_call_when_ready;
+ file_class->check_if_ready = vfs_file_check_if_ready;
+ file_class->get_date = vfs_file_get_date;
+ file_class->get_where_string = vfs_file_get_where_string;
+ file_class->set_metadata = vfs_file_set_metadata;
+ file_class->set_metadata_as_list = vfs_file_set_metadata_as_list;
+ file_class->mount = vfs_file_mount;
+ file_class->unmount = vfs_file_unmount;
+ file_class->eject = vfs_file_eject;
+ file_class->start = vfs_file_start;
+ file_class->stop = vfs_file_stop;
+ file_class->poll_for_media = vfs_file_poll_for_media;
+}
diff --git a/src/nautilus-vfs-file.h b/src/nautilus-vfs-file.h
new file mode 100644
index 0000000..cbb21ab
--- /dev/null
+++ b/src/nautilus-vfs-file.h
@@ -0,0 +1,49 @@
+/*
+ nautilus-vfs-file.h: Subclass of NautilusFile to implement the
+ the case of a VFS file.
+
+ Copyright (C) 1999, 2000 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/>.
+
+ Author: Darin Adler <darin@bentspoon.com>
+*/
+
+#pragma once
+
+#include "nautilus-file.h"
+
+#define NAUTILUS_TYPE_VFS_FILE nautilus_vfs_file_get_type()
+#define NAUTILUS_VFS_FILE(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST ((obj), NAUTILUS_TYPE_VFS_FILE, NautilusVFSFile))
+#define NAUTILUS_VFS_FILE_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST ((klass), NAUTILUS_TYPE_VFS_FILE, NautilusVFSFileClass))
+#define NAUTILUS_IS_VFS_FILE(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE ((obj), NAUTILUS_TYPE_VFS_FILE))
+#define NAUTILUS_IS_VFS_FILE_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE ((klass), NAUTILUS_TYPE_VFS_FILE))
+#define NAUTILUS_VFS_FILE_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), NAUTILUS_TYPE_VFS_FILE, NautilusVFSFileClass))
+
+typedef struct NautilusVFSFileDetails NautilusVFSFileDetails;
+
+typedef struct {
+ NautilusFile parent_slot;
+} NautilusVFSFile;
+
+typedef struct {
+ NautilusFileClass parent_slot;
+} NautilusVFSFileClass;
+
+GType nautilus_vfs_file_get_type (void); \ No newline at end of file
diff --git a/src/nautilus-video-mime-types.h b/src/nautilus-video-mime-types.h
new file mode 100644
index 0000000..e0d4aac
--- /dev/null
+++ b/src/nautilus-video-mime-types.h
@@ -0,0 +1,65 @@
+/* generated with mime-type-include.sh in the totem module, don't edit or
+ commit in the nautilus module without filing a bug against totem */
+static const char *video_mime_types[] = {
+"application/mxf",
+"application/ogg",
+"application/ram",
+"application/sdp",
+"application/vnd.apple.mpegurl",
+"application/vnd.ms-wpl",
+"application/vnd.rn-realmedia",
+"application/x-extension-m4a",
+"application/x-extension-mp4",
+"application/x-flash-video",
+"application/x-matroska",
+"application/x-netshow-channel",
+"application/x-ogg",
+"application/x-quicktimeplayer",
+"application/x-shorten",
+"image/vnd.rn-realpix",
+"image/x-pict",
+"misc/ultravox",
+"text/x-google-video-pointer",
+"video/3gp",
+"video/3gpp",
+"video/dv",
+"video/divx",
+"video/fli",
+"video/flv",
+"video/mp2t",
+"video/mp4",
+"video/mp4v-es",
+"video/mpeg",
+"video/msvideo",
+"video/ogg",
+"video/quicktime",
+"video/vivo",
+"video/vnd.divx",
+"video/vnd.mpegurl",
+"video/vnd.rn-realvideo",
+"video/vnd.vivo",
+"video/webm",
+"video/x-anim",
+"video/x-avi",
+"video/x-flc",
+"video/x-fli",
+"video/x-flic",
+"video/x-flv",
+"video/x-m4v",
+"video/x-matroska",
+"video/x-mpeg",
+"video/x-mpeg2",
+"video/x-ms-asf",
+"video/x-ms-asx",
+"video/x-msvideo",
+"video/x-ms-wm",
+"video/x-ms-wmv",
+"video/x-ms-wmx",
+"video/x-ms-wvx",
+"video/x-nsv",
+"video/x-ogm+ogg",
+"video/x-theora+ogg",
+"video/x-totem-stream",
+"audio/x-pn-realaudio",
+NULL
+};
diff --git a/src/nautilus-view-cell.c b/src/nautilus-view-cell.c
new file mode 100644
index 0000000..6f28fd8
--- /dev/null
+++ b/src/nautilus-view-cell.c
@@ -0,0 +1,190 @@
+/*
+ * Copyright (C) 2022 2022 António Fernandes <antoniof@gnome.org>
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#include "nautilus-view-cell.h"
+#include "nautilus-list-base.h"
+
+/**
+ * NautilusViewCell:
+ *
+ * Abstract class of widgets tailored to be set as #GtkListItem:child in a view
+ * which subclasses #NautilusListBase.
+ *
+ * Subclass constructors should take a pointer to the #NautilusListBase view.
+ *
+ * The view is responsible for setting #NautilusViewCell:item. This can be done
+ * using a GBinding from #GtkListItem:item to #NautilusViewCell:item.
+ */
+
+typedef struct _NautilusViewCellPrivate NautilusViewCellPrivate;
+struct _NautilusViewCellPrivate
+{
+ AdwBin parent_instance;
+
+ NautilusListBase *view; /* Unowned */
+ NautilusViewItem *item; /* Owned reference */
+
+ gboolean called_once;
+};
+
+
+G_DEFINE_ABSTRACT_TYPE_WITH_PRIVATE (NautilusViewCell, nautilus_view_cell, ADW_TYPE_BIN)
+
+enum
+{
+ PROP_0,
+ PROP_VIEW,
+ PROP_ITEM,
+ N_PROPS
+};
+
+static GParamSpec *properties[N_PROPS] = { NULL, };
+
+static void
+nautilus_view_cell_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ NautilusViewCell *self = NAUTILUS_VIEW_CELL (object);
+ NautilusViewCellPrivate *priv = nautilus_view_cell_get_instance_private (self);
+
+ switch (prop_id)
+ {
+ case PROP_VIEW:
+ {
+ g_value_set_object (value, priv->view);
+ }
+ break;
+
+ case PROP_ITEM:
+ {
+ g_value_set_object (value, priv->item);
+ }
+ break;
+
+ default:
+ {
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+ }
+}
+
+static void
+nautilus_view_cell_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ NautilusViewCell *self = NAUTILUS_VIEW_CELL (object);
+ NautilusViewCellPrivate *priv = nautilus_view_cell_get_instance_private (self);
+
+ switch (prop_id)
+ {
+ case PROP_VIEW:
+ {
+ priv->view = g_value_get_object (value);
+ }
+ break;
+
+ case PROP_ITEM:
+ {
+ g_set_object (&priv->item, g_value_get_object (value));
+ }
+ break;
+
+ default:
+ {
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+ }
+}
+
+static void
+nautilus_view_cell_init (NautilusViewCell *self)
+{
+ gtk_widget_set_name (GTK_WIDGET (self), "NautilusViewCell");
+}
+
+static void
+nautilus_view_cell_finalize (GObject *object)
+{
+ NautilusViewCell *self = NAUTILUS_VIEW_CELL (object);
+ NautilusViewCellPrivate *priv = nautilus_view_cell_get_instance_private (self);
+
+ g_clear_object (&priv->item);
+
+ G_OBJECT_CLASS (nautilus_view_cell_parent_class)->finalize (object);
+}
+
+static void
+nautilus_view_cell_class_init (NautilusViewCellClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->finalize = nautilus_view_cell_finalize;
+ object_class->get_property = nautilus_view_cell_get_property;
+ object_class->set_property = nautilus_view_cell_set_property;
+
+ properties[PROP_VIEW] = g_param_spec_object ("view",
+ "", "",
+ NAUTILUS_TYPE_LIST_BASE,
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
+ properties[PROP_ITEM] = g_param_spec_object ("item",
+ "", "",
+ NAUTILUS_TYPE_VIEW_ITEM,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+
+ g_object_class_install_properties (object_class, N_PROPS, properties);
+}
+
+gboolean
+nautilus_view_cell_once (NautilusViewCell *self)
+{
+ NautilusViewCellPrivate *priv = nautilus_view_cell_get_instance_private (self);
+
+ if (priv->called_once)
+ {
+ return FALSE;
+ }
+ priv->called_once = TRUE;
+
+ return TRUE;
+}
+
+NautilusListBase *
+nautilus_view_cell_get_view (NautilusViewCell *self)
+{
+ NautilusListBase *view;
+
+ g_return_val_if_fail (NAUTILUS_IS_VIEW_CELL (self), NULL);
+
+ g_object_get (self, "view", &view, NULL);
+
+ return view;
+}
+
+void
+nautilus_view_cell_set_item (NautilusViewCell *self,
+ NautilusViewItem *item)
+{
+ g_return_if_fail (NAUTILUS_IS_VIEW_CELL (self));
+ g_return_if_fail (item == NULL || NAUTILUS_IS_VIEW_ITEM (item));
+
+ g_object_set (self, "item", item, NULL);
+}
+
+NautilusViewItem *
+nautilus_view_cell_get_item (NautilusViewCell *self)
+{
+ NautilusViewItem *item;
+
+ g_return_val_if_fail (NAUTILUS_IS_VIEW_CELL (self), NULL);
+
+ g_object_get (self, "item", &item, NULL);
+
+ return item;
+}
diff --git a/src/nautilus-view-cell.h b/src/nautilus-view-cell.h
new file mode 100644
index 0000000..78297b8
--- /dev/null
+++ b/src/nautilus-view-cell.h
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2022 António Fernandes <antoniof@gnome.org>
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#pragma once
+
+#include <libadwaita-1/adwaita.h>
+
+#include "nautilus-types.h"
+#include "nautilus-view-item.h"
+
+G_BEGIN_DECLS
+
+#define NAUTILUS_TYPE_VIEW_CELL (nautilus_view_cell_get_type())
+
+G_DECLARE_DERIVABLE_TYPE (NautilusViewCell, nautilus_view_cell, NAUTILUS, VIEW_CELL, AdwBin)
+
+struct _NautilusViewCellClass
+{
+ AdwBinClass parent_class;
+};
+
+NautilusListBase *nautilus_view_cell_get_view (NautilusViewCell *self);
+void nautilus_view_cell_set_item (NautilusViewCell *self,
+ NautilusViewItem *item);
+NautilusViewItem *nautilus_view_cell_get_item (NautilusViewCell *self);
+gboolean nautilus_view_cell_once (NautilusViewCell *self);
+
+G_END_DECLS
diff --git a/src/nautilus-view-controls.c b/src/nautilus-view-controls.c
new file mode 100644
index 0000000..bd8e6d7
--- /dev/null
+++ b/src/nautilus-view-controls.c
@@ -0,0 +1,187 @@
+/*
+ * Copyright (C) 2022 The GNOME project contributors
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include "nautilus-view-controls.h"
+
+#include "nautilus-toolbar-menu-sections.h"
+#include "nautilus-window.h"
+
+struct _NautilusViewControls
+{
+ AdwBin parent_instance;
+
+ GtkWidget *view_split_button;
+ GMenuModel *view_menu;
+
+ NautilusWindowSlot *window_slot;
+};
+
+G_DEFINE_FINAL_TYPE (NautilusViewControls, nautilus_view_controls, ADW_TYPE_BIN);
+
+
+enum
+{
+ PROP_0,
+ PROP_WINDOW_SLOT,
+ N_PROPS
+};
+
+static GParamSpec *properties[N_PROPS];
+
+static void
+on_slot_toolbar_menu_sections_changed (NautilusViewControls *self,
+ GParamSpec *param,
+ NautilusWindowSlot *slot)
+{
+ NautilusToolbarMenuSections *new_sections;
+ g_autoptr (GMenuItem) zoom_item = NULL;
+ g_autoptr (GMenuItem) sort_item = NULL;
+
+ new_sections = nautilus_window_slot_get_toolbar_menu_sections (slot);
+
+ gtk_widget_set_sensitive (self->view_split_button, (new_sections != NULL));
+ if (new_sections == NULL)
+ {
+ return;
+ }
+
+ /* Let's assume that sort section is the first item
+ * in view_menu, as per nautilus-toolbar.ui. */
+
+ sort_item = g_menu_item_new_from_model (self->view_menu, 0);
+ g_menu_remove (G_MENU (self->view_menu), 0);
+ g_menu_item_set_section (sort_item, new_sections->sort_section);
+ g_menu_insert_item (G_MENU (self->view_menu), 0, sort_item);
+}
+
+static void
+disconnect_toolbar_menu_sections_change_handler (NautilusViewControls *self)
+{
+ if (self->window_slot == NULL)
+ {
+ return;
+ }
+
+ g_signal_handlers_disconnect_by_func (self->window_slot,
+ G_CALLBACK (on_slot_toolbar_menu_sections_changed),
+ self);
+}
+
+
+static void
+nautilus_view_controls_set_window_slot (NautilusViewControls *self,
+ NautilusWindowSlot *window_slot)
+{
+ g_return_if_fail (NAUTILUS_IS_VIEW_CONTROLS (self));
+ g_return_if_fail (window_slot == NULL || NAUTILUS_IS_WINDOW_SLOT (window_slot));
+
+ if (self->window_slot == window_slot)
+ {
+ return;
+ }
+
+ disconnect_toolbar_menu_sections_change_handler (self);
+
+ self->window_slot = window_slot;
+
+ if (self->window_slot != NULL)
+ {
+ on_slot_toolbar_menu_sections_changed (self, NULL, self->window_slot);
+ g_signal_connect_swapped (self->window_slot, "notify::toolbar-menu-sections",
+ G_CALLBACK (on_slot_toolbar_menu_sections_changed), self);
+ }
+
+ g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_WINDOW_SLOT]);
+}
+
+static void
+nautilus_view_controls_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ NautilusViewControls *self = NAUTILUS_VIEW_CONTROLS (object);
+
+ switch (prop_id)
+ {
+ case PROP_WINDOW_SLOT:
+ {
+ g_value_set_object (value, G_OBJECT (self->window_slot));
+ break;
+ }
+
+ default:
+ {
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+ }
+}
+
+static void
+nautilus_view_controls_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ NautilusViewControls *self = NAUTILUS_VIEW_CONTROLS (object);
+
+ switch (prop_id)
+ {
+ case PROP_WINDOW_SLOT:
+ {
+ nautilus_view_controls_set_window_slot (self, NAUTILUS_WINDOW_SLOT (g_value_get_object (value)));
+ break;
+ }
+
+ default:
+ {
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+ }
+}
+
+static void
+nautilus_view_controls_finalize (GObject *obj)
+{
+ NautilusViewControls *self = NAUTILUS_VIEW_CONTROLS (obj);
+
+ if (self->window_slot != NULL)
+ {
+ g_signal_handlers_disconnect_by_data (self->window_slot, self);
+ self->window_slot = NULL;
+ }
+
+ G_OBJECT_CLASS (nautilus_view_controls_parent_class)->finalize (obj);
+}
+
+static void
+nautilus_view_controls_class_init (NautilusViewControlsClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+
+ object_class->finalize = nautilus_view_controls_finalize;
+ object_class->get_property = nautilus_view_controls_get_property;
+ object_class->set_property = nautilus_view_controls_set_property;
+
+ properties[PROP_WINDOW_SLOT] = g_param_spec_object ("window-slot",
+ NULL, NULL,
+ NAUTILUS_TYPE_WINDOW_SLOT,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_EXPLICIT_NOTIFY);
+
+ g_object_class_install_properties (object_class, N_PROPS, properties);
+
+ gtk_widget_class_set_template_from_resource (widget_class,
+ "/org/gnome/nautilus/ui/nautilus-view-controls.ui");
+ gtk_widget_class_bind_template_child (widget_class, NautilusViewControls, view_menu);
+ gtk_widget_class_bind_template_child (widget_class, NautilusViewControls, view_split_button);
+}
+
+static void
+nautilus_view_controls_init (NautilusViewControls *self)
+{
+ gtk_widget_init_template (GTK_WIDGET (self));
+}
diff --git a/src/nautilus-view-controls.h b/src/nautilus-view-controls.h
new file mode 100644
index 0000000..1b54737
--- /dev/null
+++ b/src/nautilus-view-controls.h
@@ -0,0 +1,20 @@
+/*
+ * Copyright (C) 2022 The GNOME project contributors
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#pragma once
+
+#include <gtk/gtk.h>
+#include <libadwaita-1/adwaita.h>
+
+#include "nautilus-window-slot.h"
+
+G_BEGIN_DECLS
+
+#define NAUTILUS_TYPE_VIEW_CONTROLS (nautilus_view_controls_get_type())
+
+G_DECLARE_FINAL_TYPE (NautilusViewControls, nautilus_view_controls, NAUTILUS, VIEW_CONTROLS, AdwBin)
+
+G_END_DECLS
diff --git a/src/nautilus-view-item.c b/src/nautilus-view-item.c
new file mode 100644
index 0000000..84423fa
--- /dev/null
+++ b/src/nautilus-view-item.c
@@ -0,0 +1,280 @@
+/*
+ * Copyright (C) 2022 The GNOME project contributors
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#include "nautilus-view-item.h"
+
+struct _NautilusViewItem
+{
+ GObject parent_instance;
+ guint icon_size;
+ gboolean is_cut;
+ gboolean drag_accept;
+ NautilusFile *file;
+ GtkWidget *item_ui;
+};
+
+G_DEFINE_TYPE (NautilusViewItem, nautilus_view_item, G_TYPE_OBJECT)
+
+enum
+{
+ PROP_0,
+ PROP_FILE,
+ PROP_ICON_SIZE,
+ PROP_IS_CUT,
+ PROP_DRAG_ACCEPT,
+ PROP_ITEM_UI,
+ N_PROPS
+};
+
+static GParamSpec *properties[N_PROPS] = { NULL, };
+
+enum
+{
+ FILE_CHANGED,
+ LAST_SIGNAL
+};
+
+static guint signals[LAST_SIGNAL];
+
+static void
+nautilus_view_item_dispose (GObject *object)
+{
+ NautilusViewItem *self = NAUTILUS_VIEW_ITEM (object);
+
+ g_clear_object (&self->item_ui);
+
+ G_OBJECT_CLASS (nautilus_view_item_parent_class)->dispose (object);
+}
+
+static void
+nautilus_view_item_finalize (GObject *object)
+{
+ NautilusViewItem *self = NAUTILUS_VIEW_ITEM (object);
+
+ g_clear_object (&self->file);
+
+ G_OBJECT_CLASS (nautilus_view_item_parent_class)->finalize (object);
+}
+
+static void
+nautilus_view_item_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ NautilusViewItem *self = NAUTILUS_VIEW_ITEM (object);
+
+ switch (prop_id)
+ {
+ case PROP_FILE:
+ {
+ g_value_set_object (value, self->file);
+ }
+ break;
+
+ case PROP_ICON_SIZE:
+ {
+ g_value_set_int (value, self->icon_size);
+ }
+ break;
+
+ case PROP_IS_CUT:
+ {
+ g_value_set_boolean (value, self->is_cut);
+ }
+ break;
+
+ case PROP_DRAG_ACCEPT:
+ {
+ g_value_set_boolean (value, self->drag_accept);
+ }
+ break;
+
+ case PROP_ITEM_UI:
+ {
+ g_value_set_object (value, self->item_ui);
+ }
+ break;
+
+ default:
+ {
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+ }
+}
+
+static void
+nautilus_view_item_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ NautilusViewItem *self = NAUTILUS_VIEW_ITEM (object);
+
+ switch (prop_id)
+ {
+ case PROP_FILE:
+ {
+ self->file = g_value_dup_object (value);
+ }
+ break;
+
+ case PROP_ICON_SIZE:
+ {
+ self->icon_size = g_value_get_int (value);
+ }
+ break;
+
+ case PROP_IS_CUT:
+ {
+ self->is_cut = g_value_get_boolean (value);
+ }
+ break;
+
+ case PROP_DRAG_ACCEPT:
+ {
+ self->drag_accept = g_value_get_boolean (value);
+ }
+ break;
+
+ case PROP_ITEM_UI:
+ {
+ g_set_object (&self->item_ui, g_value_get_object (value));
+ }
+ break;
+
+ default:
+ {
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+ }
+}
+
+static void
+nautilus_view_item_init (NautilusViewItem *self)
+{
+}
+
+static void
+nautilus_view_item_class_init (NautilusViewItemClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->dispose = nautilus_view_item_dispose;
+ object_class->finalize = nautilus_view_item_finalize;
+ object_class->get_property = nautilus_view_item_get_property;
+ object_class->set_property = nautilus_view_item_set_property;
+
+ properties[PROP_ICON_SIZE] = g_param_spec_int ("icon-size",
+ "", "",
+ NAUTILUS_LIST_ICON_SIZE_SMALL,
+ NAUTILUS_GRID_ICON_SIZE_EXTRA_LARGE,
+ NAUTILUS_GRID_ICON_SIZE_LARGE,
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS);
+ properties[PROP_IS_CUT] = g_param_spec_boolean ("is-cut",
+ "", "",
+ FALSE,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+ properties[PROP_DRAG_ACCEPT] = g_param_spec_boolean ("drag-accept",
+ "", "",
+ FALSE,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+ properties[PROP_FILE] = g_param_spec_object ("file",
+ "", "",
+ NAUTILUS_TYPE_FILE,
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
+ properties[PROP_ITEM_UI] = g_param_spec_object ("item-ui",
+ "", "",
+ GTK_TYPE_WIDGET,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_properties (object_class, N_PROPS, properties);
+
+ signals[FILE_CHANGED] = g_signal_new ("file-changed",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST,
+ 0,
+ NULL, NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+}
+
+NautilusViewItem *
+nautilus_view_item_new (NautilusFile *file,
+ guint icon_size)
+{
+ return g_object_new (NAUTILUS_TYPE_VIEW_ITEM,
+ "file", file,
+ "icon-size", icon_size,
+ NULL);
+}
+
+guint
+nautilus_view_item_get_icon_size (NautilusViewItem *self)
+{
+ g_return_val_if_fail (NAUTILUS_IS_VIEW_ITEM (self), -1);
+
+ return self->icon_size;
+}
+
+void
+nautilus_view_item_set_icon_size (NautilusViewItem *self,
+ guint icon_size)
+{
+ g_return_if_fail (NAUTILUS_IS_VIEW_ITEM (self));
+
+ g_object_set (self, "icon-size", icon_size, NULL);
+}
+
+void
+nautilus_view_item_set_cut (NautilusViewItem *self,
+ gboolean is_cut)
+{
+ g_return_if_fail (NAUTILUS_IS_VIEW_ITEM (self));
+
+ g_object_set (self, "is-cut", is_cut, NULL);
+}
+
+void
+nautilus_view_item_set_drag_accept (NautilusViewItem *self,
+ gboolean drag_accept)
+{
+ g_return_if_fail (NAUTILUS_IS_VIEW_ITEM (self));
+
+ g_object_set (self, "drag-accept", drag_accept, NULL);
+}
+
+NautilusFile *
+nautilus_view_item_get_file (NautilusViewItem *self)
+{
+ g_return_val_if_fail (NAUTILUS_IS_VIEW_ITEM (self), NULL);
+
+ return self->file;
+}
+
+GtkWidget *
+nautilus_view_item_get_item_ui (NautilusViewItem *self)
+{
+ g_return_val_if_fail (NAUTILUS_IS_VIEW_ITEM (self), NULL);
+
+ return self->item_ui;
+}
+
+void
+nautilus_view_item_set_item_ui (NautilusViewItem *self,
+ GtkWidget *item_ui)
+{
+ g_return_if_fail (NAUTILUS_IS_VIEW_ITEM (self));
+
+ g_object_set (self, "item-ui", item_ui, NULL);
+}
+
+void
+nautilus_view_item_file_changed (NautilusViewItem *self)
+{
+ g_return_if_fail (NAUTILUS_IS_VIEW_ITEM (self));
+
+ g_signal_emit (self, signals[FILE_CHANGED], 0);
+}
diff --git a/src/nautilus-view-item.h b/src/nautilus-view-item.h
new file mode 100644
index 0000000..9bdaff1
--- /dev/null
+++ b/src/nautilus-view-item.h
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2022 The GNOME project contributors
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#pragma once
+
+#include <glib.h>
+#include <gtk/gtk.h>
+
+#include "nautilus-file.h"
+
+G_BEGIN_DECLS
+
+#define NAUTILUS_TYPE_VIEW_ITEM (nautilus_view_item_get_type())
+
+G_DECLARE_FINAL_TYPE (NautilusViewItem, nautilus_view_item, NAUTILUS, VIEW_ITEM, GObject)
+
+NautilusViewItem * nautilus_view_item_new (NautilusFile *file,
+ guint icon_size);
+
+void nautilus_view_item_set_icon_size (NautilusViewItem *self,
+ guint icon_size);
+
+guint nautilus_view_item_get_icon_size (NautilusViewItem *self);
+void nautilus_view_item_set_cut (NautilusViewItem *self,
+ gboolean is_cut);
+void nautilus_view_item_set_drag_accept (NautilusViewItem *self,
+ gboolean drag_accept);
+
+NautilusFile * nautilus_view_item_get_file (NautilusViewItem *self);
+
+void nautilus_view_item_set_item_ui (NautilusViewItem *self,
+ GtkWidget *item_ui);
+
+GtkWidget * nautilus_view_item_get_item_ui (NautilusViewItem *self);
+void nautilus_view_item_file_changed (NautilusViewItem *self);
+
+G_END_DECLS
diff --git a/src/nautilus-view-model.c b/src/nautilus-view-model.c
new file mode 100644
index 0000000..2cb1c47
--- /dev/null
+++ b/src/nautilus-view-model.c
@@ -0,0 +1,422 @@
+#include "nautilus-view-model.h"
+#include "nautilus-view-item.h"
+#include "nautilus-global-preferences.h"
+
+struct _NautilusViewModel
+{
+ GObject parent_instance;
+
+ GHashTable *map_files_to_model;
+ GListStore *internal_model;
+ GtkMultiSelection *selection_model;
+ GtkSorter *sorter;
+ gulong sorter_changed_id;
+};
+
+static GType
+nautilus_view_model_get_item_type (GListModel *list)
+{
+ return NAUTILUS_TYPE_VIEW_ITEM;
+}
+
+static guint
+nautilus_view_model_get_n_items (GListModel *list)
+{
+ NautilusViewModel *self = NAUTILUS_VIEW_MODEL (list);
+
+ if (self->internal_model == NULL)
+ {
+ return 0;
+ }
+
+ return g_list_model_get_n_items (G_LIST_MODEL (self->internal_model));
+}
+
+static gpointer
+nautilus_view_model_get_item (GListModel *list,
+ guint position)
+{
+ NautilusViewModel *self = NAUTILUS_VIEW_MODEL (list);
+
+ if (self->internal_model == NULL)
+ {
+ return NULL;
+ }
+
+ return g_list_model_get_item (G_LIST_MODEL (self->internal_model), position);
+}
+
+static void
+nautilus_view_model_list_model_init (GListModelInterface *iface)
+{
+ iface->get_item_type = nautilus_view_model_get_item_type;
+ iface->get_n_items = nautilus_view_model_get_n_items;
+ iface->get_item = nautilus_view_model_get_item;
+}
+
+
+static gboolean
+nautilus_view_model_is_selected (GtkSelectionModel *model,
+ guint position)
+{
+ NautilusViewModel *self = NAUTILUS_VIEW_MODEL (model);
+ GtkSelectionModel *selection_model = GTK_SELECTION_MODEL (self->selection_model);
+
+ return gtk_selection_model_is_selected (selection_model, position);
+}
+
+static GtkBitset *
+nautilus_view_model_get_selection_in_range (GtkSelectionModel *model,
+ guint pos,
+ guint n_items)
+{
+ NautilusViewModel *self = NAUTILUS_VIEW_MODEL (model);
+ GtkSelectionModel *selection_model = GTK_SELECTION_MODEL (self->selection_model);
+
+ return gtk_selection_model_get_selection_in_range (selection_model, pos, n_items);
+}
+
+static gboolean
+nautilus_view_model_set_selection (GtkSelectionModel *model,
+ GtkBitset *selected,
+ GtkBitset *mask)
+{
+ NautilusViewModel *self = NAUTILUS_VIEW_MODEL (model);
+ GtkSelectionModel *selection_model = GTK_SELECTION_MODEL (self->selection_model);
+ gboolean res;
+
+ res = gtk_selection_model_set_selection (selection_model, selected, mask);
+
+ return res;
+}
+
+
+static void
+nautilus_view_model_selection_model_init (GtkSelectionModelInterface *iface)
+{
+ iface->is_selected = nautilus_view_model_is_selected;
+ iface->get_selection_in_range = nautilus_view_model_get_selection_in_range;
+ iface->set_selection = nautilus_view_model_set_selection;
+}
+
+G_DEFINE_TYPE_WITH_CODE (NautilusViewModel, nautilus_view_model, G_TYPE_OBJECT,
+ G_IMPLEMENT_INTERFACE (G_TYPE_LIST_MODEL,
+ nautilus_view_model_list_model_init)
+ G_IMPLEMENT_INTERFACE (GTK_TYPE_SELECTION_MODEL,
+ nautilus_view_model_selection_model_init))
+
+enum
+{
+ PROP_0,
+ PROP_SORTER,
+ N_PROPS
+};
+
+static GParamSpec *properties[N_PROPS] = { NULL, };
+
+static void
+dispose (GObject *object)
+{
+ NautilusViewModel *self = NAUTILUS_VIEW_MODEL (object);
+
+ if (self->selection_model != NULL)
+ {
+ g_signal_handlers_disconnect_by_func (self->selection_model,
+ gtk_selection_model_selection_changed,
+ self);
+ g_object_unref (self->selection_model);
+ self->selection_model = NULL;
+ }
+
+ if (self->internal_model != NULL)
+ {
+ g_signal_handlers_disconnect_by_func (self->internal_model,
+ g_list_model_items_changed,
+ self);
+ g_object_unref (self->internal_model);
+ self->internal_model = NULL;
+ }
+
+ g_clear_signal_handler (&self->sorter_changed_id, self->sorter);
+
+ G_OBJECT_CLASS (nautilus_view_model_parent_class)->dispose (object);
+}
+
+static void
+finalize (GObject *object)
+{
+ NautilusViewModel *self = NAUTILUS_VIEW_MODEL (object);
+
+ G_OBJECT_CLASS (nautilus_view_model_parent_class)->finalize (object);
+
+ g_hash_table_destroy (self->map_files_to_model);
+ g_clear_object (&self->sorter);
+}
+
+static void
+get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ NautilusViewModel *self = NAUTILUS_VIEW_MODEL (object);
+
+ switch (prop_id)
+ {
+ case PROP_SORTER:
+ {
+ g_value_set_object (value, nautilus_view_model_get_sorter (self));
+ }
+ break;
+
+ default:
+ {
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+ }
+}
+
+static void
+set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ NautilusViewModel *self = NAUTILUS_VIEW_MODEL (object);
+
+ switch (prop_id)
+ {
+ case PROP_SORTER:
+ {
+ nautilus_view_model_set_sorter (self, g_value_get_object (value));
+ }
+ break;
+
+ default:
+ {
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+ }
+}
+
+static void
+constructed (GObject *object)
+{
+ NautilusViewModel *self = NAUTILUS_VIEW_MODEL (object);
+
+ G_OBJECT_CLASS (nautilus_view_model_parent_class)->constructed (object);
+
+ self->internal_model = g_list_store_new (NAUTILUS_TYPE_VIEW_ITEM);
+ self->selection_model = gtk_multi_selection_new (g_object_ref (G_LIST_MODEL (self->internal_model)));
+ self->map_files_to_model = g_hash_table_new (NULL, NULL);
+
+ g_signal_connect_swapped (self->internal_model, "items-changed",
+ G_CALLBACK (g_list_model_items_changed), self);
+ g_signal_connect_swapped (self->selection_model, "selection-changed",
+ G_CALLBACK (gtk_selection_model_selection_changed), self);
+}
+
+static void
+nautilus_view_model_class_init (NautilusViewModelClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->dispose = dispose;
+ object_class->finalize = finalize;
+ object_class->get_property = get_property;
+ object_class->set_property = set_property;
+ object_class->constructed = constructed;
+
+ properties[PROP_SORTER] =
+ g_param_spec_object ("sorter",
+ "", "",
+ GTK_TYPE_SORTER,
+ G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
+
+ g_object_class_install_properties (object_class, N_PROPS, properties);
+}
+
+static void
+nautilus_view_model_init (NautilusViewModel *self)
+{
+}
+
+static gint
+compare_data_func (gconstpointer a,
+ gconstpointer b,
+ gpointer user_data)
+{
+ NautilusViewModel *self = NAUTILUS_VIEW_MODEL (user_data);
+
+ if (self->sorter == NULL)
+ {
+ return GTK_ORDERING_EQUAL;
+ }
+
+ return gtk_sorter_compare (self->sorter, (gpointer) a, (gpointer) b);
+}
+
+static void
+on_sorter_changed (GtkSorter *sorter,
+ GtkSorterChange change,
+ gpointer user_data)
+{
+ NautilusViewModel *self = NAUTILUS_VIEW_MODEL (user_data);
+
+ g_list_store_sort (self->internal_model, compare_data_func, self);
+}
+
+NautilusViewModel *
+nautilus_view_model_new (void)
+{
+ return g_object_new (NAUTILUS_TYPE_VIEW_MODEL, NULL);
+}
+
+GtkSorter *
+nautilus_view_model_get_sorter (NautilusViewModel *self)
+{
+ return self->sorter;
+}
+
+void
+nautilus_view_model_set_sorter (NautilusViewModel *self,
+ GtkSorter *sorter)
+{
+ if (self->sorter != NULL)
+ {
+ g_clear_signal_handler (&self->sorter_changed_id, self->sorter);
+ }
+
+ if (g_set_object (&self->sorter, sorter))
+ {
+ g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_SORTER]);
+ }
+
+ if (self->sorter != NULL)
+ {
+ self->sorter_changed_id = g_signal_connect (self->sorter, "changed",
+ G_CALLBACK (on_sorter_changed), self);
+ g_list_store_sort (self->internal_model, compare_data_func, self);
+ }
+}
+
+GQueue *
+nautilus_view_model_get_items_from_files (NautilusViewModel *self,
+ GQueue *files)
+{
+ GList *l;
+ guint n_items;
+ GQueue *items;
+
+ n_items = g_list_model_get_n_items (G_LIST_MODEL (self->internal_model));
+ items = g_queue_new ();
+ for (l = g_queue_peek_head_link (files); l != NULL; l = l->next)
+ {
+ NautilusFile *file1;
+
+ file1 = NAUTILUS_FILE (l->data);
+ for (guint i = 0; i < n_items; i++)
+ {
+ g_autoptr (NautilusViewItem) item = NULL;
+ NautilusFile *file2;
+ g_autofree gchar *file1_uri = NULL;
+ g_autofree gchar *file2_uri = NULL;
+
+ item = g_list_model_get_item (G_LIST_MODEL (self->internal_model), i);
+ file2 = nautilus_view_item_get_file (item);
+ file1_uri = nautilus_file_get_uri (file1);
+ file2_uri = nautilus_file_get_uri (file2);
+ if (g_strcmp0 (file1_uri, file2_uri) == 0)
+ {
+ g_queue_push_tail (items, item);
+ break;
+ }
+ }
+ }
+
+ return items;
+}
+
+NautilusViewItem *
+nautilus_view_model_get_item_from_file (NautilusViewModel *self,
+ NautilusFile *file)
+{
+ return g_hash_table_lookup (self->map_files_to_model, file);
+}
+
+void
+nautilus_view_model_remove_item (NautilusViewModel *self,
+ NautilusViewItem *item)
+{
+ guint i;
+
+ if (g_list_store_find (self->internal_model, item, &i))
+ {
+ NautilusFile *file;
+
+ file = nautilus_view_item_get_file (item);
+ g_list_store_remove (self->internal_model, i);
+ g_hash_table_remove (self->map_files_to_model, file);
+ }
+}
+
+void
+nautilus_view_model_remove_all_items (NautilusViewModel *self)
+{
+ g_list_store_remove_all (self->internal_model);
+ g_hash_table_remove_all (self->map_files_to_model);
+}
+
+void
+nautilus_view_model_add_item (NautilusViewModel *self,
+ NautilusViewItem *item)
+{
+ g_hash_table_insert (self->map_files_to_model,
+ nautilus_view_item_get_file (item),
+ item);
+ g_list_store_insert_sorted (self->internal_model, item, compare_data_func, self);
+}
+
+void
+nautilus_view_model_add_items (NautilusViewModel *self,
+ GQueue *items)
+{
+ g_autofree gpointer *array = NULL;
+ GList *l;
+ int i = 0;
+
+ /* Sort items before adding them to the internal model. This ensures that
+ * the first sorted item is become the initial focus and scroll anchor. */
+ g_queue_sort (items, compare_data_func, self);
+
+ array = g_malloc_n (g_queue_get_length (items),
+ sizeof (NautilusViewItem *));
+
+ for (l = g_queue_peek_head_link (items); l != NULL; l = l->next)
+ {
+ array[i] = l->data;
+ g_hash_table_insert (self->map_files_to_model,
+ nautilus_view_item_get_file (l->data),
+ l->data);
+ i++;
+ }
+
+ g_list_store_splice (self->internal_model,
+ g_list_model_get_n_items (G_LIST_MODEL (self->internal_model)),
+ 0, array, g_queue_get_length (items));
+
+ g_list_store_sort (self->internal_model, compare_data_func, self);
+}
+
+guint
+nautilus_view_model_get_index (NautilusViewModel *self,
+ NautilusViewItem *item)
+{
+ guint i = G_MAXUINT;
+ gboolean found;
+
+ found = g_list_store_find (self->internal_model, item, &i);
+ g_warn_if_fail (found);
+
+ return i;
+}
diff --git a/src/nautilus-view-model.h b/src/nautilus-view-model.h
new file mode 100644
index 0000000..839c6d2
--- /dev/null
+++ b/src/nautilus-view-model.h
@@ -0,0 +1,34 @@
+#pragma once
+
+#include <glib.h>
+#include "nautilus-file.h"
+#include "nautilus-view-item.h"
+
+G_BEGIN_DECLS
+
+#define NAUTILUS_TYPE_VIEW_MODEL (nautilus_view_model_get_type())
+
+G_DECLARE_FINAL_TYPE (NautilusViewModel, nautilus_view_model, NAUTILUS, VIEW_MODEL, GObject)
+
+NautilusViewModel * nautilus_view_model_new (void);
+
+GtkSorter *nautilus_view_model_get_sorter (NautilusViewModel *self);
+void nautilus_view_model_set_sorter (NautilusViewModel *self,
+ GtkSorter *sorter);
+NautilusViewItem * nautilus_view_model_get_item_from_file (NautilusViewModel *self,
+ NautilusFile *file);
+GQueue * nautilus_view_model_get_items_from_files (NautilusViewModel *self,
+ GQueue *files);
+/* Don't use inside a loop, use nautilus_view_model_remove_all_items instead. */
+void nautilus_view_model_remove_item (NautilusViewModel *self,
+ NautilusViewItem *item);
+void nautilus_view_model_remove_all_items (NautilusViewModel *self);
+/* Don't use inside a loop, use nautilus_view_model_add_items instead. */
+void nautilus_view_model_add_item (NautilusViewModel *self,
+ NautilusViewItem *item);
+void nautilus_view_model_add_items (NautilusViewModel *self,
+ GQueue *items);
+guint nautilus_view_model_get_index (NautilusViewModel *self,
+ NautilusViewItem *item);
+
+G_END_DECLS
diff --git a/src/nautilus-view.c b/src/nautilus-view.c
new file mode 100644
index 0000000..5ee424c
--- /dev/null
+++ b/src/nautilus-view.c
@@ -0,0 +1,369 @@
+/* nautilus-view.c
+ *
+ * Copyright (C) 2015 Georges Basile Stavracas Neto <georges.stavracas@gmail.com>
+ *
+ * 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 3 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/>.
+ */
+
+#include "config.h"
+
+#include "nautilus-view.h"
+#include <glib/gi18n.h>
+
+G_DEFINE_INTERFACE (NautilusView, nautilus_view, GTK_TYPE_WIDGET)
+
+static void
+nautilus_view_default_init (NautilusViewInterface *iface)
+{
+ /**
+ * NautilusView::loading:
+ *
+ * %TRUE if the view is loading the location, %FALSE otherwise.
+ */
+ g_object_interface_install_property (iface,
+ g_param_spec_boolean ("loading",
+ "Current view is loading",
+ "Whether the current view is loading the location or not",
+ FALSE,
+ G_PARAM_READABLE));
+
+ /**
+ * NautilusView::searching:
+ *
+ * %TRUE if the view is searching, %FALSE otherwise.
+ */
+ g_object_interface_install_property (iface,
+ g_param_spec_boolean ("searching",
+ "Current view is searching",
+ "Whether the current view is searching or not",
+ FALSE,
+ G_PARAM_READABLE));
+
+ /**
+ * NautilusView::location:
+ *
+ * The current location of the view.
+ */
+ g_object_interface_install_property (iface,
+ g_param_spec_object ("location",
+ "Location displayed by the view",
+ "The current location displayed by the view",
+ G_TYPE_FILE,
+ G_PARAM_READWRITE));
+
+ /**
+ * NautilusView::selection:
+ *
+ * The current selection of the view.
+ */
+ g_object_interface_install_property (iface,
+ g_param_spec_pointer ("selection",
+ "Selection of the view",
+ "The current selection of the view",
+ G_PARAM_READWRITE));
+
+ /**
+ * NautilusView::search-query:
+ *
+ * The search query being performed, or NULL.
+ */
+ g_object_interface_install_property (iface,
+ g_param_spec_object ("search-query",
+ "Search query being performed",
+ "The search query being performed on the view",
+ NAUTILUS_TYPE_QUERY,
+ G_PARAM_READWRITE));
+
+ /**
+ * NautilusView::extensions-background-menu:
+ *
+ * Menu for the background click of extensions
+ */
+ g_object_interface_install_property (iface,
+ 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));
+ /**
+ * NautilusView::templates-menu:
+ *
+ * Menu of templates
+ */
+ g_object_interface_install_property (iface,
+ g_param_spec_object ("templates-menu",
+ "Menu of templates",
+ "Menu of templates",
+ G_TYPE_MENU_MODEL,
+ G_PARAM_READWRITE));
+}
+
+/**
+ * nautilus_view_get_icon_name:
+ * @view: a #NautilusView
+ *
+ * Retrieves the icon name that represents @view.
+ *
+ * Returns: (transfer none): an icon name
+ */
+const gchar *
+nautilus_view_get_icon_name (guint view_id)
+{
+ if (view_id == NAUTILUS_VIEW_GRID_ID)
+ {
+ return "view-grid-symbolic";
+ }
+ else if (view_id == NAUTILUS_VIEW_LIST_ID || view_id == NAUTILUS_VIEW_OTHER_LOCATIONS_ID)
+ {
+ return "view-list-symbolic";
+ }
+ else
+ {
+ return NULL;
+ }
+}
+
+/**
+ * nautilus_view_get_tooltip:
+ * @view: a #NautilusView
+ *
+ * Retrieves the static string that represents @view.
+ *
+ * Returns: (transfer none): a static string
+ */
+const gchar *
+nautilus_view_get_tooltip (guint view_id)
+{
+ if (view_id == NAUTILUS_VIEW_GRID_ID)
+ {
+ return _("Grid View");
+ }
+ else if (view_id == NAUTILUS_VIEW_LIST_ID)
+ {
+ return _("List View");
+ }
+ else if (view_id == NAUTILUS_VIEW_OTHER_LOCATIONS_ID)
+ {
+ return _("List View");
+ }
+ else
+ {
+ return NULL;
+ }
+}
+
+/**
+ * nautilus_view_get_view_id:
+ * @view: a #NautilusView
+ *
+ * Retrieves the view id that represents the @view type.
+ *
+ * Returns: a guint representing the view type
+ */
+guint
+nautilus_view_get_view_id (NautilusView *view)
+{
+ g_return_val_if_fail (NAUTILUS_VIEW_GET_IFACE (view)->get_view_id, NAUTILUS_VIEW_INVALID_ID);
+
+ return NAUTILUS_VIEW_GET_IFACE (view)->get_view_id (view);
+}
+
+/**
+ * nautilus_view_get_toolbar_menu_sections:
+ * @view: a #NautilusView
+ *
+ * Retrieves the menu sections to show in the main toolbar menu when this view
+ * is active
+ *
+ * Returns: (transfer none): a #NautilusToolbarMenuSections with the sections to
+ * be displayed
+ */
+NautilusToolbarMenuSections *
+nautilus_view_get_toolbar_menu_sections (NautilusView *view)
+{
+ g_return_val_if_fail (NAUTILUS_VIEW_GET_IFACE (view)->get_toolbar_menu_sections, NULL);
+
+ return NAUTILUS_VIEW_GET_IFACE (view)->get_toolbar_menu_sections (view);
+}
+
+GMenuModel *
+nautilus_view_get_extensions_background_menu (NautilusView *view)
+{
+ g_return_val_if_fail (NAUTILUS_VIEW_GET_IFACE (view)->get_extensions_background_menu, NULL);
+
+ return NAUTILUS_VIEW_GET_IFACE (view)->get_extensions_background_menu (view);
+}
+
+/* Protected */
+void
+nautilus_view_set_extensions_background_menu (NautilusView *view,
+ GMenuModel *menu)
+{
+ g_return_if_fail (NAUTILUS_VIEW_GET_IFACE (view)->set_extensions_background_menu);
+
+ NAUTILUS_VIEW_GET_IFACE (view)->set_extensions_background_menu (view, menu);
+}
+
+GMenuModel *
+nautilus_view_get_templates_menu (NautilusView *view)
+{
+ g_return_val_if_fail (NAUTILUS_VIEW_GET_IFACE (view)->get_templates_menu, NULL);
+
+ return NAUTILUS_VIEW_GET_IFACE (view)->get_templates_menu (view);
+}
+
+/* Protected */
+void
+nautilus_view_set_templates_menu (NautilusView *view,
+ GMenuModel *menu)
+{
+ g_return_if_fail (NAUTILUS_VIEW_GET_IFACE (view)->set_templates_menu);
+
+ NAUTILUS_VIEW_GET_IFACE (view)->set_templates_menu (view, menu);
+}
+
+/**
+ * nautilus_view_get_search_query:
+ * @view: a #NautilusView
+ *
+ * Retrieves the current current location of @view.
+ *
+ * Returns: (transfer none): a #GFile
+ */
+GFile *
+nautilus_view_get_location (NautilusView *view)
+{
+ g_return_val_if_fail (NAUTILUS_VIEW_GET_IFACE (view)->get_location, NULL);
+
+ return NAUTILUS_VIEW_GET_IFACE (view)->get_location (view);
+}
+
+/**
+ * nautilus_view_set_location:
+ * @view: a #NautilusView
+ * @location: the location displayed by @view
+ *
+ * Sets the location of @view.
+ *
+ * Returns:
+ */
+void
+nautilus_view_set_location (NautilusView *view,
+ GFile *location)
+{
+ g_return_if_fail (NAUTILUS_VIEW_GET_IFACE (view)->set_location);
+
+ NAUTILUS_VIEW_GET_IFACE (view)->set_location (view, location);
+}
+
+/**
+ * nautilus_view_get_selection:
+ * @view: a #NautilusView
+ *
+ * Get the current selection of the view.
+ *
+ * Returns: (transfer full) (type GFile): a newly allocated list
+ * of the currently selected files.
+ */
+GList *
+nautilus_view_get_selection (NautilusView *view)
+{
+ g_return_val_if_fail (NAUTILUS_VIEW_GET_IFACE (view)->get_selection, NULL);
+
+ return NAUTILUS_VIEW_GET_IFACE (view)->get_selection (view);
+}
+
+/**
+ * nautilus_view_set_selection:
+ * @view: a #NautilusView
+ * @selection: (nullable): a list of files
+ *
+ * Sets the current selection of the view.
+ *
+ * Returns:
+ */
+void
+nautilus_view_set_selection (NautilusView *view,
+ GList *selection)
+{
+ g_return_if_fail (NAUTILUS_VIEW_GET_IFACE (view)->set_selection);
+
+ NAUTILUS_VIEW_GET_IFACE (view)->set_selection (view, selection);
+}
+
+/**
+ * nautilus_view_get_search_query:
+ * @view: a #NautilusView
+ *
+ * Retrieves the current search query displayed by @view.
+ *
+ * Returns: (transfer none): a #
+ */
+NautilusQuery *
+nautilus_view_get_search_query (NautilusView *view)
+{
+ g_return_val_if_fail (NAUTILUS_VIEW_GET_IFACE (view)->get_search_query, NULL);
+
+ return NAUTILUS_VIEW_GET_IFACE (view)->get_search_query (view);
+}
+
+/**
+ * nautilus_view_set_search_query:
+ * @view: a #NautilusView
+ * @query: the search query to be performed, or %NULL
+ *
+ * Sets the current search query performed by @view.
+ *
+ * Returns:
+ */
+void
+nautilus_view_set_search_query (NautilusView *view,
+ NautilusQuery *query)
+{
+ g_return_if_fail (NAUTILUS_VIEW_GET_IFACE (view)->set_search_query);
+
+ NAUTILUS_VIEW_GET_IFACE (view)->set_search_query (view, query);
+}
+
+/**
+ * nautilus_view_is_loading:
+ * @view: a #NautilusView
+ *
+ * Whether @view is loading the current location.
+ *
+ * Returns: %TRUE if @view is loading, %FALSE otherwise.
+ */
+gboolean
+nautilus_view_is_loading (NautilusView *view)
+{
+ g_return_val_if_fail (NAUTILUS_VIEW_GET_IFACE (view)->is_loading, FALSE);
+
+ return NAUTILUS_VIEW_GET_IFACE (view)->is_loading (view);
+}
+
+/**
+ * nautilus_view_is_searching:
+ * @view: a #NautilusView
+ *
+ * Whether @view is searching.
+ *
+ * Returns: %TRUE if @view is searching, %FALSE otherwise.
+ */
+gboolean
+nautilus_view_is_searching (NautilusView *view)
+{
+ g_return_val_if_fail (NAUTILUS_VIEW_GET_IFACE (view)->is_searching, FALSE);
+
+ return NAUTILUS_VIEW_GET_IFACE (view)->is_searching (view);
+}
diff --git a/src/nautilus-view.h b/src/nautilus-view.h
new file mode 100644
index 0000000..9a8911e
--- /dev/null
+++ b/src/nautilus-view.h
@@ -0,0 +1,121 @@
+/* nautilus-view.h
+ *
+ * Copyright (C) 2015 Georges Basile Stavracas Neto <georges.stavracas@gmail.com>
+ *
+ * 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 3 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/>.
+ */
+
+#pragma once
+
+#include <glib.h>
+#include <gtk/gtk.h>
+
+#include "nautilus-query.h"
+#include "nautilus-toolbar-menu-sections.h"
+
+/* Keep values in sync with the org.gnome.nautilus.FolderView schema enums: */
+#define NAUTILUS_VIEW_LIST_ID 1
+#define NAUTILUS_VIEW_GRID_ID 2
+/* Special ids, not used by GSettings schemas: */
+#define NAUTILUS_VIEW_INVALID_ID 0
+#define NAUTILUS_VIEW_OTHER_LOCATIONS_ID 3
+
+G_BEGIN_DECLS
+
+#define NAUTILUS_TYPE_VIEW (nautilus_view_get_type ())
+
+G_DECLARE_INTERFACE (NautilusView, nautilus_view, NAUTILUS, VIEW, GtkWidget)
+
+struct _NautilusViewInterface
+{
+ GTypeInterface parent;
+
+ guint (*get_view_id) (NautilusView *view);
+ /*
+ * Returns the menu sections that should be shown in the toolbar menu
+ * when this view is active. Implementations must not return %NULL
+ */
+ NautilusToolbarMenuSections * (*get_toolbar_menu_sections) (NautilusView *view);
+
+ /*
+ * Returns the menu for the background click of extensions.
+ */
+ GMenuModel * (*get_extensions_background_menu) (NautilusView *view);
+
+ void (*set_extensions_background_menu) (NautilusView *view,
+ GMenuModel *menu);
+ /*
+ * Returns the menu for templates.
+ */
+ GMenuModel * (*get_templates_menu) (NautilusView *view);
+
+ void (*set_templates_menu) (NautilusView *view,
+ GMenuModel *menu);
+ /* Current location of the view */
+ GFile* (*get_location) (NautilusView *view);
+ void (*set_location) (NautilusView *view,
+ GFile *location);
+
+ /* Selection */
+ GList* (*get_selection) (NautilusView *view);
+ void (*set_selection) (NautilusView *view,
+ GList *selection);
+
+ /* Search */
+ NautilusQuery* (*get_search_query) (NautilusView *view);
+ void (*set_search_query) (NautilusView *view,
+ NautilusQuery *query);
+
+ /* Whether the current view is loading the location */
+ gboolean (*is_loading) (NautilusView *view);
+
+ /* Whether the current view is searching or not */
+ gboolean (*is_searching) (NautilusView *view);
+};
+
+const gchar * nautilus_view_get_icon_name (guint view_id);
+
+const gchar * nautilus_view_get_tooltip (guint view_id);
+
+guint nautilus_view_get_view_id (NautilusView *view);
+
+NautilusToolbarMenuSections * nautilus_view_get_toolbar_menu_sections (NautilusView *view);
+
+GFile * nautilus_view_get_location (NautilusView *view);
+
+void nautilus_view_set_location (NautilusView *view,
+ GFile *location);
+
+GList * nautilus_view_get_selection (NautilusView *view);
+
+void nautilus_view_set_selection (NautilusView *view,
+ GList *selection);
+
+NautilusQuery * nautilus_view_get_search_query (NautilusView *view);
+
+void nautilus_view_set_search_query (NautilusView *view,
+ NautilusQuery *query);
+
+gboolean nautilus_view_is_loading (NautilusView *view);
+
+gboolean nautilus_view_is_searching (NautilusView *view);
+
+void nautilus_view_set_templates_menu (NautilusView *view,
+ GMenuModel *menu);
+GMenuModel * nautilus_view_get_templates_menu (NautilusView *view);
+void nautilus_view_set_extensions_background_menu (NautilusView *view,
+ GMenuModel *menu);
+GMenuModel * nautilus_view_get_extensions_background_menu (NautilusView *view);
+
+G_END_DECLS
diff --git a/src/nautilus-window-slot-dnd.c b/src/nautilus-window-slot-dnd.c
new file mode 100644
index 0000000..b05af1a
--- /dev/null
+++ b/src/nautilus-window-slot-dnd.c
@@ -0,0 +1,346 @@
+/*
+ * nautilus-window-slot-dnd.c - Handle DnD for widgets acting as
+ * NautilusWindowSlot proxies
+ *
+ * Copyright (C) 2000, 2001 Eazel, Inc.
+ * Copyright (C) 2010, Red Hat, Inc.
+ *
+ * The Gnome Library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * The Gnome Library 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
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with the Gnome Library; see the file COPYING.LIB. If not,
+ * see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Pavel Cisler <pavel@eazel.com>,
+ * Ettore Perazzoli <ettore@gnu.org>
+ */
+
+#include <config.h>
+
+#include "nautilus-application.h"
+#include "nautilus-files-view-dnd.h"
+#include "nautilus-window-slot-dnd.h"
+
+#ifdef GDK_WINDOWING_X11
+#include <gdk/x11/gdkx.h>
+#endif
+
+typedef struct
+{
+ NautilusFile *target_file;
+ NautilusWindowSlot *target_slot;
+ GtkWidget *widget;
+
+ graphene_point_t hover_start_point;
+ guint switch_location_timer;
+} NautilusDragSlotProxyInfo;
+
+static void
+switch_location (NautilusDragSlotProxyInfo *drag_info)
+{
+ GFile *location;
+ GtkRoot *window;
+
+ if (drag_info->target_file == NULL)
+ {
+ return;
+ }
+
+ window = gtk_widget_get_root (drag_info->widget);
+ g_assert (NAUTILUS_IS_WINDOW (window));
+
+ location = nautilus_file_get_location (drag_info->target_file);
+ nautilus_application_open_location_full (NAUTILUS_APPLICATION (g_application_get_default ()),
+ location, NAUTILUS_OPEN_FLAG_DONT_MAKE_ACTIVE,
+ NULL, NAUTILUS_WINDOW (window), NULL);
+ g_object_unref (location);
+}
+
+static gboolean
+slot_proxy_switch_location_timer (gpointer user_data)
+{
+ NautilusDragSlotProxyInfo *drag_info = user_data;
+
+ drag_info->switch_location_timer = 0;
+
+ switch_location (drag_info);
+
+ return FALSE;
+}
+
+static void
+slot_proxy_check_switch_location_timer (NautilusDragSlotProxyInfo *drag_info)
+{
+ if (drag_info->switch_location_timer)
+ {
+ return;
+ }
+
+ drag_info->switch_location_timer = g_timeout_add (HOVER_TIMEOUT,
+ slot_proxy_switch_location_timer,
+ drag_info);
+}
+
+static void
+slot_proxy_remove_switch_location_timer (NautilusDragSlotProxyInfo *drag_info)
+{
+ if (drag_info->switch_location_timer != 0)
+ {
+ g_source_remove (drag_info->switch_location_timer);
+ drag_info->switch_location_timer = 0;
+ }
+}
+
+static GdkDragAction
+slot_proxy_drag_motion (GtkDropTarget *target,
+ gdouble x,
+ gdouble y,
+ gpointer user_data)
+{
+ NautilusDragSlotProxyInfo *drag_info;
+ NautilusWindowSlot *target_slot;
+ GtkRoot *window;
+ GdkDragAction action;
+ char *target_uri;
+ GFile *location;
+ const GValue *value;
+ graphene_point_t start;
+
+ drag_info = user_data;
+
+ action = 0;
+
+ value = gtk_drop_target_get_value (target);
+ if (value == NULL)
+ {
+ return 0;
+ }
+
+ window = gtk_widget_get_root (drag_info->widget);
+ g_assert (NAUTILUS_IS_WINDOW (window));
+
+ target_uri = NULL;
+ if (drag_info->target_file != NULL)
+ {
+ target_uri = nautilus_file_get_uri (drag_info->target_file);
+ }
+ else
+ {
+ if (drag_info->target_slot != NULL)
+ {
+ target_slot = drag_info->target_slot;
+ }
+ else
+ {
+ target_slot = nautilus_window_get_active_slot (NAUTILUS_WINDOW (window));
+ }
+
+ if (target_slot != NULL)
+ {
+ location = nautilus_window_slot_get_location (target_slot);
+ target_uri = g_file_get_uri (location);
+ }
+ }
+
+ if (target_uri != NULL)
+ {
+ NautilusFile *file;
+ NautilusDirectory *directory;
+ gboolean can;
+ file = nautilus_file_get_existing_by_uri (target_uri);
+ directory = nautilus_directory_get_for_file (file);
+ can = nautilus_file_can_write (file) && nautilus_directory_is_editable (directory);
+ nautilus_directory_unref (directory);
+ g_object_unref (file);
+ if (!can)
+ {
+ action = 0;
+ goto out;
+ }
+
+ if (G_VALUE_HOLDS (value, GDK_TYPE_FILE_LIST))
+ {
+ GSList *items = g_value_get_boxed (value);
+ action = nautilus_dnd_get_preferred_action (file, items->data);
+ }
+ }
+
+ g_free (target_uri);
+
+out:
+ start = drag_info->hover_start_point;
+ if (gtk_drag_check_threshold (drag_info->widget, start.x, start.y, x, y))
+ {
+ slot_proxy_remove_switch_location_timer (drag_info);
+ slot_proxy_check_switch_location_timer (drag_info);
+ drag_info->hover_start_point.x = x;
+ drag_info->hover_start_point.y = y;
+ }
+
+ return action;
+}
+
+static void
+drag_info_free (gpointer user_data)
+{
+ NautilusDragSlotProxyInfo *drag_info = user_data;
+
+ g_clear_object (&drag_info->target_file);
+ g_clear_object (&drag_info->target_slot);
+
+ g_slice_free (NautilusDragSlotProxyInfo, drag_info);
+}
+
+static void
+drag_info_clear (NautilusDragSlotProxyInfo *drag_info)
+{
+ slot_proxy_remove_switch_location_timer (drag_info);
+}
+
+static void
+slot_proxy_drag_leave (GtkDropTarget *target,
+ gpointer user_data)
+{
+ NautilusDragSlotProxyInfo *drag_info;
+
+ drag_info = user_data;
+
+ drag_info_clear (drag_info);
+}
+
+static void
+slot_proxy_handle_drop (GtkDropTarget *target,
+ const GValue *value,
+ gdouble x,
+ gdouble y,
+ gpointer user_data)
+{
+ GtkRoot *window;
+ NautilusWindowSlot *target_slot;
+ NautilusFilesView *target_view;
+ char *target_uri;
+ GList *uri_list = NULL;
+ GFile *location;
+ NautilusDragSlotProxyInfo *drag_info;
+
+ drag_info = user_data;
+
+ window = gtk_widget_get_root (drag_info->widget);
+ g_assert (NAUTILUS_IS_WINDOW (window));
+
+ if (drag_info->target_slot != NULL)
+ {
+ target_slot = drag_info->target_slot;
+ }
+ else
+ {
+ target_slot = nautilus_window_get_active_slot (NAUTILUS_WINDOW (window));
+ }
+
+ target_uri = NULL;
+ if (drag_info->target_file != NULL)
+ {
+ target_uri = nautilus_file_get_uri (drag_info->target_file);
+ }
+ else if (target_slot != NULL)
+ {
+ location = nautilus_window_slot_get_location (target_slot);
+ target_uri = g_file_get_uri (location);
+ }
+
+ target_view = NULL;
+ if (target_slot != NULL)
+ {
+ NautilusView *view;
+
+ view = nautilus_window_slot_get_current_view (target_slot);
+
+ if (view && NAUTILUS_IS_FILES_VIEW (view))
+ {
+ target_view = NAUTILUS_FILES_VIEW (view);
+ }
+ }
+
+ if (target_slot != NULL && target_view != NULL)
+ {
+ if (G_VALUE_HOLDS (value, GDK_TYPE_FILE_LIST))
+ {
+ GSList *items = g_value_get_boxed (value);
+ GdkDragAction actions;
+
+ for (GSList *l = items; l != NULL; l = l->next)
+ {
+ uri_list = g_list_prepend (uri_list, g_file_get_uri (l->data));
+ }
+
+ actions = gdk_drop_get_actions (gtk_drop_target_get_current_drop (target));
+
+ #ifdef GDK_WINDOWING_X11
+ if (GDK_IS_X11_DISPLAY (gtk_widget_get_display (GTK_WIDGET (window))))
+ {
+ /* Temporary workaround until the below GTK MR (or equivalend fix)
+ * is merged. Without this fix, the preferred action isn't set correctly.
+ * https://gitlab.gnome.org/GNOME/gtk/-/merge_requests/4982 */
+ GdkDrag *drag = gdk_drop_get_drag (gtk_drop_target_get_current_drop (target));
+ actions = gdk_drag_get_selected_action (drag);
+ }
+ #endif
+
+ nautilus_files_view_drop_proxy_received_uris (target_view,
+ uri_list,
+ target_uri,
+ actions);
+ g_list_free_full (uri_list, g_free);
+ }
+ }
+ g_free (target_uri);
+
+ drag_info_clear (drag_info);
+}
+
+void
+nautilus_drag_slot_proxy_init (GtkWidget *widget,
+ NautilusFile *target_file,
+ NautilusWindowSlot *target_slot)
+{
+ NautilusDragSlotProxyInfo *drag_info;
+ GtkDropTarget *target;
+
+ g_assert (GTK_IS_WIDGET (widget));
+
+ drag_info = g_slice_new0 (NautilusDragSlotProxyInfo);
+
+ g_object_set_data_full (G_OBJECT (widget), "drag-slot-proxy-data", drag_info,
+ drag_info_free);
+
+ if (target_file != NULL)
+ {
+ drag_info->target_file = nautilus_file_ref (target_file);
+ }
+
+ if (target_slot != NULL)
+ {
+ drag_info->target_slot = g_object_ref (target_slot);
+ }
+
+ drag_info->widget = widget;
+ /* TODO: Implement GDK_ACTION_ASK */
+ target = gtk_drop_target_new (G_TYPE_INVALID, GDK_ACTION_ALL);
+
+ gtk_drop_target_set_preload (target, TRUE);
+ /* TODO: Implement GDK_TYPE_STRING */
+ gtk_drop_target_set_gtypes (target, (GType[1]) { GDK_TYPE_FILE_LIST }, 1);
+ g_signal_connect (target, "enter", G_CALLBACK (slot_proxy_drag_motion), drag_info);
+ g_signal_connect (target, "motion", G_CALLBACK (slot_proxy_drag_motion), drag_info);
+ g_signal_connect (target, "drop", G_CALLBACK (slot_proxy_handle_drop), drag_info);
+ g_signal_connect (target, "leave", G_CALLBACK (slot_proxy_drag_leave), drag_info);
+ gtk_widget_add_controller (widget, GTK_EVENT_CONTROLLER (target));
+}
diff --git a/src/nautilus-window-slot-dnd.h b/src/nautilus-window-slot-dnd.h
new file mode 100644
index 0000000..bd77969
--- /dev/null
+++ b/src/nautilus-window-slot-dnd.h
@@ -0,0 +1,37 @@
+/*
+ * nautilus-window-slot-dnd.c - Handle DnD for widgets acting as
+ * NautilusWindowSlot proxies
+ *
+ * Copyright (C) 2000, 2001 Eazel, Inc.
+ * Copyright (C) 2010, Red Hat, Inc.
+ *
+ * The Gnome Library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * The Gnome Library 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
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with the Gnome Library; see the file COPYING.LIB. If not,
+ * see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Pavel Cisler <pavel@eazel.com>,
+ * Ettore Perazzoli <ettore@gnu.org>
+ */
+
+#pragma once
+
+#include <gio/gio.h>
+#include <gtk/gtk.h>
+
+#include "nautilus-dnd.h"
+
+#include "nautilus-window-slot.h"
+
+void nautilus_drag_slot_proxy_init (GtkWidget *widget,
+ NautilusFile *target_file,
+ NautilusWindowSlot *target_slot);
diff --git a/src/nautilus-window-slot.c b/src/nautilus-window-slot.c
new file mode 100644
index 0000000..64dc5fc
--- /dev/null
+++ b/src/nautilus-window-slot.c
@@ -0,0 +1,3417 @@
+/*
+ * nautilus-window-slot.c: Nautilus window slot
+ *
+ * Copyright (C) 2008 Free Software Foundation, 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/>.
+ *
+ * Author: Christian Neumair <cneumair@gnome.org>
+ */
+
+#include "config.h"
+
+#include "nautilus-window-slot.h"
+
+#include "nautilus-application.h"
+#include "nautilus-bookmark.h"
+#include "nautilus-bookmark-list.h"
+#include "nautilus-files-view.h"
+#include "nautilus-mime-actions.h"
+#include "nautilus-places-view.h"
+#include "nautilus-query-editor.h"
+#include "nautilus-special-location-bar.h"
+#include "nautilus-toolbar.h"
+#include "nautilus-view.h"
+#include "nautilus-window.h"
+#include "nautilus-x-content-bar.h"
+
+#include <glib/gi18n.h>
+
+#include "nautilus-file.h"
+#include "nautilus-file-utilities.h"
+#include "nautilus-global-preferences.h"
+#include "nautilus-module.h"
+#include "nautilus-monitor.h"
+#include "nautilus-profile.h"
+#include "nautilus-ui-utilities.h"
+#include <eel/eel-vfs-extensions.h>
+
+enum
+{
+ PROP_ACTIVE = 1,
+ PROP_WINDOW,
+ PROP_ICON_NAME,
+ PROP_TOOLBAR_MENU_SECTIONS,
+ PROP_EXTENSIONS_BACKGROUND_MENU,
+ PROP_TEMPLATES_MENU,
+ PROP_LOADING,
+ PROP_SEARCHING,
+ PROP_SELECTION,
+ PROP_LOCATION,
+ PROP_TOOLTIP,
+ PROP_ALLOW_STOP,
+ PROP_TITLE,
+ NUM_PROPERTIES
+};
+
+#define FILE_SHARING_SCHEMA_ID "org.gnome.desktop.file-sharing"
+
+struct _NautilusWindowSlot
+{
+ GtkBox parent_instance;
+
+ NautilusWindow *window;
+
+ gboolean active : 1;
+ guint loading : 1;
+
+ /* slot contains
+ * 1) an vbox containing extra_location_widgets
+ * 2) the view
+ */
+ GtkWidget *extra_location_widgets;
+
+ /* Slot actions */
+ GActionGroup *slot_action_group;
+
+ /* Current location. */
+ GFile *location;
+ gchar *title;
+
+ /* Viewed file */
+ NautilusView *content_view;
+ NautilusView *new_content_view;
+ NautilusFile *viewed_file;
+ gboolean viewed_file_seen;
+ gboolean viewed_file_in_trash;
+
+ /* Information about bookmarks and history list */
+ NautilusBookmark *current_location_bookmark;
+ NautilusBookmark *last_location_bookmark;
+ GList *back_list;
+ GList *forward_list;
+
+ /* Query editor */
+ NautilusQueryEditor *query_editor;
+ NautilusQuery *pending_search_query;
+ gulong qe_changed_id;
+ gulong qe_cancel_id;
+ gulong qe_activated_id;
+ gulong qe_focus_view_id;
+
+ GtkLabel *search_info_label;
+ GtkRevealer *search_info_label_revealer;
+
+ /* Load state */
+ GCancellable *find_mount_cancellable;
+ /* It could be either the view is loading the files or the search didn't
+ * finish. Used for showing a spinner to provide feedback to the user. */
+ gboolean allow_stop;
+ gboolean needs_reload;
+
+ /* New location. */
+ GFile *pending_location;
+ NautilusLocationChangeType location_change_type;
+ guint location_change_distance;
+ char *pending_scroll_to;
+ GList *pending_selection;
+ NautilusFile *pending_file_to_activate;
+ NautilusFile *determine_view_file;
+ GCancellable *mount_cancellable;
+ GError *mount_error;
+ gboolean tried_mount;
+ gint view_mode_before_search;
+ gint view_mode_before_places;
+
+ /* Menus */
+ GMenuModel *extensions_background_menu;
+ GMenuModel *templates_menu;
+
+ /* View bindings */
+ GBinding *searching_binding;
+ GBinding *selection_binding;
+ GBinding *extensions_background_menu_binding;
+ GBinding *templates_menu_binding;
+ gboolean searching;
+ GList *selection;
+};
+
+G_DEFINE_TYPE (NautilusWindowSlot, nautilus_window_slot, GTK_TYPE_BOX);
+
+static GParamSpec *properties[NUM_PROPERTIES] = { NULL, };
+
+static void nautilus_window_slot_force_reload (NautilusWindowSlot *self);
+static void change_view (NautilusWindowSlot *self);
+static void hide_query_editor (NautilusWindowSlot *self);
+static void nautilus_window_slot_sync_actions (NautilusWindowSlot *self);
+static void nautilus_window_slot_connect_new_content_view (NautilusWindowSlot *self);
+static void nautilus_window_slot_disconnect_content_view (NautilusWindowSlot *self);
+static gboolean nautilus_window_slot_content_view_matches (NautilusWindowSlot *self,
+ guint id);
+static NautilusView *nautilus_window_slot_get_view_for_location (NautilusWindowSlot *self,
+ GFile *location);
+static void nautilus_window_slot_set_content_view (NautilusWindowSlot *self,
+ guint id);
+static void nautilus_window_slot_set_loading (NautilusWindowSlot *self,
+ gboolean loading);
+char *nautilus_window_slot_get_location_uri (NautilusWindowSlot *self);
+static void nautilus_window_slot_set_search_visible (NautilusWindowSlot *self,
+ gboolean visible);
+static gboolean nautilus_window_slot_get_search_visible (NautilusWindowSlot *self);
+static void nautilus_window_slot_set_location (NautilusWindowSlot *self,
+ GFile *location);
+static void update_search_information (NautilusWindowSlot *self);
+static void real_set_extensions_background_menu (NautilusWindowSlot *self,
+ GMenuModel *menu);
+static GMenuModel *real_get_extensions_background_menu (NautilusWindowSlot *self);
+static void real_set_templates_menu (NautilusWindowSlot *self,
+ GMenuModel *menu);
+static GMenuModel *real_get_templates_menu (NautilusWindowSlot *self);
+static void nautilus_window_slot_setup_extra_location_widgets (NautilusWindowSlot *self);
+
+void
+free_navigation_state (gpointer data)
+{
+ NautilusNavigationState *navigation_state = data;
+
+ g_list_free_full (navigation_state->back_list, g_object_unref);
+ g_list_free_full (navigation_state->forward_list, g_object_unref);
+ nautilus_file_unref (navigation_state->file);
+ g_clear_object (&navigation_state->current_location_bookmark);
+
+ g_free (navigation_state);
+}
+
+void
+nautilus_window_slot_restore_navigation_state (NautilusWindowSlot *self,
+ NautilusNavigationState *data)
+{
+ self->back_list = g_steal_pointer (&data->back_list);
+
+ self->forward_list = g_steal_pointer (&data->forward_list);
+
+ self->view_mode_before_search = data->view_before_search;
+
+ g_set_object (&self->current_location_bookmark, data->current_location_bookmark);
+
+ self->location_change_type = NAUTILUS_LOCATION_CHANGE_RELOAD;
+}
+
+NautilusNavigationState *
+nautilus_window_slot_get_navigation_state (NautilusWindowSlot *self)
+{
+ NautilusNavigationState *data;
+ GList *back_list;
+ GList *forward_list;
+
+ if (self->location == NULL)
+ {
+ return NULL;
+ }
+
+ back_list = g_list_copy_deep (self->back_list,
+ (GCopyFunc) g_object_ref,
+ NULL);
+ forward_list = g_list_copy_deep (self->forward_list,
+ (GCopyFunc) g_object_ref,
+ NULL);
+
+ /* This data is used to restore a tab after it was closed.
+ * In order to do that we need to keep the history, what was
+ * the view mode before search and a reference to the file.
+ * A GFile isn't enough, as the NautilusFile also keeps a
+ * reference to the search directory */
+ data = g_new0 (NautilusNavigationState, 1);
+ data->back_list = back_list;
+ data->forward_list = forward_list;
+ data->file = nautilus_file_get (self->location);
+ data->view_before_search = self->view_mode_before_search;
+ g_set_object (&data->current_location_bookmark, self->current_location_bookmark);
+
+ return data;
+}
+
+static NautilusView *
+nautilus_window_slot_get_view_for_location (NautilusWindowSlot *self,
+ GFile *location)
+{
+ g_autoptr (NautilusFile) file = NULL;
+ NautilusView *view;
+ guint view_id;
+
+ file = nautilus_file_get (location);
+ view = NULL;
+ view_id = NAUTILUS_VIEW_INVALID_ID;
+
+ if (nautilus_file_is_other_locations (file))
+ {
+ view = NAUTILUS_VIEW (nautilus_places_view_new ());
+
+ /* Save the current view, so we can go back after places view */
+ if (NAUTILUS_IS_FILES_VIEW (self->content_view))
+ {
+ self->view_mode_before_places = nautilus_view_get_view_id (self->content_view);
+ }
+
+ return view;
+ }
+
+ /* If we are in search, try to use by default list view. */
+ if (nautilus_file_is_in_search (file))
+ {
+ /* If it's already set, is because we already made the change to search mode,
+ * so the view mode of the current view will be the one search is using,
+ * which is not the one we are interested in */
+ if (self->view_mode_before_search == NAUTILUS_VIEW_INVALID_ID &&
+ NAUTILUS_IS_FILES_VIEW (self->content_view))
+ {
+ self->view_mode_before_search = nautilus_view_get_view_id (self->content_view);
+ }
+ view_id = g_settings_get_enum (nautilus_preferences, NAUTILUS_PREFERENCES_SEARCH_VIEW);
+ }
+ else if (self->content_view != NULL)
+ {
+ /* If there is already a view, just use the view mode that it's currently using, or
+ * if we were on search before, use what we were using before entering
+ * search mode */
+ if (self->view_mode_before_search != NAUTILUS_VIEW_INVALID_ID)
+ {
+ view_id = self->view_mode_before_search;
+ self->view_mode_before_search = NAUTILUS_VIEW_INVALID_ID;
+ }
+ else if (NAUTILUS_IS_PLACES_VIEW (self->content_view))
+ {
+ view_id = self->view_mode_before_places;
+ self->view_mode_before_places = NAUTILUS_VIEW_INVALID_ID;
+ }
+ else
+ {
+ view_id = nautilus_view_get_view_id (self->content_view);
+ }
+ }
+
+ /* If there is not previous view in this slot, use the default view mode
+ * from preferences */
+ if (view_id == NAUTILUS_VIEW_INVALID_ID)
+ {
+ view_id = g_settings_get_enum (nautilus_preferences, NAUTILUS_PREFERENCES_DEFAULT_FOLDER_VIEWER);
+ }
+ if (view_id == NAUTILUS_VIEW_INVALID_ID)
+ {
+ g_warning ("Invalid value stored for 'default-folder-viewer' key for "
+ "the 'org.gnome.nautilus.preferences' schemas. Installed "
+ "schemas may be outdated. Falling back to 'list-view'.");
+ view_id = NAUTILUS_VIEW_LIST_ID;
+ }
+
+ /* Try to reuse the current view */
+ if (nautilus_window_slot_content_view_matches (self, view_id))
+ {
+ view = self->content_view;
+ }
+ else
+ {
+ view = NAUTILUS_VIEW (nautilus_files_view_new (view_id, self));
+ }
+
+ return view;
+}
+
+static gboolean
+nautilus_window_slot_content_view_matches (NautilusWindowSlot *self,
+ guint id)
+{
+ if (self->content_view == NULL)
+ {
+ return FALSE;
+ }
+
+ if (id != NAUTILUS_VIEW_INVALID_ID)
+ {
+ return nautilus_view_get_view_id (self->content_view) == id;
+ }
+ else
+ {
+ return FALSE;
+ }
+}
+
+static void
+update_search_visible (NautilusWindowSlot *self)
+{
+ NautilusQuery *query;
+ NautilusView *view;
+
+ view = nautilus_window_slot_get_current_view (self);
+ /* If we changed location just to another search location, for example,
+ * when changing the query, just keep the search visible.
+ * Make sure the search is visible though, since we could be returning
+ * from a previous search location when using the history */
+ if (nautilus_view_is_searching (view))
+ {
+ nautilus_window_slot_set_search_visible (self, TRUE);
+ return;
+ }
+
+ query = nautilus_query_editor_get_query (self->query_editor);
+ if (query)
+ {
+ /* If the view is not searching, but search is visible, and the
+ * query is empty, we don't hide it. Some users enable the search
+ * and then change locations, then they search. */
+ if (!nautilus_query_is_empty (query))
+ {
+ nautilus_window_slot_set_search_visible (self, FALSE);
+ }
+ }
+
+ if (self->pending_search_query)
+ {
+ nautilus_window_slot_search (self, g_object_ref (self->pending_search_query));
+ }
+}
+
+static void
+nautilus_window_slot_sync_actions (NautilusWindowSlot *self)
+{
+ NautilusView *view;
+ GAction *action;
+ GVariant *variant;
+
+ if (!nautilus_window_slot_get_active (self))
+ {
+ return;
+ }
+
+ if (self->content_view == NULL || self->new_content_view != NULL)
+ {
+ return;
+ }
+
+ /* Check if we need to close the search or show search after changing the location.
+ * Needs to be done after the change has been done, if not, a loop happens,
+ * because setting the search enabled or not actually opens a location */
+ update_search_visible (self);
+
+ /* Files view mode */
+ view = nautilus_window_slot_get_current_view (self);
+ action = g_action_map_lookup_action (G_ACTION_MAP (self->slot_action_group), "files-view-mode");
+ g_simple_action_set_enabled (G_SIMPLE_ACTION (action), NAUTILUS_IS_FILES_VIEW (view));
+ if (g_action_get_enabled (action))
+ {
+ variant = g_variant_new_uint32 (nautilus_view_get_view_id (view));
+ g_action_change_state (action, variant);
+ }
+ action = g_action_map_lookup_action (G_ACTION_MAP (self->slot_action_group), "files-view-mode-toggle");
+ g_simple_action_set_enabled (G_SIMPLE_ACTION (action), NAUTILUS_IS_FILES_VIEW (view));
+}
+
+static void
+query_editor_cancel_callback (NautilusQueryEditor *editor,
+ NautilusWindowSlot *self)
+{
+ nautilus_window_slot_set_search_visible (self, FALSE);
+}
+
+static void
+query_editor_activated_callback (NautilusQueryEditor *editor,
+ NautilusWindowSlot *self)
+{
+ if (self->content_view != NULL)
+ {
+ if (NAUTILUS_IS_FILES_VIEW (self->content_view))
+ {
+ nautilus_files_view_activate_selection (NAUTILUS_FILES_VIEW (self->content_view));
+ }
+ }
+}
+
+static void
+query_editor_focus_view_callback (NautilusQueryEditor *editor,
+ NautilusWindowSlot *self)
+{
+ if (self->content_view != NULL)
+ {
+ gtk_widget_grab_focus (GTK_WIDGET (self->content_view));
+ }
+}
+
+static void
+query_editor_changed_callback (NautilusQueryEditor *editor,
+ NautilusQuery *query,
+ gboolean reload,
+ NautilusWindowSlot *self)
+{
+ NautilusView *view;
+
+ view = nautilus_window_slot_get_current_view (self);
+
+ nautilus_view_set_search_query (view, query);
+ nautilus_window_slot_open_location_full (self, nautilus_view_get_location (view), 0, NULL);
+}
+
+static void
+hide_query_editor (NautilusWindowSlot *self)
+{
+ NautilusView *view;
+
+ view = nautilus_window_slot_get_current_view (self);
+
+ g_clear_signal_handler (&self->qe_changed_id, self->query_editor);
+ g_clear_signal_handler (&self->qe_cancel_id, self->query_editor);
+ g_clear_signal_handler (&self->qe_activated_id, self->query_editor);
+ g_clear_signal_handler (&self->qe_focus_view_id, self->query_editor);
+
+ nautilus_query_editor_set_query (self->query_editor, NULL);
+
+ if (nautilus_view_is_searching (view))
+ {
+ g_autolist (NautilusFile) selection = NULL;
+
+ selection = nautilus_view_get_selection (view);
+
+ nautilus_view_set_search_query (view, NULL);
+ nautilus_window_slot_open_location_full (self,
+ nautilus_view_get_location (view),
+ 0,
+ selection);
+ }
+
+ if (nautilus_window_slot_get_active (self))
+ {
+ gtk_widget_grab_focus (GTK_WIDGET (self->window));
+ }
+}
+
+static GFile *
+nautilus_window_slot_get_current_location (NautilusWindowSlot *self)
+{
+ if (self->pending_location != NULL)
+ {
+ return self->pending_location;
+ }
+
+ if (self->location != NULL)
+ {
+ return self->location;
+ }
+
+ return NULL;
+}
+
+static void
+show_query_editor (NautilusWindowSlot *self)
+{
+ NautilusView *view;
+
+ view = nautilus_window_slot_get_current_view (self);
+ if (view == NULL)
+ {
+ return;
+ }
+
+ if (nautilus_view_is_searching (view))
+ {
+ NautilusQuery *query;
+
+ query = nautilus_view_get_search_query (view);
+
+ if (query != NULL)
+ {
+ nautilus_query_editor_set_query (self->query_editor, query);
+ }
+ }
+
+ gtk_widget_grab_focus (GTK_WIDGET (self->query_editor));
+
+ if (self->qe_changed_id == 0)
+ {
+ self->qe_changed_id =
+ g_signal_connect (self->query_editor, "changed",
+ G_CALLBACK (query_editor_changed_callback), self);
+ }
+ if (self->qe_cancel_id == 0)
+ {
+ self->qe_cancel_id =
+ g_signal_connect (self->query_editor, "cancel",
+ G_CALLBACK (query_editor_cancel_callback), self);
+ }
+ if (self->qe_activated_id == 0)
+ {
+ self->qe_activated_id =
+ g_signal_connect (self->query_editor, "activated",
+ G_CALLBACK (query_editor_activated_callback), self);
+ }
+ if (self->qe_focus_view_id == 0)
+ {
+ self->qe_focus_view_id =
+ g_signal_connect (self->query_editor, "focus-view",
+ G_CALLBACK (query_editor_focus_view_callback), self);
+ }
+}
+
+static void
+nautilus_window_slot_set_search_visible (NautilusWindowSlot *self,
+ gboolean visible)
+{
+ GAction *action;
+
+ action = g_action_map_lookup_action (G_ACTION_MAP (self->slot_action_group),
+ "search-visible");
+ g_action_change_state (action, g_variant_new_boolean (visible));
+}
+
+static gboolean
+nautilus_window_slot_get_search_visible (NautilusWindowSlot *self)
+{
+ GAction *action;
+ GVariant *state;
+ gboolean searching;
+
+ action = g_action_map_lookup_action (G_ACTION_MAP (self->slot_action_group),
+ "search-visible");
+ state = g_action_get_state (action);
+ searching = g_variant_get_boolean (state);
+
+ g_variant_unref (state);
+
+ return searching;
+}
+
+void
+nautilus_window_slot_search (NautilusWindowSlot *self,
+ NautilusQuery *query)
+{
+ NautilusView *view;
+
+ g_clear_object (&self->pending_search_query);
+
+ view = nautilus_window_slot_get_current_view (self);
+ /* We could call this when the location is still being checked in the
+ * window slot. For that, save the search we want to do for once we have
+ * a view set up */
+ if (view)
+ {
+ nautilus_window_slot_set_search_visible (self, TRUE);
+ nautilus_query_editor_set_query (self->query_editor, query);
+ nautilus_view_set_search_query (view, query);
+ }
+ else
+ {
+ self->pending_search_query = g_object_ref (query);
+ }
+}
+
+gboolean
+nautilus_window_slot_handle_event (NautilusWindowSlot *self,
+ GtkEventControllerKey *controller,
+ guint keyval,
+ GdkModifierType state)
+{
+ gboolean retval;
+ GAction *action;
+
+ retval = FALSE;
+ action = g_action_map_lookup_action (G_ACTION_MAP (self->slot_action_group),
+ "search-visible");
+
+ if (keyval == GDK_KEY_Escape ||
+ keyval == GDK_KEY_BackSpace)
+ {
+ g_autoptr (GVariant) action_state = NULL;
+
+ action_state = g_action_get_state (action);
+
+ if (!g_variant_get_boolean (action_state))
+ {
+ return GDK_EVENT_PROPAGATE;
+ }
+ else if (keyval == GDK_KEY_Escape)
+ {
+ nautilus_window_slot_set_search_visible (self, FALSE);
+ return GDK_EVENT_STOP;
+ }
+ }
+
+ /* If the action is not enabled, don't try to handle search */
+ if (g_action_get_enabled (action))
+ {
+ retval = nautilus_query_editor_handle_event (self->query_editor,
+ controller,
+ keyval,
+ state);
+ }
+
+ if (retval)
+ {
+ nautilus_window_slot_set_search_visible (self, TRUE);
+ }
+
+ return retval;
+}
+
+static void
+nautilus_window_slot_remove_extra_location_widgets (NautilusWindowSlot *self)
+{
+ GtkWidget *widget;
+ while ((widget = gtk_widget_get_first_child (self->extra_location_widgets)) != NULL)
+ {
+ gtk_box_remove (GTK_BOX (self->extra_location_widgets), widget);
+ }
+}
+
+static void
+nautilus_window_slot_add_extra_location_widget (NautilusWindowSlot *self,
+ GtkWidget *widget)
+{
+ gtk_box_append (GTK_BOX (self->extra_location_widgets), widget);
+ gtk_widget_show (self->extra_location_widgets);
+}
+
+static void
+nautilus_window_slot_set_searching (NautilusWindowSlot *self,
+ gboolean searching)
+{
+ self->searching = searching;
+ g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_SEARCHING]);
+}
+
+static void
+nautilus_window_slot_set_selection (NautilusWindowSlot *self,
+ GList *selection)
+{
+ self->selection = selection;
+ g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_SELECTION]);
+}
+
+static void
+real_set_extensions_background_menu (NautilusWindowSlot *self,
+ GMenuModel *menu)
+{
+ g_set_object (&self->extensions_background_menu, menu);
+}
+
+static void
+real_set_templates_menu (NautilusWindowSlot *self,
+ GMenuModel *menu)
+{
+ g_set_object (&self->templates_menu, menu);
+}
+
+static void
+nautilus_window_slot_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ NautilusWindowSlot *self = NAUTILUS_WINDOW_SLOT (object);
+
+ switch (property_id)
+ {
+ case PROP_ACTIVE:
+ {
+ nautilus_window_slot_set_active (self, g_value_get_boolean (value));
+ }
+ break;
+
+ case PROP_WINDOW:
+ {
+ nautilus_window_slot_set_window (self, g_value_get_object (value));
+ }
+ break;
+
+ case PROP_LOCATION:
+ {
+ nautilus_window_slot_set_location (self, g_value_get_object (value));
+ }
+ break;
+
+ case PROP_SEARCHING:
+ {
+ nautilus_window_slot_set_searching (self, g_value_get_boolean (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;
+
+ case PROP_SELECTION:
+ {
+ nautilus_window_slot_set_selection (self, g_value_get_pointer (value));
+ }
+ break;
+
+ default:
+ {
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ }
+ break;
+ }
+}
+
+static GMenuModel *
+real_get_extensions_background_menu (NautilusWindowSlot *self)
+{
+ return self->extensions_background_menu;
+}
+
+GMenuModel *
+nautilus_window_slot_get_extensions_background_menu (NautilusWindowSlot *self)
+{
+ GMenuModel *menu = NULL;
+
+ g_object_get (self, "extensions-background-menu", &menu, NULL);
+
+ return menu;
+}
+
+static GMenuModel *
+real_get_templates_menu (NautilusWindowSlot *self)
+{
+ return self->templates_menu;
+}
+
+GMenuModel *
+nautilus_window_slot_get_templates_menu (NautilusWindowSlot *self)
+{
+ GMenuModel *menu = NULL;
+
+ g_object_get (self, "templates-menu", &menu, NULL);
+
+ return menu;
+}
+
+static void
+nautilus_window_slot_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ NautilusWindowSlot *self = NAUTILUS_WINDOW_SLOT (object);
+ switch (property_id)
+ {
+ case PROP_ACTIVE:
+ {
+ g_value_set_boolean (value, nautilus_window_slot_get_active (self));
+ }
+ break;
+
+ case PROP_WINDOW:
+ {
+ g_value_set_object (value, self->window);
+ }
+ break;
+
+ case PROP_ICON_NAME:
+ {
+ g_value_set_static_string (value, nautilus_window_slot_get_icon_name (self));
+ }
+ break;
+
+ case PROP_TOOLBAR_MENU_SECTIONS:
+ {
+ g_value_set_object (value, nautilus_window_slot_get_toolbar_menu_sections (self));
+ }
+ break;
+
+ case PROP_EXTENSIONS_BACKGROUND_MENU:
+ {
+ g_value_set_object (value, real_get_extensions_background_menu (self));
+ }
+ break;
+
+ case PROP_TEMPLATES_MENU:
+ {
+ g_value_set_object (value, real_get_templates_menu (self));
+ }
+ break;
+
+ case PROP_LOADING:
+ {
+ g_value_set_boolean (value, nautilus_window_slot_get_loading (self));
+ }
+ break;
+
+ case PROP_LOCATION:
+ {
+ g_value_set_object (value, nautilus_window_slot_get_current_location (self));
+ }
+ break;
+
+ case PROP_SEARCHING:
+ {
+ g_value_set_boolean (value, nautilus_window_slot_get_searching (self));
+ }
+ break;
+
+ case PROP_TOOLTIP:
+ {
+ g_value_set_static_string (value, nautilus_window_slot_get_tooltip (self));
+ }
+ break;
+
+ case PROP_ALLOW_STOP:
+ {
+ g_value_set_boolean (value, nautilus_window_slot_get_allow_stop (self));
+ }
+ break;
+
+ case PROP_TITLE:
+ {
+ g_value_set_string (value, nautilus_window_slot_get_title (self));
+ }
+ break;
+
+ default:
+ {
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ }
+ break;
+ }
+}
+
+gboolean
+nautilus_window_slot_get_searching (NautilusWindowSlot *self)
+{
+ return self->searching;
+}
+
+GList *
+nautilus_window_slot_get_selection (NautilusWindowSlot *self)
+{
+ return self->selection;
+}
+
+static void
+nautilus_window_slot_constructed (GObject *object)
+{
+ NautilusWindowSlot *self = NAUTILUS_WINDOW_SLOT (object);
+ GtkWidget *extras_vbox;
+ GtkStyleContext *style_context;
+
+ G_OBJECT_CLASS (nautilus_window_slot_parent_class)->constructed (object);
+
+ gtk_orientable_set_orientation (GTK_ORIENTABLE (self),
+ GTK_ORIENTATION_VERTICAL);
+ gtk_widget_show (GTK_WIDGET (self));
+
+ extras_vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0);
+ self->extra_location_widgets = extras_vbox;
+ gtk_box_append (GTK_BOX (self), extras_vbox);
+ gtk_widget_show (extras_vbox);
+
+ self->query_editor = NAUTILUS_QUERY_EDITOR (nautilus_query_editor_new ());
+ /* We want to keep alive the query editor betwen additions and removals on the
+ * UI, specifically when the toolbar adds or removes it */
+ g_object_ref_sink (self->query_editor);
+ gtk_widget_show (GTK_WIDGET (self->query_editor));
+
+ self->search_info_label = GTK_LABEL (gtk_label_new (NULL));
+ self->search_info_label_revealer = GTK_REVEALER (gtk_revealer_new ());
+
+ gtk_revealer_set_child (GTK_REVEALER (self->search_info_label_revealer),
+ GTK_WIDGET (self->search_info_label));
+ gtk_box_append (GTK_BOX (self),
+ GTK_WIDGET (self->search_info_label_revealer));
+
+ gtk_widget_show (GTK_WIDGET (self->search_info_label));
+ gtk_widget_show (GTK_WIDGET (self->search_info_label_revealer));
+
+ style_context = gtk_widget_get_style_context (GTK_WIDGET (self->search_info_label));
+ gtk_style_context_add_class (style_context, "search-information");
+
+ g_object_bind_property (self, "location",
+ self->query_editor, "location",
+ G_BINDING_DEFAULT);
+
+ self->title = g_strdup (_("Loading…"));
+ g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_TITLE]);
+}
+
+static void
+action_search_visible (GSimpleAction *action,
+ GVariant *state,
+ gpointer user_data)
+{
+ NautilusWindowSlot *self;
+ GVariant *current_state;
+
+ self = NAUTILUS_WINDOW_SLOT (user_data);
+ current_state = g_action_get_state (G_ACTION (action));
+ if (g_variant_get_boolean (current_state) != g_variant_get_boolean (state))
+ {
+ g_simple_action_set_state (action, state);
+
+ if (g_variant_get_boolean (state))
+ {
+ show_query_editor (self);
+ nautilus_window_slot_set_searching (self, TRUE);
+ }
+ else
+ {
+ hide_query_editor (self);
+ nautilus_window_slot_set_searching (self, FALSE);
+ }
+
+ update_search_information (self);
+ }
+
+ g_variant_unref (current_state);
+}
+
+static void
+change_files_view_mode (NautilusWindowSlot *self,
+ guint view_id)
+{
+ const gchar *preferences_key;
+
+ if (!nautilus_window_slot_content_view_matches (self, view_id))
+ {
+ nautilus_window_slot_set_content_view (self, view_id);
+ }
+ preferences_key = nautilus_view_is_searching (nautilus_window_slot_get_current_view (self)) ?
+ NAUTILUS_PREFERENCES_SEARCH_VIEW :
+ NAUTILUS_PREFERENCES_DEFAULT_FOLDER_VIEWER;
+
+ g_settings_set_enum (nautilus_preferences, preferences_key, view_id);
+}
+
+static void
+action_files_view_mode_toggle (GSimpleAction *action,
+ GVariant *value,
+ gpointer user_data)
+{
+ NautilusWindowSlot *self;
+ guint current_view_id;
+
+ self = NAUTILUS_WINDOW_SLOT (user_data);
+ if (!NAUTILUS_IS_FILES_VIEW (self->content_view))
+ {
+ return;
+ }
+
+ current_view_id = nautilus_view_get_view_id (self->content_view);
+ if (current_view_id == NAUTILUS_VIEW_LIST_ID)
+ {
+ change_files_view_mode (self, NAUTILUS_VIEW_GRID_ID);
+ }
+ else
+ {
+ change_files_view_mode (self, NAUTILUS_VIEW_LIST_ID);
+ }
+}
+
+static void
+action_files_view_mode (GSimpleAction *action,
+ GVariant *value,
+ gpointer user_data)
+{
+ NautilusWindowSlot *self;
+ guint view_id;
+
+ view_id = g_variant_get_uint32 (value);
+ self = NAUTILUS_WINDOW_SLOT (user_data);
+
+ if (!NAUTILUS_IS_FILES_VIEW (nautilus_window_slot_get_current_view (self)))
+ {
+ return;
+ }
+
+ change_files_view_mode (self, view_id);
+
+ g_simple_action_set_state (action, value);
+}
+
+const GActionEntry slot_entries[] =
+{
+ { "files-view-mode", NULL, "u", "uint32 " G_STRINGIFY (NAUTILUS_VIEW_INVALID_ID), action_files_view_mode },
+ { "files-view-mode-toggle", action_files_view_mode_toggle },
+ { "search-visible", NULL, NULL, "false", action_search_visible },
+};
+
+static void
+update_search_information (NautilusWindowSlot *self)
+{
+ GFile *location;
+
+ if (!nautilus_window_slot_get_searching (self))
+ {
+ gtk_revealer_set_reveal_child (self->search_info_label_revealer, FALSE);
+
+ return;
+ }
+
+ location = nautilus_window_slot_get_current_location (self);
+
+ if (location)
+ {
+ g_autoptr (NautilusFile) file = NULL;
+ gchar *label;
+ g_autofree gchar *uri = NULL;
+
+ file = nautilus_file_get (location);
+ label = NULL;
+ uri = g_file_get_uri (location);
+
+ if (nautilus_file_is_other_locations (file))
+ {
+ label = _("Searching locations only");
+ }
+ else if (g_str_has_prefix (uri, "network://"))
+ {
+ label = _("Searching network locations only");
+ }
+ else if (nautilus_file_is_remote (file) &&
+ location_settings_search_get_recursive_for_location (location) == NAUTILUS_QUERY_RECURSIVE_NEVER)
+ {
+ label = _("Remote location — only searching the current folder");
+ }
+ else if (location_settings_search_get_recursive_for_location (location) == NAUTILUS_QUERY_RECURSIVE_NEVER)
+ {
+ label = _("Only searching the current folder");
+ }
+
+ gtk_label_set_label (self->search_info_label, label);
+ gtk_revealer_set_reveal_child (self->search_info_label_revealer,
+ label != NULL);
+ }
+}
+
+static void
+recursive_search_preferences_changed (GSettings *settings,
+ gchar *key,
+ gpointer callback_data)
+{
+ NautilusWindowSlot *self;
+
+ self = callback_data;
+
+ update_search_information (self);
+}
+
+static void
+remove_old_trash_files_preferences_changed (GSettings *settings,
+ gchar *key,
+ gpointer callback_data)
+{
+ NautilusWindowSlot *self;
+ GFile *location;
+ g_autoptr (NautilusDirectory) directory = NULL;
+
+ g_assert (NAUTILUS_IS_WINDOW_SLOT (callback_data));
+
+ self = NAUTILUS_WINDOW_SLOT (callback_data);
+ location = nautilus_window_slot_get_current_location (self);
+ directory = nautilus_directory_get (location);
+
+ if (nautilus_directory_is_in_trash (directory))
+ {
+ if (g_settings_get_boolean (gnome_privacy_preferences, "remove-old-trash-files"))
+ {
+ nautilus_window_slot_setup_extra_location_widgets (self);
+ }
+ else
+ {
+ nautilus_window_slot_remove_extra_location_widgets (self);
+ }
+ }
+}
+
+static void
+nautilus_window_slot_init (NautilusWindowSlot *self)
+{
+ GApplication *app;
+ const gchar *search_visible_accels[] =
+ {
+ "<control>f",
+ "Search",
+ NULL
+ };
+
+ app = g_application_get_default ();
+
+ g_signal_connect_object (nautilus_preferences,
+ "changed::recursive-search",
+ G_CALLBACK (recursive_search_preferences_changed),
+ self, 0);
+
+ g_signal_connect_object (gnome_privacy_preferences,
+ "changed::remove-old-trash-files",
+ G_CALLBACK (remove_old_trash_files_preferences_changed),
+ self, 0);
+
+ self->slot_action_group = G_ACTION_GROUP (g_simple_action_group_new ());
+ g_action_map_add_action_entries (G_ACTION_MAP (self->slot_action_group),
+ slot_entries,
+ G_N_ELEMENTS (slot_entries),
+ self);
+ gtk_widget_insert_action_group (GTK_WIDGET (self),
+ "slot",
+ G_ACTION_GROUP (self->slot_action_group));
+
+ nautilus_application_set_accelerator (app,
+ "slot.files-view-mode(uint32 " G_STRINGIFY (NAUTILUS_VIEW_LIST_ID) ")",
+ "<control>1");
+ nautilus_application_set_accelerator (app,
+ "slot.files-view-mode(uint32 " G_STRINGIFY (NAUTILUS_VIEW_GRID_ID) ")",
+ "<control>2");
+ nautilus_application_set_accelerators (app, "slot.search-visible", search_visible_accels);
+
+ self->view_mode_before_search = NAUTILUS_VIEW_INVALID_ID;
+ self->view_mode_before_places = NAUTILUS_VIEW_INVALID_ID;
+}
+
+#define DEBUG_FLAG NAUTILUS_DEBUG_WINDOW
+#include "nautilus-debug.h"
+
+static void begin_location_change (NautilusWindowSlot *slot,
+ GFile *location,
+ GFile *previous_location,
+ GList *new_selection,
+ NautilusLocationChangeType type,
+ guint distance,
+ const char *scroll_pos);
+static void free_location_change (NautilusWindowSlot *self);
+static void end_location_change (NautilusWindowSlot *self);
+static void got_file_info_for_view_selection_callback (NautilusFile *file,
+ gpointer callback_data);
+static gboolean setup_view (NautilusWindowSlot *self,
+ NautilusView *view);
+static void load_new_location (NautilusWindowSlot *slot,
+ GFile *location,
+ GList *selection,
+ NautilusFile *file_to_activate,
+ gboolean tell_current_content_view,
+ gboolean tell_new_content_view);
+
+void
+nautilus_window_slot_open_location_full (NautilusWindowSlot *self,
+ GFile *location,
+ NautilusOpenFlags flags,
+ GList *new_selection)
+{
+ GFile *old_location;
+ g_autolist (NautilusFile) old_selection = NULL;
+
+ old_selection = NULL;
+ old_location = nautilus_window_slot_get_location (self);
+
+ if (self->content_view)
+ {
+ old_selection = nautilus_view_get_selection (self->content_view);
+ }
+ if (old_location && g_file_equal (old_location, location) &&
+ nautilus_file_selection_equal (old_selection, new_selection))
+ {
+ goto done;
+ }
+
+ begin_location_change (self, location, old_location, new_selection,
+ NAUTILUS_LOCATION_CHANGE_STANDARD, 0, NULL);
+
+done:
+ nautilus_profile_end (NULL);
+}
+
+static GList *
+check_select_old_location_containing_folder (GList *new_selection,
+ GFile *location,
+ GFile *previous_location)
+{
+ GFile *from_folder, *parent;
+
+ /* If there is no new selection and the new location is
+ * a (grand)parent of the old location then we automatically
+ * select the folder the previous location was in */
+ if (new_selection == NULL && previous_location != NULL &&
+ g_file_has_prefix (previous_location, location))
+ {
+ from_folder = g_object_ref (previous_location);
+ parent = g_file_get_parent (from_folder);
+ while (parent != NULL && !g_file_equal (parent, location))
+ {
+ g_object_unref (from_folder);
+ from_folder = parent;
+ parent = g_file_get_parent (from_folder);
+ }
+
+ if (parent != NULL)
+ {
+ new_selection = g_list_prepend (NULL, nautilus_file_get (from_folder));
+ g_object_unref (parent);
+ }
+
+ g_object_unref (from_folder);
+ }
+
+ return new_selection;
+}
+
+static void
+check_force_reload (GFile *location,
+ NautilusLocationChangeType type)
+{
+ NautilusDirectory *directory;
+ NautilusFile *file;
+ gboolean force_reload;
+
+ /* The code to force a reload is here because if we do it
+ * after determining an initial view (in the components), then
+ * we end up fetching things twice.
+ */
+ directory = nautilus_directory_get (location);
+ file = nautilus_file_get (location);
+
+ if (type == NAUTILUS_LOCATION_CHANGE_RELOAD)
+ {
+ force_reload = TRUE;
+ }
+ else
+ {
+ force_reload = !g_file_is_native (location);
+ }
+
+ /* We need to invalidate file attributes as well due to how mounting works
+ * in the window slot and to avoid other caching issues.
+ * Read handle_mount_if_needed for one example */
+ if (force_reload)
+ {
+ nautilus_file_invalidate_all_attributes (file);
+ nautilus_directory_force_reload (directory);
+ }
+
+ nautilus_directory_unref (directory);
+ nautilus_file_unref (file);
+}
+
+static void
+save_scroll_position_for_history (NautilusWindowSlot *self)
+{
+ /* Set current_bookmark scroll pos */
+ if (self->current_location_bookmark != NULL &&
+ self->content_view != NULL &&
+ NAUTILUS_IS_FILES_VIEW (self->content_view))
+ {
+ char *current_pos;
+
+ current_pos = nautilus_files_view_get_first_visible_file (NAUTILUS_FILES_VIEW (self->content_view));
+ nautilus_bookmark_set_scroll_pos (self->current_location_bookmark, current_pos);
+ g_free (current_pos);
+ }
+}
+
+/*
+ * begin_location_change
+ *
+ * Change a window slot's location.
+ * @window: The NautilusWindow whose location should be changed.
+ * @location: A url specifying the location to load
+ * @previous_location: The url that was previously shown in the window that initialized the change, if any
+ * @new_selection: The initial selection to present after loading the location
+ * @type: Which type of location change is this? Standard, back, forward, or reload?
+ * @distance: If type is back or forward, the index into the back or forward chain. If
+ * type is standard or reload, this is ignored, and must be 0.
+ * @scroll_pos: The file to scroll to when the location is loaded.
+ *
+ * This is the core function for changing the location of a window. Every change to the
+ * location begins here.
+ */
+static void
+begin_location_change (NautilusWindowSlot *self,
+ GFile *location,
+ GFile *previous_location,
+ GList *new_selection,
+ NautilusLocationChangeType type,
+ guint distance,
+ const char *scroll_pos)
+{
+ g_assert (self != NULL);
+ g_assert (location != NULL);
+ g_assert (type == NAUTILUS_LOCATION_CHANGE_BACK
+ || type == NAUTILUS_LOCATION_CHANGE_FORWARD
+ || distance == 0);
+
+ nautilus_profile_start (NULL);
+
+ /* Avoid to update status from the current view in our async calls */
+ nautilus_window_slot_disconnect_content_view (self);
+ /* We are going to change the location, so make sure we stop any loading
+ * or searching of the previous view, so we avoid to be slow */
+ nautilus_window_slot_stop_loading (self);
+
+ nautilus_window_slot_set_allow_stop (self, TRUE);
+
+ new_selection = check_select_old_location_containing_folder (new_selection, location, previous_location);
+
+ g_assert (self->pending_location == NULL);
+
+ self->pending_location = g_object_ref (location);
+ self->location_change_type = type;
+ self->location_change_distance = distance;
+ self->tried_mount = FALSE;
+ self->pending_selection = nautilus_file_list_copy (new_selection);
+
+ self->pending_scroll_to = g_strdup (scroll_pos);
+
+ check_force_reload (location, type);
+
+ save_scroll_position_for_history (self);
+
+ /* Get the info needed to make decisions about how to open the new location */
+ self->determine_view_file = nautilus_file_get (location);
+ g_assert (self->determine_view_file != NULL);
+
+ nautilus_file_call_when_ready (self->determine_view_file,
+ NAUTILUS_FILE_ATTRIBUTE_INFO |
+ NAUTILUS_FILE_ATTRIBUTE_MOUNT,
+ got_file_info_for_view_selection_callback,
+ self);
+
+ nautilus_profile_end (NULL);
+}
+
+static void
+nautilus_window_slot_set_location (NautilusWindowSlot *self,
+ GFile *location)
+{
+ GFile *old_location;
+
+ if (self->location &&
+ g_file_equal (location, self->location))
+ {
+ /* The location name could be updated even if the location
+ * wasn't changed. This is the case for a search.
+ */
+ nautilus_window_slot_update_title (self);
+ return;
+ }
+
+ old_location = self->location;
+ self->location = g_object_ref (location);
+
+ if (nautilus_window_slot_get_active (self))
+ {
+ nautilus_window_sync_location_widgets (self->window);
+ }
+
+ nautilus_window_slot_update_title (self);
+
+ if (old_location)
+ {
+ g_object_unref (old_location);
+ }
+
+ g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_LOCATION]);
+}
+
+static void
+viewed_file_changed_callback (NautilusFile *file,
+ NautilusWindowSlot *self)
+{
+ GFile *new_location;
+ gboolean is_in_trash, was_in_trash;
+
+ g_assert (NAUTILUS_IS_FILE (file));
+ g_assert (NAUTILUS_IS_WINDOW_SLOT (self));
+
+ g_assert (file == self->viewed_file);
+
+ if (!nautilus_file_is_not_yet_confirmed (file))
+ {
+ self->viewed_file_seen = TRUE;
+ }
+
+ was_in_trash = self->viewed_file_in_trash;
+
+ self->viewed_file_in_trash = is_in_trash = nautilus_file_is_in_trash (file);
+
+ if (nautilus_file_is_gone (file) || (is_in_trash && !was_in_trash))
+ {
+ if (self->viewed_file_seen)
+ {
+ GFile *go_to_file;
+ GFile *parent;
+ GFile *location;
+ GMount *mount;
+
+ parent = NULL;
+ location = nautilus_file_get_location (file);
+
+ if (g_file_is_native (location))
+ {
+ mount = nautilus_get_mounted_mount_for_root (location);
+
+ if (mount == NULL)
+ {
+ parent = g_file_get_parent (location);
+ }
+
+ g_clear_object (&mount);
+ }
+
+ if (parent != NULL)
+ {
+ /* auto-show existing parent */
+ go_to_file = nautilus_find_existing_uri_in_hierarchy (parent);
+ }
+ else
+ {
+ go_to_file = g_file_new_for_path (g_get_home_dir ());
+ }
+
+ nautilus_window_slot_open_location_full (self, go_to_file, 0, NULL);
+
+ g_clear_object (&parent);
+ g_object_unref (go_to_file);
+ g_object_unref (location);
+ }
+ }
+ else
+ {
+ new_location = nautilus_file_get_location (file);
+ nautilus_window_slot_set_location (self, new_location);
+ g_object_unref (new_location);
+ }
+}
+
+static void
+nautilus_window_slot_go_home (NautilusWindowSlot *self,
+ NautilusOpenFlags flags)
+{
+ GFile *home;
+
+ g_return_if_fail (NAUTILUS_IS_WINDOW_SLOT (self));
+
+ home = g_file_new_for_path (g_get_home_dir ());
+ nautilus_window_slot_open_location_full (self, home, flags, NULL);
+ g_object_unref (home);
+}
+
+static void
+nautilus_window_slot_set_viewed_file (NautilusWindowSlot *self,
+ NautilusFile *file)
+{
+ NautilusFileAttributes attributes;
+
+ if (self->viewed_file == file)
+ {
+ return;
+ }
+
+ nautilus_file_ref (file);
+
+ if (self->viewed_file != NULL)
+ {
+ g_signal_handlers_disconnect_by_func (self->viewed_file,
+ G_CALLBACK (viewed_file_changed_callback),
+ self);
+ nautilus_file_monitor_remove (self->viewed_file,
+ self);
+ }
+
+ if (file != NULL)
+ {
+ attributes = NAUTILUS_FILE_ATTRIBUTE_INFO;
+ nautilus_file_monitor_add (file, self, attributes);
+
+ g_signal_connect_object (file, "changed",
+ G_CALLBACK (viewed_file_changed_callback), self, 0);
+ }
+
+ nautilus_file_unref (self->viewed_file);
+ self->viewed_file = file;
+}
+
+typedef struct
+{
+ GCancellable *cancellable;
+ NautilusWindowSlot *slot;
+} MountNotMountedData;
+
+static void
+mount_not_mounted_callback (GObject *source_object,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ MountNotMountedData *data;
+ NautilusWindowSlot *self;
+ GError *error;
+ GCancellable *cancellable;
+
+ data = user_data;
+ self = data->slot;
+ cancellable = data->cancellable;
+ g_free (data);
+
+ if (g_cancellable_is_cancelled (cancellable))
+ {
+ /* Cancelled, don't call back */
+ g_object_unref (cancellable);
+ return;
+ }
+
+ self->mount_cancellable = NULL;
+
+ self->determine_view_file = nautilus_file_get (self->pending_location);
+
+ error = NULL;
+ if (!g_file_mount_enclosing_volume_finish (G_FILE (source_object), res, &error))
+ {
+ self->mount_error = error;
+ got_file_info_for_view_selection_callback (self->determine_view_file, self);
+ self->mount_error = NULL;
+ g_error_free (error);
+ }
+ else
+ {
+ nautilus_file_invalidate_all_attributes (self->determine_view_file);
+ nautilus_file_call_when_ready (self->determine_view_file,
+ NAUTILUS_FILE_ATTRIBUTE_INFO |
+ NAUTILUS_FILE_ATTRIBUTE_MOUNT,
+ got_file_info_for_view_selection_callback,
+ self);
+ }
+
+ g_object_unref (cancellable);
+}
+
+static void
+nautilus_window_slot_display_view_selection_failure (NautilusWindow *window,
+ NautilusFile *file,
+ GFile *location,
+ GError *error)
+{
+ char *error_message;
+ char *detail_message;
+ char *scheme_string;
+ char *file_path;
+
+ /* Some sort of failure occurred. How 'bout we tell the user? */
+
+ error_message = g_strdup (_("Oops! Something went wrong."));
+ detail_message = NULL;
+ if (error == NULL)
+ {
+ if (nautilus_file_is_directory (file))
+ {
+ detail_message = g_strdup (_("Unable to display the contents of this folder."));
+ }
+ else
+ {
+ detail_message = g_strdup (_("This location doesn’t appear to be a folder."));
+ }
+ }
+ else if (error->domain == G_IO_ERROR)
+ {
+ switch (error->code)
+ {
+ case G_IO_ERROR_NOT_FOUND:
+ {
+ file_path = g_file_get_path (location);
+ if (file_path != NULL)
+ {
+ detail_message = g_strdup_printf (_("Unable to find “%s”. Please check the spelling and try again."),
+ file_path);
+ }
+ else
+ {
+ detail_message = g_strdup (_("Unable to find the requested file. Please check the spelling and try again."));
+ }
+ g_free (file_path);
+ }
+ break;
+
+ case G_IO_ERROR_NOT_SUPPORTED:
+ {
+ scheme_string = g_file_get_uri_scheme (location);
+ if (scheme_string != NULL)
+ {
+ detail_message = g_strdup_printf (_("“%s” locations are not supported."),
+ scheme_string);
+ }
+ else
+ {
+ detail_message = g_strdup (_("Unable to handle this kind of location."));
+ }
+ g_free (scheme_string);
+ }
+ break;
+
+ case G_IO_ERROR_NOT_MOUNTED:
+ {
+ detail_message = g_strdup (_("Unable to access the requested location."));
+ }
+ break;
+
+ case G_IO_ERROR_PERMISSION_DENIED:
+ {
+ detail_message = g_strdup (_("Don’t have permission to access the requested location."));
+ }
+ break;
+
+ case G_IO_ERROR_HOST_NOT_FOUND:
+ {
+ /* This case can be hit for user-typed strings like "foo" due to
+ * the code that guesses web addresses when there's no initial "/".
+ * But this case is also hit for legitimate web addresses when
+ * the proxy is set up wrong.
+ */
+ detail_message = g_strdup (_("Unable to find the requested location. Please check the spelling or the network settings."));
+ }
+ break;
+
+ case G_IO_ERROR_CONNECTION_REFUSED:
+ {
+ /* This case can be hit when server application is not installed
+ * or is inactive in the system user is trying to connect to.
+ */
+ detail_message = g_strdup (_("The server has refused the connection. Typically this means that the firewall is blocking access or that the remote service is not running."));
+ }
+ break;
+
+ case G_IO_ERROR_CANCELLED:
+ case G_IO_ERROR_FAILED_HANDLED:
+ {
+ goto done;
+ }
+
+ default:
+ {
+ }
+ break;
+ }
+ }
+
+ if (detail_message == NULL)
+ {
+ detail_message = g_strdup_printf (_("Unhandled error message: %s"), error->message);
+ }
+
+ show_dialog (error_message, detail_message, GTK_WINDOW (window), GTK_MESSAGE_ERROR);
+
+done:
+ g_free (error_message);
+ g_free (detail_message);
+}
+
+/* FIXME: This works in the folowwing way. begin_location_change tries to get the
+ * information of the file directly.
+ * If the nautilus file finds that there is an error trying to get its
+ * information and the error match that the file is not mounted, it sets an
+ * internal attribute with the error then we try to mount it here.
+ *
+ * However, files are cached, and if the file doesn't get finalized in a location
+ * change, because needs to be in the navigation history or is a bookmark, and the
+ * file is not the root of the mount point, which is tracked by a volume monitor,
+ * and it gets unmounted aftwerwards, the file doesn't realize it's unmounted, and
+ * therefore this trick to open an unmounted file will fail the next time the user
+ * tries to open.
+ * For that, we need to always invalidate the file attributes when a location is
+ * changed, which is done in check_force_reload.
+ * A better way would be to make sure any children of the mounted root gets
+ * akwnoledge by it either by adding a reference to its parent volume monitor
+ * or with another solution. */
+static gboolean
+handle_mount_if_needed (NautilusWindowSlot *self,
+ NautilusFile *file)
+{
+ NautilusWindow *window;
+ GMountOperation *mount_op;
+ MountNotMountedData *data;
+ GFile *location;
+ GError *error = NULL;
+ gboolean needs_mount_handling = FALSE;
+
+ window = nautilus_window_slot_get_window (self);
+ if (self->mount_error)
+ {
+ error = g_error_copy (self->mount_error);
+ }
+ else if (nautilus_file_get_file_info_error (file) != NULL)
+ {
+ error = g_error_copy (nautilus_file_get_file_info_error (file));
+ }
+
+ if (error && error->domain == G_IO_ERROR && error->code == G_IO_ERROR_NOT_MOUNTED &&
+ !self->tried_mount)
+ {
+ self->tried_mount = TRUE;
+
+ mount_op = gtk_mount_operation_new (GTK_WINDOW (window));
+ g_mount_operation_set_password_save (mount_op, G_PASSWORD_SAVE_FOR_SESSION);
+ location = nautilus_file_get_location (file);
+ data = g_new0 (MountNotMountedData, 1);
+ data->cancellable = g_cancellable_new ();
+ data->slot = self;
+ self->mount_cancellable = data->cancellable;
+ g_file_mount_enclosing_volume (location, 0, mount_op, self->mount_cancellable,
+ mount_not_mounted_callback, data);
+ g_object_unref (location);
+ g_object_unref (mount_op);
+
+ needs_mount_handling = TRUE;
+ }
+
+ g_clear_error (&error);
+
+ return needs_mount_handling;
+}
+
+static gboolean
+handle_regular_file_if_needed (NautilusWindowSlot *self,
+ NautilusFile *file)
+{
+ NautilusFile *parent_file;
+ gboolean needs_regular_file_handling = FALSE;
+ parent_file = nautilus_file_get_parent (file);
+ if ((parent_file != NULL) &&
+ nautilus_file_get_file_type (file) == G_FILE_TYPE_REGULAR)
+ {
+ g_clear_pointer (&self->pending_selection, nautilus_file_list_free);
+ g_clear_object (&self->pending_location);
+ g_clear_object (&self->pending_file_to_activate);
+ g_free (self->pending_scroll_to);
+
+ self->pending_location = nautilus_file_get_parent_location (file);
+ if (nautilus_mime_file_extracts (file))
+ {
+ self->pending_file_to_activate = nautilus_file_ref (file);
+ }
+ else
+ {
+ self->pending_selection = g_list_prepend (NULL, nautilus_file_ref (file));
+ }
+ self->determine_view_file = nautilus_file_ref (parent_file);
+ self->pending_scroll_to = nautilus_file_get_uri (file);
+
+ nautilus_file_invalidate_all_attributes (self->determine_view_file);
+ nautilus_file_call_when_ready (self->determine_view_file,
+ NAUTILUS_FILE_ATTRIBUTE_INFO |
+ NAUTILUS_FILE_ATTRIBUTE_MOUNT,
+ got_file_info_for_view_selection_callback,
+ self);
+
+ needs_regular_file_handling = TRUE;
+ }
+
+ nautilus_file_unref (parent_file);
+
+ return needs_regular_file_handling;
+}
+
+static void
+got_file_info_for_view_selection_callback (NautilusFile *file,
+ gpointer callback_data)
+{
+ GError *error = NULL;
+ NautilusWindow *window;
+ NautilusWindowSlot *self;
+ NautilusFile *viewed_file;
+ NautilusView *view;
+ GFile *location;
+ NautilusApplication *app;
+
+ self = callback_data;
+ window = nautilus_window_slot_get_window (self);
+
+ g_assert (self->determine_view_file == file);
+ self->determine_view_file = NULL;
+
+ nautilus_profile_start (NULL);
+
+ if (handle_mount_if_needed (self, file))
+ {
+ goto done;
+ }
+
+ if (handle_regular_file_if_needed (self, file))
+ {
+ goto done;
+ }
+
+ if (self->mount_error)
+ {
+ error = g_error_copy (self->mount_error);
+ }
+ else if (nautilus_file_get_file_info_error (file) != NULL)
+ {
+ error = g_error_copy (nautilus_file_get_file_info_error (file));
+ }
+
+ location = self->pending_location;
+
+ /* desktop and other-locations GFile operations report G_IO_ERROR_NOT_SUPPORTED,
+ * but it's not an actual error for Nautilus */
+ if (!error || g_error_matches (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED))
+ {
+ view = nautilus_window_slot_get_view_for_location (self, location);
+ setup_view (self, view);
+ }
+ else
+ {
+ if (error == NULL)
+ {
+ error = g_error_new (G_IO_ERROR, G_IO_ERROR_NOT_FOUND,
+ _("Unable to load location"));
+ }
+ nautilus_window_slot_display_view_selection_failure (window,
+ file,
+ location,
+ error);
+
+ if (!gtk_widget_get_visible (GTK_WIDGET (window)))
+ {
+ /* Destroy never-had-a-chance-to-be-seen window. This case
+ * happens when a new window cannot display its initial URI.
+ */
+ /* if this is the only window, we don't want to quit, so we redirect it to home */
+
+ app = NAUTILUS_APPLICATION (g_application_get_default ());
+
+ if (g_list_length (nautilus_application_get_windows (app)) == 1)
+ {
+ /* the user could have typed in a home directory that doesn't exist,
+ * in which case going home would cause an infinite loop, so we
+ * better test for that */
+
+ if (!nautilus_is_root_directory (location))
+ {
+ if (!nautilus_is_home_directory (location))
+ {
+ nautilus_window_slot_go_home (self, FALSE);
+ }
+ else
+ {
+ GFile *root;
+
+ root = g_file_new_for_path ("/");
+ /* the last fallback is to go to a known place that can't be deleted! */
+ nautilus_window_slot_open_location_full (self, location, 0, NULL);
+ g_object_unref (root);
+ }
+ }
+ else
+ {
+ gtk_window_destroy (GTK_WINDOW (window));
+ }
+ }
+ else
+ {
+ /* Since this is a window, destroying it will also unref it. */
+ gtk_window_destroy (GTK_WINDOW (window));
+ }
+ }
+ else
+ {
+ GFile *slot_location;
+
+ /* Clean up state of already-showing window */
+ end_location_change (self);
+ slot_location = nautilus_window_slot_get_location (self);
+
+ /* XXX FIXME VOODOO TODO:
+ * Context: https://gitlab.gnome.org/GNOME/nautilus/issues/562
+ * (and the associated MR)
+ *
+ * This used to just close the slot, which, in combination with
+ * the transient error dialog, caused Mutter to have a heart attack
+ * and die when the slot happened to be the only one remaining.
+ * The following condition can hold true in (at least) two cases:
+ *
+ * 1. We are inside the “Other Locations” view and are opening
+ * a broken bookmark, which causes the window slot to get replaced
+ * with one that handles the location, and is, understandably,
+ * empty.
+ * 2. We open a broken bookmark in a new window, which works almost
+ * the same, in that it has no open location.
+ *
+ * Ernestas: I’m leaning towards having an in-view message about the
+ * failure, which avoids dialogs and magically disappearing
+ * slots/tabs/windows (also allowing to go back to the
+ * previous location), but a dialog is quicker to inform
+ * about the failure.
+ * XXX
+ */
+ if (slot_location == NULL)
+ {
+ nautilus_window_slot_go_home (self, 0);
+ }
+ else
+ {
+ /* We disconnected this, so we need to re-connect it */
+ viewed_file = nautilus_file_get (slot_location);
+ nautilus_window_slot_set_viewed_file (self, viewed_file);
+ nautilus_file_unref (viewed_file);
+
+ /* Leave the location bar showing the bad location that the user
+ * typed (or maybe achieved by dragging or something). Many times
+ * the mistake will just be an easily-correctable typo. The user
+ * can choose "Refresh" to get the original URI back in the location bar.
+ */
+ }
+ }
+ }
+
+done:
+ g_clear_error (&error);
+
+ nautilus_file_unref (file);
+ nautilus_profile_end (NULL);
+}
+
+/* Load a view into the window, either reusing the old one or creating
+ * a new one. This happens when you want to load a new location, or just
+ * switch to a different view.
+ * If pending_location is set we're loading a new location and
+ * pending_location/selection will be used. If not, we're just switching
+ * view, and the current location will be used.
+ */
+static gboolean
+setup_view (NautilusWindowSlot *self,
+ NautilusView *view)
+{
+ gboolean ret = TRUE;
+ GFile *old_location;
+ nautilus_profile_start (NULL);
+
+ nautilus_window_slot_disconnect_content_view (self);
+
+ self->new_content_view = view;
+
+ nautilus_window_slot_connect_new_content_view (self);
+
+ /* Forward search selection and state before loading the new model */
+ old_location = self->content_view ? nautilus_view_get_location (self->content_view) : NULL;
+
+ /* Actually load the pending location and selection: */
+ if (self->pending_location != NULL)
+ {
+ load_new_location (self,
+ self->pending_location,
+ self->pending_selection,
+ self->pending_file_to_activate,
+ FALSE,
+ TRUE);
+
+ nautilus_file_list_free (self->pending_selection);
+ self->pending_selection = NULL;
+ }
+ else if (old_location != NULL)
+ {
+ g_autolist (NautilusFile) selection = NULL;
+
+ selection = nautilus_view_get_selection (self->content_view);
+
+ load_new_location (self,
+ old_location,
+ selection,
+ NULL,
+ FALSE,
+ TRUE);
+ }
+ else
+ {
+ ret = FALSE;
+ goto out;
+ }
+
+ change_view (self);
+ gtk_widget_show (GTK_WIDGET (self->window));
+
+out:
+ nautilus_profile_end (NULL);
+
+ return ret;
+}
+
+static void
+load_new_location (NautilusWindowSlot *self,
+ GFile *location,
+ GList *selection,
+ NautilusFile *file_to_activate,
+ gboolean tell_current_content_view,
+ gboolean tell_new_content_view)
+{
+ NautilusView *view;
+ g_assert (self != NULL);
+ g_assert (location != NULL);
+
+ view = NULL;
+ nautilus_profile_start (NULL);
+ /* Note, these may recurse into report_load_underway */
+ if (self->content_view != NULL && tell_current_content_view)
+ {
+ view = self->content_view;
+ nautilus_view_set_location (self->content_view, location);
+ }
+
+ if (self->new_content_view != NULL && tell_new_content_view &&
+ (!tell_current_content_view ||
+ self->new_content_view != self->content_view))
+ {
+ view = self->new_content_view;
+ nautilus_view_set_location (self->new_content_view, location);
+ }
+ if (view)
+ {
+ nautilus_view_set_selection (view, selection);
+ if (file_to_activate != NULL)
+ {
+ g_autoptr (GAppInfo) app_info = NULL;
+ const gchar *app_id;
+
+ g_return_if_fail (NAUTILUS_IS_FILES_VIEW (view));
+ app_info = nautilus_mime_get_default_application_for_file (file_to_activate);
+ app_id = g_app_info_get_id (app_info);
+ if (g_strcmp0 (app_id, NAUTILUS_DESKTOP_ID) == 0)
+ {
+ nautilus_files_view_activate_file (NAUTILUS_FILES_VIEW (view),
+ file_to_activate, 0);
+ }
+ }
+ }
+
+ nautilus_profile_end (NULL);
+}
+
+static void
+end_location_change (NautilusWindowSlot *self)
+{
+ char *uri;
+ uri = nautilus_window_slot_get_location_uri (self);
+ if (uri)
+ {
+ DEBUG ("Finished loading window for uri %s", uri);
+ g_free (uri);
+ }
+
+ nautilus_window_slot_set_allow_stop (self, FALSE);
+
+ /* Now we can free details->pending_scroll_to, since the load_complete
+ * callback already has been emitted.
+ */
+ g_free (self->pending_scroll_to);
+ self->pending_scroll_to = NULL;
+
+ free_location_change (self);
+}
+
+static void
+free_location_change (NautilusWindowSlot *self)
+{
+ g_clear_object (&self->pending_location);
+ g_clear_object (&self->pending_file_to_activate);
+ nautilus_file_list_free (self->pending_selection);
+ self->pending_selection = NULL;
+
+ /* Don't free details->pending_scroll_to, since thats needed until
+ * the load_complete callback.
+ */
+
+ if (self->mount_cancellable != NULL)
+ {
+ g_cancellable_cancel (self->mount_cancellable);
+ self->mount_cancellable = NULL;
+ }
+
+ if (self->determine_view_file != NULL)
+ {
+ nautilus_file_cancel_call_when_ready
+ (self->determine_view_file,
+ got_file_info_for_view_selection_callback, self);
+ self->determine_view_file = NULL;
+ }
+}
+
+/* This sets up a new view, for the current location, with the provided id. Used
+ * whenever the user changes the type of view to use.
+ *
+ * Note that the current view will be thrown away, even if it has the same id.
+ * Callers may first check if !nautilus_window_slot_content_view_matches().
+ */
+static void
+nautilus_window_slot_set_content_view (NautilusWindowSlot *self,
+ guint id)
+{
+ NautilusFilesView *view;
+ g_autolist (NautilusFile) selection = NULL;
+ char *uri;
+ g_assert (self != NULL);
+
+ uri = nautilus_window_slot_get_location_uri (self);
+ DEBUG ("Change view of window %s to %d", uri, id);
+ g_free (uri);
+
+ selection = nautilus_view_get_selection (self->content_view);
+ view = nautilus_files_view_new (id, self);
+
+ nautilus_window_slot_stop_loading (self);
+
+ nautilus_window_slot_set_allow_stop (self, TRUE);
+
+ if (g_list_length (selection) == 0 && NAUTILUS_IS_FILES_VIEW (self->content_view))
+ {
+ /* If there is no selection, queue a scroll to the same icon that
+ * is currently visible */
+ self->pending_scroll_to = nautilus_files_view_get_first_visible_file (NAUTILUS_FILES_VIEW (self->content_view));
+ }
+
+ self->location_change_type = NAUTILUS_LOCATION_CHANGE_RELOAD;
+
+ if (!setup_view (self, NAUTILUS_VIEW (view)))
+ {
+ /* Just load the homedir. */
+ nautilus_window_slot_go_home (self, FALSE);
+ }
+}
+
+void
+nautilus_window_slot_back_or_forward (NautilusWindowSlot *self,
+ gboolean back,
+ guint distance)
+{
+ GList *list;
+ guint len;
+ NautilusBookmark *bookmark;
+ g_autoptr (GFile) location = NULL;
+ GFile *old_location;
+ g_autofree char *scroll_pos = NULL;
+
+ list = back ? self->back_list : self->forward_list;
+ len = g_list_length (list);
+
+ /* If we can't move in the direction at all, just return. */
+ if (list == NULL)
+ {
+ return;
+ }
+
+ /* If the distance to move is off the end of the list, go to the end
+ * of the list. */
+ if (distance >= len)
+ {
+ distance = len - 1;
+ }
+
+ bookmark = g_list_nth_data (list, distance);
+ location = nautilus_bookmark_get_location (bookmark);
+ old_location = nautilus_window_slot_get_location (self);
+ scroll_pos = nautilus_bookmark_get_scroll_pos (bookmark);
+
+ begin_location_change (self,
+ location, old_location,
+ NULL,
+ back ? NAUTILUS_LOCATION_CHANGE_BACK : NAUTILUS_LOCATION_CHANGE_FORWARD,
+ distance,
+ scroll_pos);
+}
+
+/* reload the contents of the window */
+static void
+nautilus_window_slot_force_reload (NautilusWindowSlot *self)
+{
+ GFile *location;
+ char *current_pos;
+ g_autolist (NautilusFile) selection = NULL;
+
+ g_assert (NAUTILUS_IS_WINDOW_SLOT (self));
+
+ location = nautilus_window_slot_get_location (self);
+ if (location == NULL)
+ {
+ return;
+ }
+
+ /* peek_slot_field (window, location) can be free'd during the processing
+ * of begin_location_change, so make a copy
+ */
+ g_object_ref (location);
+ current_pos = NULL;
+
+ if (self->new_content_view)
+ {
+ selection = nautilus_view_get_selection (self->content_view);
+
+ if (NAUTILUS_IS_FILES_VIEW (self->new_content_view))
+ {
+ current_pos = nautilus_files_view_get_first_visible_file (NAUTILUS_FILES_VIEW (self->content_view));
+ }
+ }
+ begin_location_change
+ (self, location, location, selection,
+ NAUTILUS_LOCATION_CHANGE_RELOAD, 0, current_pos);
+ g_free (current_pos);
+ g_object_unref (location);
+}
+
+void
+nautilus_window_slot_queue_reload (NautilusWindowSlot *self)
+{
+ g_assert (NAUTILUS_IS_WINDOW_SLOT (self));
+
+ if (nautilus_window_slot_get_location (self) == NULL)
+ {
+ return;
+ }
+
+ if (self->pending_location != NULL
+ || self->content_view == NULL
+ || nautilus_view_is_loading (self->content_view))
+ {
+ /* there is a reload in flight */
+ self->needs_reload = TRUE;
+ return;
+ }
+
+ nautilus_window_slot_force_reload (self);
+}
+
+static void
+nautilus_window_slot_clear_forward_list (NautilusWindowSlot *self)
+{
+ g_assert (NAUTILUS_IS_WINDOW_SLOT (self));
+
+ g_list_free_full (self->forward_list, g_object_unref);
+ self->forward_list = NULL;
+}
+
+static void
+nautilus_window_slot_clear_back_list (NautilusWindowSlot *self)
+{
+ g_assert (NAUTILUS_IS_WINDOW_SLOT (self));
+
+ g_list_free_full (self->back_list, g_object_unref);
+ self->back_list = NULL;
+}
+
+static void
+nautilus_window_slot_update_bookmark (NautilusWindowSlot *self,
+ NautilusFile *file)
+{
+ gboolean recreate;
+ GFile *new_location;
+ new_location = nautilus_file_get_location (file);
+
+ if (self->current_location_bookmark == NULL)
+ {
+ recreate = TRUE;
+ }
+ else
+ {
+ GFile *bookmark_location;
+ bookmark_location = nautilus_bookmark_get_location (self->current_location_bookmark);
+ recreate = !g_file_equal (bookmark_location, new_location);
+ g_object_unref (bookmark_location);
+ }
+
+ if (recreate)
+ {
+ char *display_name = NULL;
+
+ /* We've changed locations, must recreate bookmark for current location. */
+ g_clear_object (&self->last_location_bookmark);
+ self->last_location_bookmark = self->current_location_bookmark;
+
+ display_name = nautilus_file_get_display_name (file);
+ self->current_location_bookmark = nautilus_bookmark_new (new_location, display_name);
+ g_free (display_name);
+ }
+
+ g_object_unref (new_location);
+}
+
+static void
+check_bookmark_location_matches (NautilusBookmark *bookmark,
+ GFile *location)
+{
+ GFile *bookmark_location;
+ char *bookmark_uri, *uri;
+
+ bookmark_location = nautilus_bookmark_get_location (bookmark);
+ if (!g_file_equal (location, bookmark_location))
+ {
+ bookmark_uri = g_file_get_uri (bookmark_location);
+ uri = g_file_get_uri (location);
+ g_warning ("bookmark uri is %s, but expected %s", bookmark_uri, uri);
+ g_free (uri);
+ g_free (bookmark_uri);
+ }
+ g_object_unref (bookmark_location);
+}
+
+/* Debugging function used to verify that the last_location_bookmark
+ * is in the state we expect when we're about to use it to update the
+ * Back or Forward list.
+ */
+static void
+check_last_bookmark_location_matches_slot (NautilusWindowSlot *self)
+{
+ check_bookmark_location_matches (self->last_location_bookmark,
+ nautilus_window_slot_get_location (self));
+}
+
+static void
+handle_go_direction (NautilusWindowSlot *self,
+ GFile *location,
+ gboolean forward)
+{
+ GList **list_ptr, **other_list_ptr;
+ GList *list, *other_list, *link;
+ NautilusBookmark *bookmark;
+ gint i;
+ list_ptr = (forward) ? (&self->forward_list) : (&self->back_list);
+ other_list_ptr = (forward) ? (&self->back_list) : (&self->forward_list);
+ list = *list_ptr;
+ other_list = *other_list_ptr;
+
+ /* Move items from the list to the other list. */
+ g_assert (g_list_length (list) > self->location_change_distance);
+ check_bookmark_location_matches (g_list_nth_data (list, self->location_change_distance),
+ location);
+ g_assert (nautilus_window_slot_get_location (self) != NULL);
+
+ /* Move current location to list */
+ check_last_bookmark_location_matches_slot (self);
+
+ /* Use the first bookmark in the history list rather than creating a new one. */
+ other_list = g_list_prepend (other_list, self->last_location_bookmark);
+ g_object_ref (other_list->data);
+
+ /* Move extra links from the list to the other list */
+ for (i = 0; i < self->location_change_distance; ++i)
+ {
+ bookmark = NAUTILUS_BOOKMARK (list->data);
+ list = g_list_remove (list, bookmark);
+ other_list = g_list_prepend (other_list, bookmark);
+ }
+
+ /* One bookmark falls out of back/forward lists and becomes viewed location */
+ link = list;
+ list = g_list_remove_link (list, link);
+ g_object_unref (link->data);
+ g_list_free_1 (link);
+
+ *list_ptr = list;
+ *other_list_ptr = other_list;
+}
+
+static void
+handle_go_elsewhere (NautilusWindowSlot *self,
+ GFile *location)
+{
+ GFile *slot_location;
+ /* Clobber the entire forward list, and move displayed location to back list */
+ nautilus_window_slot_clear_forward_list (self);
+
+ slot_location = nautilus_window_slot_get_location (self);
+
+ if (slot_location != NULL)
+ {
+ /* If we're returning to the same uri somehow, don't put this uri on back list.
+ * This also avoids a problem where set_displayed_location
+ * didn't update last_location_bookmark since the uri didn't change.
+ */
+ if (!g_file_equal (slot_location, location))
+ {
+ /* Store bookmark for current location in back list, unless there is no current location */
+ check_last_bookmark_location_matches_slot (self);
+ /* Use the first bookmark in the history list rather than creating a new one. */
+ self->back_list = g_list_prepend (self->back_list,
+ self->last_location_bookmark);
+ g_object_ref (self->back_list->data);
+ }
+ }
+}
+
+static void
+update_history (NautilusWindowSlot *self,
+ NautilusLocationChangeType type,
+ GFile *new_location)
+{
+ switch (type)
+ {
+ case NAUTILUS_LOCATION_CHANGE_STANDARD:
+ {
+ handle_go_elsewhere (self, new_location);
+ return;
+ }
+
+ case NAUTILUS_LOCATION_CHANGE_RELOAD:
+ {
+ /* for reload there is no work to do */
+ return;
+ }
+
+ case NAUTILUS_LOCATION_CHANGE_BACK:
+ {
+ handle_go_direction (self, new_location, FALSE);
+ return;
+ }
+
+ case NAUTILUS_LOCATION_CHANGE_FORWARD:
+ {
+ handle_go_direction (self, new_location, TRUE);
+ return;
+ }
+ }
+ g_return_if_fail (FALSE);
+}
+
+typedef struct
+{
+ NautilusWindowSlot *slot;
+ GCancellable *cancellable;
+ GMount *mount;
+} FindMountData;
+
+static void
+nautilus_window_slot_show_x_content_bar (NautilusWindowSlot *self,
+ GMount *mount,
+ const char * const *x_content_types)
+{
+ GtkWidget *bar;
+
+ g_assert (NAUTILUS_IS_WINDOW_SLOT (self));
+
+ if (!should_handle_content_types (x_content_types))
+ {
+ return;
+ }
+
+ bar = nautilus_x_content_bar_new (mount, x_content_types);
+ gtk_widget_show (bar);
+ nautilus_window_slot_add_extra_location_widget (self, bar);
+}
+
+static void
+found_content_type_cb (const char **x_content_types,
+ gpointer user_data)
+{
+ NautilusWindowSlot *self;
+ FindMountData *data = user_data;
+ self = data->slot;
+ if (g_cancellable_is_cancelled (data->cancellable))
+ {
+ goto out;
+ }
+
+
+ if (x_content_types != NULL && x_content_types[0] != NULL)
+ {
+ nautilus_window_slot_show_x_content_bar (self, data->mount, (const char * const *) x_content_types);
+ }
+
+ self->find_mount_cancellable = NULL;
+
+out:
+ g_object_unref (data->mount);
+ g_object_unref (data->cancellable);
+ g_free (data);
+}
+
+static void
+found_mount_cb (GObject *source_object,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ FindMountData *data = user_data;
+ NautilusWindowSlot *self;
+ GMount *mount;
+ self = NAUTILUS_WINDOW_SLOT (data->slot);
+ if (g_cancellable_is_cancelled (data->cancellable))
+ {
+ goto out;
+ }
+
+ mount = g_file_find_enclosing_mount_finish (G_FILE (source_object),
+ res,
+ NULL);
+ if (mount != NULL)
+ {
+ data->mount = mount;
+ nautilus_get_x_content_types_for_mount_async (mount,
+ found_content_type_cb,
+ data->cancellable,
+ data);
+ return;
+ }
+
+ self->find_mount_cancellable = NULL;
+
+out:
+ g_object_unref (data->cancellable);
+ g_free (data);
+}
+
+static void
+nautilus_window_slot_show_special_location_bar (NautilusWindowSlot *self,
+ NautilusSpecialLocation special_location)
+{
+ GtkWidget *bar;
+
+ bar = nautilus_special_location_bar_new (special_location);
+ gtk_widget_show (bar);
+
+ nautilus_window_slot_add_extra_location_widget (self, bar);
+}
+
+static void
+nautilus_window_slot_update_for_new_location (NautilusWindowSlot *self)
+{
+ GFile *new_location;
+ NautilusFile *file;
+ new_location = self->pending_location;
+ self->pending_location = NULL;
+
+ file = nautilus_file_get (new_location);
+ nautilus_window_slot_update_bookmark (self, file);
+
+ update_history (self, self->location_change_type, new_location);
+
+ /* Create a NautilusFile for this location, so we can catch it
+ * if it goes away.
+ */
+ nautilus_window_slot_set_viewed_file (self, file);
+ self->viewed_file_seen = !nautilus_file_is_not_yet_confirmed (file);
+ self->viewed_file_in_trash = nautilus_file_is_in_trash (file);
+ nautilus_file_unref (file);
+
+ nautilus_window_slot_set_location (self, new_location);
+
+ /* Sync the actions for this new location. */
+ nautilus_window_slot_sync_actions (self);
+}
+
+static void
+view_started_loading (NautilusWindowSlot *self,
+ NautilusView *view)
+{
+ if (view == self->content_view)
+ {
+ nautilus_window_slot_set_allow_stop (self, TRUE);
+ }
+
+ /* Only grab focus if the menu isn't showing. Otherwise the menu disappears
+ * e.g. when the user toggles Show Hidden Files
+ */
+ if (!nautilus_toolbar_is_menu_visible (NAUTILUS_TOOLBAR (nautilus_window_get_toolbar (self->window))))
+ {
+ gtk_widget_grab_focus (GTK_WIDGET (self->window));
+ }
+
+ gtk_widget_show (GTK_WIDGET (self->window));
+
+ nautilus_window_slot_set_loading (self, TRUE);
+}
+
+static void
+view_ended_loading (NautilusWindowSlot *self,
+ NautilusView *view)
+{
+ if (view == self->content_view)
+ {
+ if (NAUTILUS_IS_FILES_VIEW (view) && self->pending_scroll_to != NULL)
+ {
+ nautilus_files_view_scroll_to_file (NAUTILUS_FILES_VIEW (self->content_view), self->pending_scroll_to);
+ }
+
+ end_location_change (self);
+ }
+
+ if (self->needs_reload)
+ {
+ nautilus_window_slot_queue_reload (self);
+ self->needs_reload = FALSE;
+ }
+
+ nautilus_window_slot_set_allow_stop (self, FALSE);
+
+ nautilus_window_slot_set_loading (self, FALSE);
+}
+
+static void
+view_is_loading_changed_cb (GObject *object,
+ GParamSpec *pspec,
+ NautilusWindowSlot *self)
+{
+ NautilusView *view;
+
+ view = NAUTILUS_VIEW (object);
+
+ nautilus_profile_start (NULL);
+
+ if (nautilus_view_is_loading (view))
+ {
+ view_started_loading (self, view);
+ }
+ else
+ {
+ view_ended_loading (self, view);
+ }
+
+ nautilus_profile_end (NULL);
+}
+
+static gboolean
+nautilus_file_is_public_share_folder (NautilusFile *file)
+{
+ if (nautilus_file_is_user_special_directory (file, G_USER_DIRECTORY_PUBLIC_SHARE))
+ {
+ return TRUE;
+ }
+ if (g_strcmp0 (g_get_home_dir (), g_get_user_special_dir (G_USER_DIRECTORY_PUBLIC_SHARE)))
+ {
+ /* In order to match the behavior of gnome-user-share the ~/Public folder
+ * is considered to be the public sharing folder when XDG_PUBLICSHARE_DIR
+ * is set to the home folder. */
+ g_autoptr (GFile) public_folder = g_file_new_build_filename (g_get_home_dir (), "Public", NULL);
+ g_autoptr (GFile) location = nautilus_file_get_location (file);
+
+ return g_file_equal (public_folder, location);
+ }
+ return FALSE;
+}
+
+static void
+nautilus_window_slot_setup_extra_location_widgets (NautilusWindowSlot *self)
+{
+ GFile *location;
+ FindMountData *data;
+ NautilusDirectory *directory;
+ NautilusFile *file;
+ GFile *scripts_file;
+ char *scripts_path;
+
+ scripts_path = nautilus_get_scripts_directory_path ();
+ location = nautilus_window_slot_get_current_location (self);
+
+ if (location == NULL)
+ {
+ return;
+ }
+
+ directory = nautilus_directory_get (location);
+
+ scripts_file = g_file_new_for_path (scripts_path);
+ g_free (scripts_path);
+
+ file = nautilus_file_get (location);
+
+ if (nautilus_should_use_templates_directory () &&
+ nautilus_file_is_user_special_directory (file, G_USER_DIRECTORY_TEMPLATES))
+ {
+ nautilus_window_slot_show_special_location_bar (self, NAUTILUS_SPECIAL_LOCATION_TEMPLATES);
+ }
+ else if (g_file_equal (location, scripts_file))
+ {
+ nautilus_window_slot_show_special_location_bar (self, NAUTILUS_SPECIAL_LOCATION_SCRIPTS);
+ }
+ else if (check_schema_available (FILE_SHARING_SCHEMA_ID) && nautilus_file_is_public_share_folder (file))
+ {
+ nautilus_window_slot_show_special_location_bar (self, NAUTILUS_SPECIAL_LOCATION_SHARING);
+ }
+ else if (nautilus_directory_is_in_trash (directory) &&
+ g_settings_get_boolean (gnome_privacy_preferences, "remove-old-trash-files"))
+ {
+ nautilus_window_slot_show_special_location_bar (self, NAUTILUS_SPECIAL_LOCATION_TRASH);
+ }
+
+ g_object_unref (scripts_file);
+ nautilus_file_unref (file);
+
+ /* need the mount to determine if we should put up the x-content cluebar */
+ if (self->find_mount_cancellable != NULL)
+ {
+ g_cancellable_cancel (self->find_mount_cancellable);
+ self->find_mount_cancellable = NULL;
+ }
+
+ data = g_new (FindMountData, 1);
+ data->slot = self;
+ data->cancellable = g_cancellable_new ();
+ data->mount = NULL;
+
+ self->find_mount_cancellable = data->cancellable;
+ g_file_find_enclosing_mount_async (location,
+ G_PRIORITY_DEFAULT,
+ data->cancellable,
+ found_mount_cb,
+ data);
+
+ nautilus_directory_unref (directory);
+}
+
+static void
+nautilus_window_slot_connect_new_content_view (NautilusWindowSlot *self)
+{
+ if (self->new_content_view)
+ {
+ g_signal_connect (self->new_content_view,
+ "notify::loading",
+ G_CALLBACK (view_is_loading_changed_cb),
+ self);
+ }
+}
+
+static void
+nautilus_window_slot_disconnect_content_view (NautilusWindowSlot *self)
+{
+ if (self->content_view)
+ {
+ /* disconnect old view */
+ g_signal_handlers_disconnect_by_func (self->content_view,
+ G_CALLBACK (view_is_loading_changed_cb),
+ self);
+ }
+}
+
+static void
+nautilus_window_slot_switch_new_content_view (NautilusWindowSlot *self)
+{
+ GtkWidget *widget;
+ gboolean reusing_view;
+ reusing_view = self->new_content_view &&
+ gtk_widget_get_parent (GTK_WIDGET (self->new_content_view)) != NULL;
+ /* We are either reusing the view, so new_content_view and content_view
+ * are the same, or the new_content_view is invalid */
+ if (self->new_content_view == NULL || reusing_view)
+ {
+ goto done;
+ }
+
+ if (self->content_view != NULL)
+ {
+ g_binding_unbind (self->searching_binding);
+ g_binding_unbind (self->selection_binding);
+ g_binding_unbind (self->extensions_background_menu_binding);
+ g_binding_unbind (self->templates_menu_binding);
+ widget = GTK_WIDGET (self->content_view);
+ gtk_box_remove (GTK_BOX (self), widget);
+ g_clear_object (&self->content_view);
+ }
+
+ if (self->new_content_view != NULL)
+ {
+ self->content_view = self->new_content_view;
+ self->new_content_view = NULL;
+
+ widget = GTK_WIDGET (self->content_view);
+ gtk_box_append (GTK_BOX (self), widget);
+ gtk_widget_set_vexpand (widget, TRUE);
+ gtk_widget_show (widget);
+ self->searching_binding = g_object_bind_property (self->content_view, "searching",
+ self, "searching",
+ G_BINDING_DEFAULT | G_BINDING_SYNC_CREATE);
+ self->selection_binding = g_object_bind_property (self->content_view, "selection",
+ self, "selection",
+ G_BINDING_DEFAULT | G_BINDING_SYNC_CREATE);
+ self->extensions_background_menu_binding = g_object_bind_property (self->content_view, "extensions-background-menu",
+ self, "extensions-background-menu",
+ G_BINDING_DEFAULT | G_BINDING_SYNC_CREATE);
+ self->templates_menu_binding = g_object_bind_property (self->content_view, "templates-menu",
+ self, "templates-menu",
+ G_BINDING_DEFAULT | G_BINDING_SYNC_CREATE);
+ g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_ICON_NAME]);
+ g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_TOOLBAR_MENU_SECTIONS]);
+ g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_EXTENSIONS_BACKGROUND_MENU]);
+ g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_TEMPLATES_MENU]);
+ g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_TOOLTIP]);
+ }
+
+done:
+ /* Clean up, so we don't confuse having a new_content_view available or
+ * just that we didn't care about it here */
+ self->new_content_view = NULL;
+}
+
+/* This is called when we have decided we can actually change to the new view/location situation. */
+static void
+change_view (NautilusWindowSlot *self)
+{
+ /* Switch to the new content view.
+ * Destroy the extra location widgets first, since they might hold
+ * a pointer to the old view, which will possibly be destroyed inside
+ * nautilus_window_slot_switch_new_content_view().
+ */
+ nautilus_window_slot_remove_extra_location_widgets (self);
+ nautilus_window_slot_switch_new_content_view (self);
+
+ if (self->pending_location != NULL)
+ {
+ /* Tell the window we are finished. */
+ nautilus_window_slot_update_for_new_location (self);
+ }
+
+ /* Now that we finished switching to the new location,
+ * add back the extra location widgets.
+ */
+ nautilus_window_slot_setup_extra_location_widgets (self);
+}
+
+static void
+nautilus_window_slot_dispose (GObject *object)
+{
+ NautilusWindowSlot *self;
+ self = NAUTILUS_WINDOW_SLOT (object);
+
+ g_signal_handlers_disconnect_by_data (nautilus_preferences, self);
+
+ nautilus_window_slot_clear_forward_list (self);
+ nautilus_window_slot_clear_back_list (self);
+
+ nautilus_window_slot_remove_extra_location_widgets (self);
+
+ g_clear_pointer (&self->searching_binding, g_binding_unbind);
+ g_clear_pointer (&self->selection_binding, g_binding_unbind);
+ g_clear_pointer (&self->extensions_background_menu_binding, g_binding_unbind);
+ g_clear_pointer (&self->templates_menu_binding, g_binding_unbind);
+
+ g_clear_object (&self->templates_menu);
+ g_clear_object (&self->extensions_background_menu);
+
+ if (self->content_view)
+ {
+ gtk_box_remove (GTK_BOX (self), GTK_WIDGET (self->content_view));
+ g_clear_object (&self->content_view);
+ }
+
+ if (self->new_content_view)
+ {
+ gtk_box_remove (GTK_BOX (self), GTK_WIDGET (self->new_content_view));
+ g_clear_object (&self->new_content_view);
+ }
+
+ nautilus_window_slot_set_viewed_file (self, NULL);
+
+ g_clear_object (&self->location);
+ g_clear_object (&self->pending_file_to_activate);
+ g_clear_pointer (&self->pending_selection, nautilus_file_list_free);
+
+ g_clear_object (&self->current_location_bookmark);
+ g_clear_object (&self->last_location_bookmark);
+ g_clear_object (&self->slot_action_group);
+ g_clear_object (&self->pending_search_query);
+
+ g_clear_pointer (&self->find_mount_cancellable, g_cancellable_cancel);
+
+ if (self->query_editor)
+ {
+ g_clear_object (&self->query_editor);
+ }
+
+ free_location_change (self);
+
+ G_OBJECT_CLASS (nautilus_window_slot_parent_class)->dispose (object);
+}
+
+static void
+nautilus_window_slot_finalize (GObject *object)
+{
+ NautilusWindowSlot *self;
+ self = NAUTILUS_WINDOW_SLOT (object);
+ g_clear_pointer (&self->title, g_free);
+
+ G_OBJECT_CLASS (nautilus_window_slot_parent_class)->finalize (object);
+}
+
+static gboolean
+nautilus_window_slot_grab_focus (GtkWidget *widget)
+{
+ NautilusWindowSlot *self;
+ self = NAUTILUS_WINDOW_SLOT (widget);
+
+ if (nautilus_window_slot_get_search_visible (self))
+ {
+ return gtk_widget_grab_focus (GTK_WIDGET (self->query_editor));
+ }
+ else if (self->content_view != NULL)
+ {
+ return gtk_widget_grab_focus (GTK_WIDGET (self->content_view));
+ }
+ else if (self->new_content_view != NULL)
+ {
+ return gtk_widget_grab_focus (GTK_WIDGET (self->new_content_view));
+ }
+
+ return GTK_WIDGET_CLASS (nautilus_window_slot_parent_class)->grab_focus (widget);
+}
+
+static void
+nautilus_window_slot_class_init (NautilusWindowSlotClass *klass)
+{
+ GObjectClass *oclass = G_OBJECT_CLASS (klass);
+ GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+
+ oclass->dispose = nautilus_window_slot_dispose;
+ oclass->finalize = nautilus_window_slot_finalize;
+ oclass->constructed = nautilus_window_slot_constructed;
+ oclass->set_property = nautilus_window_slot_set_property;
+ oclass->get_property = nautilus_window_slot_get_property;
+
+ widget_class->grab_focus = nautilus_window_slot_grab_focus;
+
+ properties[PROP_ACTIVE] =
+ g_param_spec_boolean ("active",
+ "Whether the slot is active",
+ "Whether the slot is the active slot of the window",
+ FALSE,
+ G_PARAM_READWRITE);
+
+ properties[PROP_LOADING] =
+ g_param_spec_boolean ("loading",
+ "Whether the slot loading",
+ "Whether the slot is loading a new location",
+ FALSE,
+ G_PARAM_READABLE);
+
+ properties[PROP_SEARCHING] =
+ g_param_spec_boolean ("searching",
+ "Whether the current view of the slot is searching",
+ "Whether the current view of the slot is searching. Proxy property from the view",
+ FALSE,
+ G_PARAM_READWRITE);
+
+ properties[PROP_SELECTION] =
+ g_param_spec_pointer ("selection",
+ "Selection of the current view of the slot",
+ "The selection of the current view of the slot. Proxy property from the view",
+ G_PARAM_READWRITE);
+
+ properties[PROP_WINDOW] =
+ g_param_spec_object ("window",
+ "The NautilusWindow",
+ "The NautilusWindow this slot is part of",
+ NAUTILUS_TYPE_WINDOW,
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT);
+
+ properties[PROP_ICON_NAME] =
+ g_param_spec_string ("icon-name",
+ "Icon that represents the slot",
+ "The icon that represents the slot",
+ NULL,
+ G_PARAM_READABLE);
+
+ properties[PROP_TOOLBAR_MENU_SECTIONS] =
+ g_param_spec_pointer ("toolbar-menu-sections",
+ "Menu sections for the toolbar menu",
+ "The menu sections to add to the toolbar menu for this slot",
+ G_PARAM_READABLE);
+
+ properties[PROP_EXTENSIONS_BACKGROUND_MENU] =
+ g_param_spec_object ("extensions-background-menu",
+ "Background menu of extensions",
+ "Proxy property from the view for the background menu for extensions",
+ G_TYPE_MENU_MODEL,
+ G_PARAM_READWRITE);
+
+ properties[PROP_TEMPLATES_MENU] =
+ g_param_spec_object ("templates-menu",
+ "Templates menu",
+ "Proxy property from the view for the templates menu",
+ G_TYPE_MENU_MODEL,
+ G_PARAM_READWRITE);
+
+ properties[PROP_LOCATION] =
+ g_param_spec_object ("location",
+ "Current location visible on the slot",
+ "Either the location that is used currently, or the pending location. Clients will see the same value they set, and therefore it will be cosistent from clients point of view.",
+ G_TYPE_FILE,
+ G_PARAM_READWRITE);
+
+ properties[PROP_TOOLTIP] =
+ g_param_spec_string ("tooltip",
+ "Tooltip that represents the slot",
+ "The tooltip that represents the slot",
+ NULL,
+ G_PARAM_READWRITE);
+
+ properties[PROP_ALLOW_STOP] =
+ g_param_spec_boolean ("allow-stop", "", "",
+ FALSE,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+
+ properties[PROP_TITLE] =
+ g_param_spec_string ("title", "", "",
+ NULL,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+
+ g_object_class_install_properties (oclass, NUM_PROPERTIES, properties);
+}
+
+GFile *
+nautilus_window_slot_get_location (NautilusWindowSlot *self)
+{
+ g_return_val_if_fail (NAUTILUS_IS_WINDOW_SLOT (self), NULL);
+
+ return self->location;
+}
+
+GFile *
+nautilus_window_slot_get_pending_location (NautilusWindowSlot *self)
+{
+ g_return_val_if_fail (NAUTILUS_IS_WINDOW_SLOT (self), NULL);
+
+ return self->pending_location;
+}
+
+const gchar *
+nautilus_window_slot_get_title (NautilusWindowSlot *self)
+{
+ return self->title;
+}
+
+char *
+nautilus_window_slot_get_location_uri (NautilusWindowSlot *self)
+{
+ g_assert (NAUTILUS_IS_WINDOW_SLOT (self));
+
+ if (self->location)
+ {
+ return g_file_get_uri (self->location);
+ }
+ return NULL;
+}
+
+NautilusWindow *
+nautilus_window_slot_get_window (NautilusWindowSlot *self)
+{
+ g_assert (NAUTILUS_IS_WINDOW_SLOT (self));
+
+ return self->window;
+}
+
+void
+nautilus_window_slot_set_window (NautilusWindowSlot *self,
+ NautilusWindow *window)
+{
+ g_assert (NAUTILUS_IS_WINDOW_SLOT (self));
+ g_assert (NAUTILUS_IS_WINDOW (window));
+
+ if (self->window != window)
+ {
+ self->window = window;
+ g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_WINDOW]);
+ }
+}
+
+/* nautilus_window_slot_update_title:
+ *
+ * Re-calculate the slot title.
+ * Called when the location or view has changed.
+ * @slot: The NautilusWindowSlot in question.
+ *
+ */
+void
+nautilus_window_slot_update_title (NautilusWindowSlot *self)
+{
+ NautilusWindow *window;
+ g_autofree char *title = NULL;
+ title = nautilus_compute_title_for_location (self->location);
+ window = nautilus_window_slot_get_window (self);
+
+ if (g_strcmp0 (title, self->title) != 0)
+ {
+ g_free (self->title);
+ self->title = g_steal_pointer (&title);
+
+ g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_TITLE]);
+
+ nautilus_window_sync_title (window, self);
+ }
+}
+
+gboolean
+nautilus_window_slot_get_allow_stop (NautilusWindowSlot *self)
+{
+ return self->allow_stop;
+}
+
+void
+nautilus_window_slot_set_allow_stop (NautilusWindowSlot *self,
+ gboolean allow)
+{
+ NautilusWindow *window;
+ g_assert (NAUTILUS_IS_WINDOW_SLOT (self));
+
+ self->allow_stop = allow;
+
+ window = nautilus_window_slot_get_window (self);
+ nautilus_window_sync_allow_stop (window, self);
+
+ g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_ALLOW_STOP]);
+}
+
+void
+nautilus_window_slot_stop_loading (NautilusWindowSlot *self)
+{
+ GFile *location;
+ NautilusDirectory *directory;
+ location = nautilus_window_slot_get_location (self);
+ directory = nautilus_directory_get (self->location);
+
+ if (NAUTILUS_IS_FILES_VIEW (self->content_view))
+ {
+ nautilus_files_view_stop_loading (NAUTILUS_FILES_VIEW (self->content_view));
+ }
+
+ nautilus_directory_unref (directory);
+
+ if (self->pending_location != NULL &&
+ location != NULL &&
+ self->content_view != NULL &&
+ NAUTILUS_IS_FILES_VIEW (self->content_view))
+ {
+ /* No need to tell the new view - either it is the
+ * same as the old view, in which case it will already
+ * be told, or it is the very pending change we wish
+ * to cancel.
+ */
+ g_autolist (NautilusFile) selection = NULL;
+
+ selection = nautilus_view_get_selection (self->content_view);
+ load_new_location (self,
+ location,
+ selection,
+ NULL,
+ TRUE,
+ FALSE);
+ }
+
+ end_location_change (self);
+
+ if (self->new_content_view)
+ {
+ g_object_unref (self->new_content_view);
+ self->new_content_view = NULL;
+ }
+}
+
+NautilusView *
+nautilus_window_slot_get_current_view (NautilusWindowSlot *self)
+{
+ if (self->content_view != NULL)
+ {
+ return self->content_view;
+ }
+ else if (self->new_content_view)
+ {
+ return self->new_content_view;
+ }
+
+ return NULL;
+}
+
+NautilusBookmark *
+nautilus_window_slot_get_bookmark (NautilusWindowSlot *self)
+{
+ return self->current_location_bookmark;
+}
+
+GList *
+nautilus_window_slot_get_back_history (NautilusWindowSlot *self)
+{
+ return self->back_list;
+}
+
+GList *
+nautilus_window_slot_get_forward_history (NautilusWindowSlot *self)
+{
+ return self->forward_list;
+}
+
+NautilusWindowSlot *
+nautilus_window_slot_new (NautilusWindow *window)
+{
+ return g_object_new (NAUTILUS_TYPE_WINDOW_SLOT,
+ "window", window,
+ NULL);
+}
+
+const gchar *
+nautilus_window_slot_get_icon_name (NautilusWindowSlot *self)
+{
+ guint current_view_id;
+ g_return_val_if_fail (NAUTILUS_IS_WINDOW_SLOT (self), NULL);
+
+ if (self->content_view == NULL)
+ {
+ return "";
+ }
+
+ current_view_id = nautilus_view_get_view_id (NAUTILUS_VIEW (self->content_view));
+ switch (current_view_id)
+ {
+ case NAUTILUS_VIEW_LIST_ID:
+ {
+ return nautilus_view_get_icon_name (NAUTILUS_VIEW_GRID_ID);
+ }
+ break;
+
+ case NAUTILUS_VIEW_GRID_ID:
+ {
+ return nautilus_view_get_icon_name (NAUTILUS_VIEW_LIST_ID);
+ }
+ break;
+
+ case NAUTILUS_VIEW_OTHER_LOCATIONS_ID:
+ {
+ return nautilus_view_get_icon_name (NAUTILUS_VIEW_OTHER_LOCATIONS_ID);
+ }
+ break;
+
+ default:
+ {
+ return NULL;
+ }
+ }
+}
+
+const gchar *
+nautilus_window_slot_get_tooltip (NautilusWindowSlot *self)
+{
+ guint current_view_id;
+ g_return_val_if_fail (NAUTILUS_IS_WINDOW_SLOT (self), NULL);
+
+ if (self->content_view == NULL)
+ {
+ return NULL;
+ }
+
+ current_view_id = nautilus_view_get_view_id (NAUTILUS_VIEW (self->content_view));
+ switch (current_view_id)
+ {
+ case NAUTILUS_VIEW_LIST_ID:
+ {
+ return nautilus_view_get_tooltip (NAUTILUS_VIEW_GRID_ID);
+ }
+ break;
+
+ case NAUTILUS_VIEW_GRID_ID:
+ {
+ return nautilus_view_get_tooltip (NAUTILUS_VIEW_LIST_ID);
+ }
+ break;
+
+ case NAUTILUS_VIEW_OTHER_LOCATIONS_ID:
+ {
+ return nautilus_view_get_tooltip (NAUTILUS_VIEW_OTHER_LOCATIONS_ID);
+ }
+ break;
+
+ default:
+ {
+ return NULL;
+ }
+ }
+}
+
+NautilusToolbarMenuSections *
+nautilus_window_slot_get_toolbar_menu_sections (NautilusWindowSlot *self)
+{
+ NautilusView *view;
+
+ g_return_val_if_fail (NAUTILUS_IS_WINDOW_SLOT (self), NULL);
+
+ view = nautilus_window_slot_get_current_view (self);
+
+ return view ? nautilus_view_get_toolbar_menu_sections (view) : NULL;
+}
+
+gboolean
+nautilus_window_slot_get_active (NautilusWindowSlot *self)
+{
+ g_return_val_if_fail (NAUTILUS_IS_WINDOW_SLOT (self), FALSE);
+
+ return self->active;
+}
+
+void
+nautilus_window_slot_set_active (NautilusWindowSlot *self,
+ gboolean active)
+{
+ NautilusWindow *window;
+
+ g_return_if_fail (NAUTILUS_IS_WINDOW_SLOT (self));
+
+ if (self->active != active)
+ {
+ self->active = active;
+
+ if (active)
+ {
+ AdwTabView *tab_view;
+ AdwTabPage *page;
+
+ window = self->window;
+
+ tab_view = nautilus_window_get_tab_view (window);
+ page = adw_tab_view_get_page (tab_view, GTK_WIDGET (self));
+
+ adw_tab_view_set_selected_page (tab_view, page);
+
+ /* sync window to new slot */
+ nautilus_window_sync_allow_stop (window, self);
+ nautilus_window_sync_title (window, self);
+ nautilus_window_sync_location_widgets (window);
+ nautilus_window_slot_sync_actions (self);
+
+ gtk_widget_insert_action_group (GTK_WIDGET (window), "slot", self->slot_action_group);
+ }
+ else
+ {
+ window = nautilus_window_slot_get_window (self);
+ g_assert (self == nautilus_window_get_active_slot (window));
+
+ gtk_widget_insert_action_group (GTK_WIDGET (window), "slot", NULL);
+ }
+
+ g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_ACTIVE]);
+ }
+}
+
+static void
+nautilus_window_slot_set_loading (NautilusWindowSlot *self,
+ gboolean loading)
+{
+ g_return_if_fail (NAUTILUS_IS_WINDOW_SLOT (self));
+
+ self->loading = loading;
+
+ g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_LOADING]);
+}
+
+gboolean
+nautilus_window_slot_get_loading (NautilusWindowSlot *self)
+{
+ g_return_val_if_fail (NAUTILUS_IS_WINDOW_SLOT (self), FALSE);
+
+ return self->loading;
+}
+
+NautilusQueryEditor *
+nautilus_window_slot_get_query_editor (NautilusWindowSlot *self)
+{
+ g_return_val_if_fail (NAUTILUS_IS_WINDOW_SLOT (self), NULL);
+
+ return self->query_editor;
+}
diff --git a/src/nautilus-window-slot.h b/src/nautilus-window-slot.h
new file mode 100644
index 0000000..364c637
--- /dev/null
+++ b/src/nautilus-window-slot.h
@@ -0,0 +1,120 @@
+/*
+ nautilus-window-slot.h: Nautilus window slot
+
+ Copyright (C) 2008 Free Software Foundation, 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/>.
+
+ Author: Christian Neumair <cneumair@gnome.org>
+*/
+
+#pragma once
+
+#include <gdk/gdk.h>
+#include <gio/gio.h>
+#include <gtk/gtk.h>
+
+#include "nautilus-types.h"
+
+typedef enum {
+ NAUTILUS_LOCATION_CHANGE_STANDARD,
+ NAUTILUS_LOCATION_CHANGE_BACK,
+ NAUTILUS_LOCATION_CHANGE_FORWARD,
+ NAUTILUS_LOCATION_CHANGE_RELOAD
+} NautilusLocationChangeType;
+
+#define NAUTILUS_TYPE_WINDOW_SLOT (nautilus_window_slot_get_type ())
+G_DECLARE_FINAL_TYPE (NautilusWindowSlot, nautilus_window_slot, NAUTILUS, WINDOW_SLOT, GtkBox)
+
+typedef struct
+{
+ NautilusFile *file;
+ gint view_before_search;
+ GList *back_list;
+ GList *forward_list;
+ NautilusBookmark *current_location_bookmark;
+} NautilusNavigationState;
+
+NautilusWindowSlot * nautilus_window_slot_new (NautilusWindow *window);
+
+NautilusWindow * nautilus_window_slot_get_window (NautilusWindowSlot *slot);
+void nautilus_window_slot_set_window (NautilusWindowSlot *slot,
+ NautilusWindow *window);
+
+void nautilus_window_slot_open_location_full (NautilusWindowSlot *slot,
+ GFile *location,
+ NautilusOpenFlags flags,
+ GList *new_selection);
+
+GFile * nautilus_window_slot_get_location (NautilusWindowSlot *slot);
+GFile * nautilus_window_slot_get_pending_location (NautilusWindowSlot *slot);
+
+NautilusBookmark *nautilus_window_slot_get_bookmark (NautilusWindowSlot *slot);
+
+GList * nautilus_window_slot_get_back_history (NautilusWindowSlot *slot);
+GList * nautilus_window_slot_get_forward_history (NautilusWindowSlot *slot);
+
+gboolean nautilus_window_slot_get_allow_stop (NautilusWindowSlot *slot);
+void nautilus_window_slot_set_allow_stop (NautilusWindowSlot *slot,
+ gboolean allow_stop);
+void nautilus_window_slot_stop_loading (NautilusWindowSlot *slot);
+
+const gchar *nautilus_window_slot_get_title (NautilusWindowSlot *slot);
+void nautilus_window_slot_update_title (NautilusWindowSlot *slot);
+
+gboolean nautilus_window_slot_handle_event (NautilusWindowSlot *slot,
+ GtkEventControllerKey *controller,
+ guint keyval,
+ GdkModifierType state);
+
+void nautilus_window_slot_queue_reload (NautilusWindowSlot *slot);
+
+const gchar* nautilus_window_slot_get_icon_name (NautilusWindowSlot *slot);
+
+const gchar* nautilus_window_slot_get_tooltip (NautilusWindowSlot *slot);
+
+NautilusToolbarMenuSections * nautilus_window_slot_get_toolbar_menu_sections (NautilusWindowSlot *slot);
+
+GMenuModel* nautilus_window_slot_get_templates_menu (NautilusWindowSlot *self);
+
+GMenuModel* nautilus_window_slot_get_extensions_background_menu (NautilusWindowSlot *self);
+
+gboolean nautilus_window_slot_get_active (NautilusWindowSlot *slot);
+
+void nautilus_window_slot_set_active (NautilusWindowSlot *slot,
+ gboolean active);
+gboolean nautilus_window_slot_get_loading (NautilusWindowSlot *slot);
+
+gboolean nautilus_window_slot_get_searching (NautilusWindowSlot *slot);
+
+GList* nautilus_window_slot_get_selection (NautilusWindowSlot *slot);
+
+void nautilus_window_slot_search (NautilusWindowSlot *slot,
+ NautilusQuery *query);
+
+void nautilus_window_slot_restore_navigation_state (NautilusWindowSlot *self,
+ NautilusNavigationState *data);
+
+NautilusNavigationState* nautilus_window_slot_get_navigation_state (NautilusWindowSlot *self);
+
+NautilusQueryEditor *nautilus_window_slot_get_query_editor (NautilusWindowSlot *self);
+
+/* Only used by slot-dnd */
+NautilusView* nautilus_window_slot_get_current_view (NautilusWindowSlot *slot);
+
+void nautilus_window_slot_back_or_forward (NautilusWindowSlot *slot,
+ gboolean back,
+ guint distance);
+
+void free_navigation_state (gpointer data);
diff --git a/src/nautilus-window.c b/src/nautilus-window.c
new file mode 100644
index 0000000..0b966c0
--- /dev/null
+++ b/src/nautilus-window.c
@@ -0,0 +1,2328 @@
+/*
+ * Nautilus
+ *
+ * Copyright (C) 1999, 2000, 2004 Red Hat, Inc.
+ * Copyright (C) 1999, 2000, 2001 Eazel, Inc.
+ *
+ * Nautilus is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * Nautilus is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Elliot Lee <sopwith@redhat.com>
+ * John Sullivan <sullivan@eazel.com>
+ * Alexander Larsson <alexl@redhat.com>
+ */
+
+/* nautilus-window.c: Implementation of the main window object */
+
+#include "nautilus-window.h"
+
+#include <eel/eel-debug.h>
+#include <eel/eel-vfs-extensions.h>
+#include <gdk-pixbuf/gdk-pixbuf.h>
+#include <gdk/gdkkeysyms.h>
+#include <glib/gi18n.h>
+#include <gtk/gtk.h>
+#include <math.h>
+#include <sys/time.h>
+
+#ifdef GDK_WINDOWING_WAYLAND
+#include <gdk/wayland/gdkwayland.h>
+#endif
+
+#ifdef GDK_WINDOWING_X11
+#include <gdk/x11/gdkx.h>
+#endif
+
+#define DEBUG_FLAG NAUTILUS_DEBUG_WINDOW
+#include "nautilus-debug.h"
+
+#include "gtk/nautilusgtkplacessidebarprivate.h"
+
+#include "nautilus-application.h"
+#include "nautilus-bookmark-list.h"
+#include "nautilus-clipboard.h"
+#include "nautilus-dnd.h"
+#include "nautilus-enums.h"
+#include "nautilus-file-operations.h"
+#include "nautilus-file-undo-manager.h"
+#include "nautilus-file-utilities.h"
+#include "nautilus-global-preferences.h"
+#include "nautilus-location-entry.h"
+#include "nautilus-metadata.h"
+#include "nautilus-mime-actions.h"
+#include "nautilus-module.h"
+#include "nautilus-pathbar.h"
+#include "nautilus-profile.h"
+#include "nautilus-signaller.h"
+#include "nautilus-toolbar.h"
+#include "nautilus-trash-monitor.h"
+#include "nautilus-ui-utilities.h"
+#include "nautilus-window-slot.h"
+
+/* Forward and back buttons on the mouse */
+static gboolean mouse_extra_buttons = TRUE;
+static int mouse_forward_button = 9;
+static int mouse_back_button = 8;
+
+static void mouse_back_button_changed (gpointer callback_data);
+static void mouse_forward_button_changed (gpointer callback_data);
+static void use_extra_mouse_buttons_changed (gpointer callback_data);
+static void nautilus_window_initialize_actions (NautilusWindow *window);
+static GtkWidget *nautilus_window_ensure_location_entry (NautilusWindow *window);
+static void nautilus_window_back_or_forward (NautilusWindow *window,
+ gboolean back,
+ guint distance);
+
+/* Sanity check: highest mouse button value I could find was 14. 5 is our
+ * lower threshold (well-documented to be the one of the button events for the
+ * scrollwheel), so it's hardcoded in the functions below. However, if you have
+ * a button that registers higher and want to map it, file a bug and
+ * we'll move the bar. Makes you wonder why the X guys don't have
+ * defined values for these like the XKB stuff, huh?
+ */
+#define UPPER_MOUSE_LIMIT 14
+
+struct _NautilusWindow
+{
+ AdwApplicationWindow parent_instance;
+
+ AdwTabView *tab_view;
+ AdwTabPage *menu_page;
+
+ GList *slots;
+ NautilusWindowSlot *active_slot; /* weak reference */
+
+ GtkWidget *content_flap;
+
+ /* Side Pane */
+ GtkWidget *places_sidebar; /* the actual GtkPlacesSidebar */
+ GVolume *selected_volume; /* the selected volume in the sidebar popup callback */
+ GFile *selected_file; /* the selected file in the sidebar popup callback */
+
+ /* Notifications */
+ AdwToastOverlay *toast_overlay;
+
+ /* Toolbar */
+ GtkWidget *toolbar;
+ gboolean temporary_navigation_bar;
+
+ /* focus widget before the location bar has been shown temporarily */
+ GtkWidget *last_focus_widget;
+
+ /* Handle when exported */
+ gchar *export_handle;
+
+ guint sidebar_width_handler_id;
+ gulong bookmarks_id;
+
+ GQueue *tab_data_queue;
+
+ /* Pad controller which holds a reference to the window. Kept around to
+ * break reference-counting cycles during finalization. */
+ GtkPadController *pad_controller;
+};
+
+enum
+{
+ SLOT_ADDED,
+ SLOT_REMOVED,
+ LAST_SIGNAL
+};
+
+static guint signals[LAST_SIGNAL] = { 0 };
+
+G_DEFINE_TYPE (NautilusWindow, nautilus_window, ADW_TYPE_APPLICATION_WINDOW);
+
+enum
+{
+ PROP_0,
+ PROP_ACTIVE_SLOT,
+ N_PROPS
+};
+
+static GParamSpec *properties[N_PROPS];
+
+static const GtkPadActionEntry pad_actions[] =
+{
+ { GTK_PAD_ACTION_BUTTON, 0, -1, N_("Parent folder"), "up" },
+ { GTK_PAD_ACTION_BUTTON, 1, -1, N_("Home"), "go-home" },
+ { GTK_PAD_ACTION_BUTTON, 2, -1, N_("New tab"), "new-tab" },
+ { GTK_PAD_ACTION_BUTTON, 3, -1, N_("Close current view"), "close-current-view" },
+ { GTK_PAD_ACTION_BUTTON, 4, -1, N_("Back"), "back" },
+ { GTK_PAD_ACTION_BUTTON, 5, -1, N_("Forward"), "forward" },
+};
+
+static void
+action_close_current_view (GSimpleAction *action,
+ GVariant *state,
+ gpointer user_data)
+{
+ NautilusWindow *window = user_data;
+ AdwTabPage *page = window->menu_page;
+
+ if (adw_tab_view_get_n_pages (window->tab_view) <= 1)
+ {
+ nautilus_window_close (window);
+ return;
+ }
+
+ if (page == NULL)
+ {
+ page = adw_tab_view_get_selected_page (window->tab_view);
+ }
+
+ adw_tab_view_close_page (window->tab_view, page);
+}
+
+static void
+action_go_home (GSimpleAction *action,
+ GVariant *state,
+ gpointer user_data)
+{
+ NautilusWindow *window;
+ GFile *home;
+
+ window = NAUTILUS_WINDOW (user_data);
+ home = g_file_new_for_path (g_get_home_dir ());
+
+ nautilus_window_open_location_full (window, home, 0, NULL, NULL);
+
+ g_object_unref (home);
+}
+
+static void
+action_go_starred (GSimpleAction *action,
+ GVariant *state,
+ gpointer user_data)
+{
+ NautilusWindow *window;
+ g_autoptr (GFile) starred = NULL;
+
+ window = NAUTILUS_WINDOW (user_data);
+ starred = g_file_new_for_uri ("starred:///");
+
+ nautilus_window_open_location_full (window, starred, 0, NULL, NULL);
+}
+
+static void
+action_reload (GSimpleAction *action,
+ GVariant *state,
+ gpointer user_data)
+{
+ NautilusWindowSlot *slot;
+
+ slot = nautilus_window_get_active_slot (NAUTILUS_WINDOW (user_data));
+ nautilus_window_slot_queue_reload (slot);
+}
+
+static void
+action_stop (GSimpleAction *action,
+ GVariant *state,
+ gpointer user_data)
+{
+ NautilusWindow *window;
+ NautilusWindowSlot *slot;
+
+ window = NAUTILUS_WINDOW (user_data);
+ slot = nautilus_window_get_active_slot (window);
+
+ nautilus_window_slot_stop_loading (slot);
+}
+
+static void
+action_up (GSimpleAction *action,
+ GVariant *state,
+ gpointer user_data)
+{
+ NautilusWindowSlot *slot;
+ GFile *parent, *location;
+
+ slot = nautilus_window_get_active_slot (NAUTILUS_WINDOW (user_data));
+ location = nautilus_window_slot_get_location (slot);
+
+ if (location != NULL)
+ {
+ parent = g_file_get_parent (location);
+ if (parent != NULL)
+ {
+ nautilus_window_open_location_full (NAUTILUS_WINDOW (user_data),
+ parent,
+ 0,
+ NULL, NULL);
+ }
+
+ g_clear_object (&parent);
+ }
+}
+
+static void
+action_back (GSimpleAction *action,
+ GVariant *state,
+ gpointer user_data)
+{
+ nautilus_window_back_or_forward (NAUTILUS_WINDOW (user_data), TRUE, 0);
+}
+
+static void
+action_forward (GSimpleAction *action,
+ GVariant *state,
+ gpointer user_data)
+{
+ nautilus_window_back_or_forward (NAUTILUS_WINDOW (user_data), FALSE, 0);
+}
+
+static void
+action_back_n (GSimpleAction *action,
+ GVariant *parameter,
+ gpointer user_data)
+{
+ nautilus_window_back_or_forward (NAUTILUS_WINDOW (user_data),
+ TRUE,
+ g_variant_get_uint32 (parameter));
+}
+
+static void
+action_forward_n (GSimpleAction *action,
+ GVariant *parameter,
+ gpointer user_data)
+{
+ nautilus_window_back_or_forward (NAUTILUS_WINDOW (user_data),
+ FALSE,
+ g_variant_get_uint32 (parameter));
+}
+
+static void
+action_bookmark_current_location (GSimpleAction *action,
+ GVariant *state,
+ gpointer user_data)
+{
+ NautilusWindow *window = user_data;
+ NautilusApplication *app = NAUTILUS_APPLICATION (g_application_get_default ());
+ NautilusWindowSlot *slot;
+
+ slot = nautilus_window_get_active_slot (window);
+ nautilus_bookmark_list_append (nautilus_application_get_bookmarks (app),
+ nautilus_window_slot_get_bookmark (slot));
+}
+
+static void
+action_new_tab (GSimpleAction *action,
+ GVariant *state,
+ gpointer user_data)
+{
+ nautilus_window_new_tab (NAUTILUS_WINDOW (user_data));
+}
+
+static void
+action_enter_location (GSimpleAction *action,
+ GVariant *state,
+ gpointer user_data)
+{
+ NautilusWindow *window = user_data;
+
+ nautilus_window_ensure_location_entry (window);
+}
+
+static void
+action_tab_move_left (GSimpleAction *action,
+ GVariant *state,
+ gpointer user_data)
+{
+ NautilusWindow *window = user_data;
+ AdwTabPage *page = window->menu_page;
+
+ if (page == NULL)
+ {
+ page = adw_tab_view_get_selected_page (window->tab_view);
+ }
+
+ adw_tab_view_reorder_backward (window->tab_view, page);
+}
+
+static void
+action_tab_move_right (GSimpleAction *action,
+ GVariant *state,
+ gpointer user_data)
+{
+ NautilusWindow *window = user_data;
+ AdwTabPage *page = window->menu_page;
+
+ if (page == NULL)
+ {
+ page = adw_tab_view_get_selected_page (window->tab_view);
+ }
+
+ adw_tab_view_reorder_forward (window->tab_view, page);
+}
+
+static void
+action_go_to_tab (GSimpleAction *action,
+ GVariant *value,
+ gpointer user_data)
+{
+ NautilusWindow *window = NAUTILUS_WINDOW (user_data);
+ gint16 num;
+
+ num = g_variant_get_int32 (value);
+ if (num < adw_tab_view_get_n_pages (window->tab_view))
+ {
+ AdwTabPage *page = adw_tab_view_get_nth_page (window->tab_view, num);
+
+ adw_tab_view_set_selected_page (window->tab_view, page);
+ }
+}
+
+static void
+prompt_for_location (NautilusWindow *window,
+ const char *path)
+{
+ GtkWidget *entry;
+
+ entry = nautilus_window_ensure_location_entry (window);
+ nautilus_location_entry_set_special_text (NAUTILUS_LOCATION_ENTRY (entry),
+ path);
+ gtk_editable_set_position (GTK_EDITABLE (entry), -1);
+}
+
+static void
+action_prompt_for_location_root (GSimpleAction *action,
+ GVariant *state,
+ gpointer user_data)
+{
+ prompt_for_location (NAUTILUS_WINDOW (user_data), "/");
+}
+
+static void
+action_prompt_for_location_home (GSimpleAction *action,
+ GVariant *state,
+ gpointer user_data)
+{
+ prompt_for_location (NAUTILUS_WINDOW (user_data), "~");
+}
+
+static void
+action_redo (GSimpleAction *action,
+ GVariant *state,
+ gpointer user_data)
+{
+ NautilusWindow *window = user_data;
+
+ nautilus_file_undo_manager_redo (GTK_WINDOW (window), NULL);
+}
+
+static void
+action_undo (GSimpleAction *action,
+ GVariant *state,
+ gpointer user_data)
+{
+ NautilusWindow *window = user_data;
+
+ nautilus_file_undo_manager_undo (GTK_WINDOW (window), NULL);
+}
+
+static void
+action_toggle_state_view_button (GSimpleAction *action,
+ GVariant *state,
+ gpointer user_data)
+{
+ GVariant *current_state;
+
+ current_state = g_action_get_state (G_ACTION (action));
+ g_action_change_state (G_ACTION (action),
+ g_variant_new_boolean (!g_variant_get_boolean (current_state)));
+ g_variant_unref (current_state);
+}
+
+static void
+action_show_current_location_menu (GSimpleAction *action,
+ GVariant *state,
+ gpointer user_data)
+{
+ NautilusWindow *window = user_data;
+ GtkWidget *path_bar;
+
+ path_bar = nautilus_toolbar_get_path_bar (NAUTILUS_TOOLBAR (window->toolbar));
+
+ nautilus_path_bar_show_current_location_menu (NAUTILUS_PATH_BAR (path_bar));
+}
+
+static void
+action_open_location (GSimpleAction *action,
+ GVariant *state,
+ gpointer user_data)
+{
+ NautilusWindow *window = NAUTILUS_WINDOW (user_data);
+ g_autoptr (GFile) folder_to_open = NULL;
+
+ folder_to_open = g_file_new_for_uri (g_variant_get_string (state, NULL));
+
+ nautilus_window_open_location_full (window, folder_to_open, 0, NULL, NULL);
+}
+
+static void
+on_location_changed (NautilusWindow *window)
+{
+ nautilus_gtk_places_sidebar_set_location (NAUTILUS_GTK_PLACES_SIDEBAR (window->places_sidebar),
+ nautilus_window_slot_get_location (nautilus_window_get_active_slot (window)));
+}
+
+static void
+on_slot_location_changed (NautilusWindowSlot *slot,
+ GParamSpec *pspec,
+ NautilusWindow *window)
+{
+ if (nautilus_window_get_active_slot (window) == slot)
+ {
+ on_location_changed (window);
+ }
+}
+
+static void
+tab_view_setup_menu_cb (AdwTabView *tab_view,
+ AdwTabPage *page,
+ NautilusWindow *window)
+{
+ GAction *move_tab_left_action;
+ GAction *move_tab_right_action;
+ int position, n_pages;
+
+ if (page != NULL)
+ {
+ position = adw_tab_view_get_page_position (tab_view, page);
+ n_pages = adw_tab_view_get_n_pages (tab_view);
+ }
+
+ move_tab_left_action = g_action_map_lookup_action (G_ACTION_MAP (window),
+ "tab-move-left");
+ move_tab_right_action = g_action_map_lookup_action (G_ACTION_MAP (window),
+ "tab-move-right");
+
+ g_simple_action_set_enabled (G_SIMPLE_ACTION (move_tab_left_action),
+ page == NULL || position > 0);
+ g_simple_action_set_enabled (G_SIMPLE_ACTION (move_tab_right_action),
+ page == NULL || position < n_pages - 1);
+
+ window->menu_page = page;
+}
+
+static void
+tab_view_notify_selected_page_cb (AdwTabView *tab_view,
+ GParamSpec *pspec,
+ NautilusWindow *window)
+{
+ AdwTabPage *page;
+ NautilusWindowSlot *slot;
+ GtkWidget *widget;
+
+ page = adw_tab_view_get_selected_page (tab_view);
+ widget = adw_tab_page_get_child (page);
+
+ g_assert (widget != NULL);
+
+ /* find slot corresponding to the target page */
+ slot = NAUTILUS_WINDOW_SLOT (widget);
+ g_assert (slot != NULL);
+
+ nautilus_window_set_active_slot (nautilus_window_slot_get_window (slot),
+ slot);
+}
+
+static void
+connect_slot (NautilusWindow *window,
+ NautilusWindowSlot *slot)
+{
+ g_signal_connect (slot, "notify::location",
+ G_CALLBACK (on_slot_location_changed), window);
+}
+
+static void
+disconnect_slot (NautilusWindow *window,
+ NautilusWindowSlot *slot)
+{
+ g_signal_handlers_disconnect_by_data (slot, window);
+}
+
+static NautilusWindowSlot *
+nautilus_window_create_and_init_slot (NautilusWindow *window,
+ NautilusOpenFlags flags)
+{
+ NautilusWindowSlot *slot;
+
+ slot = nautilus_window_slot_new (window);
+ nautilus_window_initialize_slot (window, slot, flags);
+
+ return slot;
+}
+
+static gboolean
+location_to_tooltip (GBinding *binding,
+ const GValue *input,
+ GValue *output,
+ NautilusWindowSlot *slot)
+{
+ GFile *location = g_value_get_object (input);
+ g_autofree gchar *location_name = NULL;
+
+ if (location == NULL)
+ {
+ return TRUE;
+ }
+
+ /* Set the tooltip on the label's parent (the tab label hbox),
+ * so it covers all of the tab label.
+ */
+ location_name = g_file_get_parse_name (location);
+
+ if (eel_uri_is_search (location_name))
+ {
+ g_value_set_string (output, nautilus_window_slot_get_title (slot));
+ }
+ else
+ {
+ g_value_set_string (output, location_name);
+ }
+
+ return TRUE;
+}
+
+void
+nautilus_window_initialize_slot (NautilusWindow *window,
+ NautilusWindowSlot *slot,
+ NautilusOpenFlags flags)
+{
+ AdwTabPage *page, *current;
+
+ g_assert (NAUTILUS_IS_WINDOW (window));
+ g_assert (NAUTILUS_IS_WINDOW_SLOT (slot));
+
+ connect_slot (window, slot);
+
+ current = adw_tab_view_get_selected_page (window->tab_view);
+ page = adw_tab_view_add_page (window->tab_view, GTK_WIDGET (slot), current);
+
+ g_object_bind_property (slot, "allow-stop",
+ page, "loading",
+ G_BINDING_SYNC_CREATE);
+ g_object_bind_property (slot, "title",
+ page, "title",
+ G_BINDING_SYNC_CREATE);
+ g_object_bind_property_full (slot, "location",
+ page, "tooltip",
+ G_BINDING_SYNC_CREATE,
+ (GBindingTransformFunc) location_to_tooltip,
+ NULL, slot, NULL);
+}
+
+void
+nautilus_window_open_location_full (NautilusWindow *window,
+ GFile *location,
+ NautilusOpenFlags flags,
+ GList *selection,
+ NautilusWindowSlot *target_slot)
+{
+ NautilusWindowSlot *active_slot;
+
+ /* Assert that we are not managing new windows */
+ g_assert (!(flags & NAUTILUS_OPEN_FLAG_NEW_WINDOW));
+
+ active_slot = nautilus_window_get_active_slot (window);
+ if (!target_slot)
+ {
+ target_slot = active_slot;
+ }
+
+ if (target_slot == NULL || (flags & NAUTILUS_OPEN_FLAG_NEW_TAB) != 0)
+ {
+ target_slot = nautilus_window_create_and_init_slot (window, flags);
+ }
+
+ /* Make the opened location the one active if we weren't ask for the
+ * oposite, since it's the most usual use case */
+ if (!(flags & NAUTILUS_OPEN_FLAG_DONT_MAKE_ACTIVE))
+ {
+ gtk_window_present (GTK_WINDOW (window));
+ nautilus_window_set_active_slot (window, target_slot);
+ }
+
+ nautilus_window_slot_open_location_full (target_slot, location, flags, selection);
+}
+
+static void
+unset_focus_widget (NautilusWindow *window)
+{
+ if (window->last_focus_widget != NULL)
+ {
+ g_object_remove_weak_pointer (G_OBJECT (window->last_focus_widget),
+ (gpointer *) &window->last_focus_widget);
+ window->last_focus_widget = NULL;
+ }
+}
+
+static void
+remember_focus_widget (NautilusWindow *window)
+{
+ GtkWidget *focus_widget;
+
+ focus_widget = gtk_window_get_focus (GTK_WINDOW (window));
+ if (focus_widget != NULL)
+ {
+ unset_focus_widget (window);
+
+ window->last_focus_widget = focus_widget;
+ g_object_add_weak_pointer (G_OBJECT (focus_widget),
+ (gpointer *) &(window->last_focus_widget));
+ }
+}
+
+static gboolean
+nautilus_window_grab_focus (GtkWidget *widget)
+{
+ NautilusWindowSlot *slot;
+
+ slot = nautilus_window_get_active_slot (NAUTILUS_WINDOW (widget));
+
+ if (slot != NULL)
+ {
+ return gtk_widget_grab_focus (GTK_WIDGET (slot));
+ }
+
+ return GTK_WIDGET_CLASS (nautilus_window_parent_class)->grab_focus (widget);
+}
+
+static void
+restore_focus_widget (NautilusWindow *window)
+{
+ if (window->last_focus_widget != NULL)
+ {
+ gtk_widget_grab_focus (window->last_focus_widget);
+ unset_focus_widget (window);
+ }
+}
+
+static void
+location_entry_cancel_callback (GtkWidget *widget,
+ NautilusWindow *window)
+{
+ nautilus_toolbar_set_show_location_entry (NAUTILUS_TOOLBAR (window->toolbar), FALSE);
+
+ restore_focus_widget (window);
+}
+
+static void
+location_entry_location_changed_callback (GtkWidget *widget,
+ GFile *location,
+ NautilusWindow *window)
+{
+ nautilus_toolbar_set_show_location_entry (NAUTILUS_TOOLBAR (window->toolbar), FALSE);
+
+ restore_focus_widget (window);
+
+ nautilus_window_open_location_full (window, location, 0, NULL, NULL);
+}
+
+static void
+remove_slot_from_window (NautilusWindowSlot *slot,
+ NautilusWindow *window)
+{
+ g_return_if_fail (NAUTILUS_IS_WINDOW_SLOT (slot));
+ g_return_if_fail (NAUTILUS_WINDOW (window));
+
+ DEBUG ("Removing slot %p", slot);
+
+ disconnect_slot (window, slot);
+ window->slots = g_list_remove (window->slots, slot);
+ g_signal_emit (window, signals[SLOT_REMOVED], 0, slot);
+}
+
+void
+nautilus_window_new_tab (NautilusWindow *window)
+{
+ NautilusWindowSlot *current_slot;
+ GFile *location;
+ g_autofree gchar *uri = NULL;
+
+ current_slot = nautilus_window_get_active_slot (window);
+ location = nautilus_window_slot_get_location (current_slot);
+
+ if (location != NULL)
+ {
+ uri = g_file_get_uri (location);
+ if (eel_uri_is_search (uri))
+ {
+ location = g_file_new_for_path (g_get_home_dir ());
+ }
+ else
+ {
+ g_object_ref (location);
+ }
+
+ nautilus_window_open_location_full (window, location,
+ NAUTILUS_OPEN_FLAG_NEW_TAB,
+ NULL, NULL);
+ g_object_unref (location);
+ }
+}
+
+static void
+update_cursor (NautilusWindow *window)
+{
+ NautilusWindowSlot *slot;
+
+ slot = nautilus_window_get_active_slot (window);
+
+ if (slot != NULL &&
+ nautilus_window_slot_get_allow_stop (slot))
+ {
+ gtk_widget_set_cursor_from_name (GTK_WIDGET (window), "progress");
+ }
+ else
+ {
+ gtk_widget_set_cursor (GTK_WIDGET (window), NULL);
+ }
+}
+
+void
+nautilus_window_reset_menus (NautilusWindow *window)
+{
+ nautilus_window_sync_allow_stop (window, nautilus_window_get_active_slot (window));
+}
+
+void
+nautilus_window_sync_allow_stop (NautilusWindow *window,
+ NautilusWindowSlot *slot)
+{
+ GAction *stop_action;
+ GAction *reload_action;
+ gboolean allow_stop, slot_is_active, slot_allow_stop;
+
+ stop_action = g_action_map_lookup_action (G_ACTION_MAP (window),
+ "stop");
+ reload_action = g_action_map_lookup_action (G_ACTION_MAP (window),
+ "reload");
+ allow_stop = g_action_get_enabled (stop_action);
+
+ slot_allow_stop = nautilus_window_slot_get_allow_stop (slot);
+ slot_is_active = (slot == nautilus_window_get_active_slot (window));
+
+
+ if (!slot_is_active ||
+ allow_stop != slot_allow_stop)
+ {
+ if (slot_is_active)
+ {
+ g_simple_action_set_enabled (G_SIMPLE_ACTION (stop_action), slot_allow_stop);
+ g_simple_action_set_enabled (G_SIMPLE_ACTION (reload_action), !slot_allow_stop);
+ }
+ if (gtk_widget_get_realized (GTK_WIDGET (window)))
+ {
+ update_cursor (window);
+ }
+ }
+}
+
+AdwTabView *
+nautilus_window_get_tab_view (NautilusWindow *window)
+{
+ g_return_val_if_fail (NAUTILUS_IS_WINDOW (window), NULL);
+
+ return window->tab_view;
+}
+
+/* Callback used when the places sidebar changes location; we need to change the displayed folder */
+static void
+open_location_cb (NautilusWindow *window,
+ GFile *location,
+ NautilusGtkPlacesOpenFlags open_flags)
+{
+ NautilusOpenFlags flags;
+ NautilusApplication *application;
+
+ switch (open_flags)
+ {
+ case NAUTILUS_GTK_PLACES_OPEN_NEW_TAB:
+ {
+ flags = NAUTILUS_OPEN_FLAG_NEW_TAB |
+ NAUTILUS_OPEN_FLAG_DONT_MAKE_ACTIVE;
+ }
+ break;
+
+ case NAUTILUS_GTK_PLACES_OPEN_NEW_WINDOW:
+ {
+ flags = NAUTILUS_OPEN_FLAG_NEW_WINDOW;
+ }
+ break;
+
+ case NAUTILUS_GTK_PLACES_OPEN_NORMAL: /* fall-through */
+ default:
+ {
+ flags = 0;
+ }
+ break;
+ }
+
+ application = NAUTILUS_APPLICATION (g_application_get_default ());
+ /* FIXME: We shouldn't need to provide the window, but seems gtk_application_get_active_window
+ * is not working properly in GtkApplication, so we cannot rely on that...
+ */
+ nautilus_application_open_location_full (application, location, flags,
+ NULL, window, NULL);
+}
+
+/* Callback used when the places sidebar needs us to present an error message */
+static void
+places_sidebar_show_error_message_cb (NautilusGtkPlacesSidebar *sidebar,
+ const char *primary,
+ const char *secondary,
+ gpointer user_data)
+{
+ NautilusWindow *window = NAUTILUS_WINDOW (user_data);
+
+ show_dialog (primary, secondary, GTK_WINDOW (window), GTK_MESSAGE_ERROR);
+}
+
+static void
+places_sidebar_show_other_locations_with_flags (NautilusWindow *window,
+ NautilusGtkPlacesOpenFlags open_flags)
+{
+ GFile *location;
+
+ location = g_file_new_for_uri ("other-locations:///");
+
+ open_location_cb (window, location, open_flags);
+
+ g_object_unref (location);
+}
+
+static void
+places_sidebar_show_starred_location (NautilusWindow *window,
+ NautilusGtkPlacesOpenFlags open_flags)
+{
+ GFile *location;
+
+ location = g_file_new_for_uri ("starred:///");
+
+ open_location_cb (window, location, open_flags);
+
+ g_object_unref (location);
+}
+
+/* Callback used when the places sidebar needs to know the drag action to suggest */
+static GdkDragAction
+places_sidebar_drag_action_requested_cb (NautilusGtkPlacesSidebar *sidebar,
+ NautilusFile *dest_file,
+ GList *source_file_list)
+{
+ return nautilus_dnd_get_preferred_action (dest_file, source_file_list->data);
+}
+#if 0 && NAUTILUS_DND_NEEDS_GTK4_REIMPLEMENTATION
+/* Callback used when the places sidebar needs us to pop up a menu with possible drag actions */
+static GdkDragAction
+places_sidebar_drag_action_ask_cb (NautilusGtkPlacesSidebar *sidebar,
+ GdkDragAction actions,
+ gpointer user_data)
+{
+ return nautilus_drag_drop_action_ask (GTK_WIDGET (sidebar), actions);
+}
+#endif
+static GList *
+build_uri_list_from_gfile_list (GSList *file_list)
+{
+ GList *result;
+ GSList *l;
+
+ result = NULL;
+
+ for (l = file_list; l; l = l->next)
+ {
+ GFile *file = l->data;
+ char *uri;
+
+ uri = g_file_get_uri (file);
+ result = g_list_prepend (result, uri);
+ }
+
+ return g_list_reverse (result);
+}
+
+/* Callback used when the places sidebar has URIs dropped into it. We do a normal file operation for them. */
+static void
+places_sidebar_drag_perform_drop_cb (NautilusGtkPlacesSidebar *sidebar,
+ GFile *dest_file,
+ GSList *source_file_list,
+ GdkDragAction action,
+ gpointer user_data)
+{
+ char *dest_uri;
+ GList *source_uri_list;
+
+ dest_uri = g_file_get_uri (dest_file);
+ source_uri_list = build_uri_list_from_gfile_list (source_file_list);
+
+ nautilus_file_operations_copy_move (source_uri_list, dest_uri, action, GTK_WIDGET (sidebar), NULL, NULL, NULL);
+
+ g_free (dest_uri);
+ g_list_free_full (source_uri_list, g_free);
+}
+
+static void
+action_restore_tab (GSimpleAction *action,
+ GVariant *state,
+ gpointer user_data)
+{
+ NautilusWindow *window = NAUTILUS_WINDOW (user_data);
+ NautilusOpenFlags flags;
+ g_autoptr (GFile) location = NULL;
+ NautilusWindowSlot *slot;
+ NautilusNavigationState *data;
+
+ if (g_queue_get_length (window->tab_data_queue) == 0)
+ {
+ return;
+ }
+
+ flags = NAUTILUS_OPEN_FLAG_NEW_TAB | NAUTILUS_OPEN_FLAG_DONT_MAKE_ACTIVE;
+
+ data = g_queue_pop_head (window->tab_data_queue);
+
+ location = nautilus_file_get_location (data->file);
+
+ slot = nautilus_window_create_and_init_slot (window, flags);
+
+ nautilus_window_slot_open_location_full (slot, location, flags, NULL);
+ nautilus_window_slot_restore_navigation_state (slot, data);
+
+ free_navigation_state (data);
+}
+
+static void
+action_toggle_sidebar (GSimpleAction *action,
+ GVariant *state,
+ gpointer user_data)
+{
+ NautilusWindow *window = NAUTILUS_WINDOW (user_data);
+ gboolean revealed;
+
+ revealed = adw_flap_get_reveal_flap (ADW_FLAP (window->content_flap));
+ adw_flap_set_reveal_flap (ADW_FLAP (window->content_flap), !revealed);
+}
+
+
+static guint
+get_window_xid (NautilusWindow *window)
+{
+#ifdef GDK_WINDOWING_X11
+ if (GDK_IS_X11_DISPLAY (gtk_widget_get_display (GTK_WIDGET (window))))
+ {
+ GdkSurface *gdk_surface = gtk_native_get_surface (GTK_NATIVE (window));
+ return (guint) gdk_x11_surface_get_xid (gdk_surface);
+ }
+#endif
+ return 0;
+}
+
+static void
+nautilus_window_set_up_sidebar (NautilusWindow *window)
+{
+ nautilus_gtk_places_sidebar_set_open_flags (NAUTILUS_GTK_PLACES_SIDEBAR (window->places_sidebar),
+ (NAUTILUS_GTK_PLACES_OPEN_NORMAL
+ | NAUTILUS_GTK_PLACES_OPEN_NEW_TAB
+ | NAUTILUS_GTK_PLACES_OPEN_NEW_WINDOW));
+
+ g_signal_connect_swapped (window->places_sidebar, "open-location",
+ G_CALLBACK (open_location_cb), window);
+ g_signal_connect (window->places_sidebar, "show-error-message",
+ G_CALLBACK (places_sidebar_show_error_message_cb), window);
+
+ g_signal_connect (window->places_sidebar, "drag-action-requested",
+ G_CALLBACK (places_sidebar_drag_action_requested_cb), window);
+#if 0 && NAUTILUS_DND_NEEDS_GTK4_REIMPLEMENTATION
+ g_signal_connect (window->places_sidebar, "drag-action-ask",
+ G_CALLBACK (places_sidebar_drag_action_ask_cb), window);
+ #endif
+ g_signal_connect (window->places_sidebar, "drag-perform-drop",
+ G_CALLBACK (places_sidebar_drag_perform_drop_cb), window);
+}
+
+void
+nautilus_window_slot_close (NautilusWindow *window,
+ NautilusWindowSlot *slot)
+{
+ NautilusNavigationState *data;
+ AdwTabPage *page;
+
+ DEBUG ("Requesting to remove slot %p from window %p", slot, window);
+ if (window == NULL || slot == NULL)
+ {
+ return;
+ }
+
+ data = nautilus_window_slot_get_navigation_state (slot);
+ if (data != NULL)
+ {
+ g_queue_push_head (window->tab_data_queue, data);
+ }
+
+ remove_slot_from_window (slot, window);
+
+ page = adw_tab_view_get_page (window->tab_view, GTK_WIDGET (slot));
+ /* this will destroy the slot */
+ adw_tab_view_close_page (window->tab_view, page);
+
+ /* If that was the last slot in the window, close the window. */
+ if (window->slots == NULL)
+ {
+ DEBUG ("Last slot removed, closing the window");
+ nautilus_window_close (window);
+ }
+}
+
+static void
+nautilus_window_sync_bookmarks (NautilusWindow *window)
+{
+ gboolean can_bookmark = FALSE;
+ NautilusWindowSlot *slot;
+ NautilusBookmarkList *bookmarks;
+ GAction *action;
+ GFile *location;
+
+ slot = window->active_slot;
+ location = slot != NULL ? nautilus_window_slot_get_location (slot) : NULL;
+
+ if (location != NULL)
+ {
+ bookmarks = nautilus_application_get_bookmarks
+ (NAUTILUS_APPLICATION (gtk_window_get_application (GTK_WINDOW (window))));
+ can_bookmark = nautilus_bookmark_list_can_bookmark_location (bookmarks, location);
+ }
+
+ action = g_action_map_lookup_action (G_ACTION_MAP (window), "bookmark-current-location");
+ g_simple_action_set_enabled (G_SIMPLE_ACTION (action), can_bookmark);
+}
+
+void
+nautilus_window_sync_location_widgets (NautilusWindow *window)
+{
+ NautilusWindowSlot *slot;
+ GFile *location;
+ GAction *action;
+ gboolean enabled;
+
+ slot = window->active_slot;
+ /* This function is called by the active slot. */
+ g_assert (slot != NULL);
+
+ location = nautilus_window_slot_get_location (slot);
+
+ /* Change the location bar and path bar to match the current location. */
+ if (location != NULL)
+ {
+ GtkWidget *location_entry;
+ GtkWidget *path_bar;
+
+ location_entry = nautilus_toolbar_get_location_entry (NAUTILUS_TOOLBAR (window->toolbar));
+ nautilus_location_entry_set_location (NAUTILUS_LOCATION_ENTRY (location_entry), location);
+
+ path_bar = nautilus_toolbar_get_path_bar (NAUTILUS_TOOLBAR (window->toolbar));
+ nautilus_path_bar_set_path (NAUTILUS_PATH_BAR (path_bar), location);
+ }
+
+ enabled = nautilus_window_slot_get_back_history (slot) != NULL;
+ action = g_action_map_lookup_action (G_ACTION_MAP (window), "back");
+ g_simple_action_set_enabled (G_SIMPLE_ACTION (action), enabled);
+ action = g_action_map_lookup_action (G_ACTION_MAP (window), "back-n");
+ g_simple_action_set_enabled (G_SIMPLE_ACTION (action), enabled);
+
+ enabled = nautilus_window_slot_get_forward_history (slot) != NULL;
+ action = g_action_map_lookup_action (G_ACTION_MAP (window), "forward");
+ g_simple_action_set_enabled (G_SIMPLE_ACTION (action), enabled);
+ action = g_action_map_lookup_action (G_ACTION_MAP (window), "forward-n");
+ g_simple_action_set_enabled (G_SIMPLE_ACTION (action), enabled);
+
+ nautilus_window_sync_bookmarks (window);
+}
+
+static GtkWidget *
+nautilus_window_ensure_location_entry (NautilusWindow *window)
+{
+ GtkWidget *location_entry;
+
+ remember_focus_widget (window);
+
+ nautilus_toolbar_set_show_location_entry (NAUTILUS_TOOLBAR (window->toolbar), TRUE);
+
+ location_entry = nautilus_toolbar_get_location_entry (NAUTILUS_TOOLBAR (window->toolbar));
+ gtk_widget_grab_focus (location_entry);
+
+ return location_entry;
+}
+
+static gchar *
+toast_undo_deleted_get_label (NautilusFileUndoInfo *undo_info)
+{
+ GList *files;
+ gchar *file_label;
+ gchar *label;
+ gint length;
+
+ files = nautilus_file_undo_info_trash_get_files (NAUTILUS_FILE_UNDO_INFO_TRASH (undo_info));
+ length = g_list_length (files);
+ if (length == 1)
+ {
+ file_label = g_file_get_basename (files->data);
+ /* Translators: only one item has been deleted and %s is its name. */
+ label = g_markup_printf_escaped (_("“%s” moved to trash"), file_label);
+ g_free (file_label);
+ }
+ else
+ {
+ /* Translators: one or more items might have been deleted, and %d
+ * is the count. */
+ label = g_markup_printf_escaped (ngettext ("%d file deleted", "%d files deleted", length), length);
+ }
+
+ return label;
+}
+
+static gchar *
+toast_undo_unstar_get_label (NautilusFileUndoInfo *undo_info)
+{
+ GList *files;
+ gchar *label;
+ gint length;
+
+ files = nautilus_file_undo_info_starred_get_files (NAUTILUS_FILE_UNDO_INFO_STARRED (undo_info));
+ length = g_list_length (files);
+ if (length == 1)
+ {
+ g_autofree gchar *file_label = NULL;
+
+ file_label = nautilus_file_get_display_name (files->data);
+ /* Translators: one item has been unstarred and %s is its name. */
+ label = g_markup_printf_escaped (_("“%s” unstarred"), file_label);
+ }
+ else
+ {
+ /* Translators: one or more items have been unstarred, and %d
+ * is the count. */
+ label = g_markup_printf_escaped (ngettext ("%d file unstarred", "%d files unstarred", length), length);
+ }
+
+ return label;
+}
+
+static void
+nautilus_window_on_undo_changed (NautilusFileUndoManager *manager,
+ NautilusWindow *window)
+{
+ NautilusFileUndoInfo *undo_info;
+ NautilusFileUndoManagerState state;
+ AdwToast *toast;
+
+ undo_info = nautilus_file_undo_manager_get_action ();
+ state = nautilus_file_undo_manager_get_state ();
+
+ if (undo_info != NULL &&
+ state == NAUTILUS_FILE_UNDO_MANAGER_STATE_UNDO)
+ {
+ gboolean popup_toast = FALSE;
+ g_autofree gchar *label = NULL;
+
+ if (nautilus_file_undo_info_get_op_type (undo_info) == NAUTILUS_FILE_UNDO_OP_MOVE_TO_TRASH)
+ {
+ g_autoptr (GList) files = NULL;
+
+ files = nautilus_file_undo_info_trash_get_files (NAUTILUS_FILE_UNDO_INFO_TRASH (undo_info));
+
+ /* Don't pop up a notification if user canceled the operation or the focus
+ * is not in the this window. This is an easy way to know from which window
+ * was the delete operation made */
+ if (files != NULL && gtk_window_is_active (GTK_WINDOW (window)))
+ {
+ popup_toast = TRUE;
+ label = toast_undo_deleted_get_label (undo_info);
+ }
+ }
+ else if (nautilus_file_undo_info_get_op_type (undo_info) == NAUTILUS_FILE_UNDO_OP_STARRED)
+ {
+ NautilusWindowSlot *active_slot;
+ GFile *location;
+
+ active_slot = nautilus_window_get_active_slot (window);
+ location = nautilus_window_slot_get_location (active_slot);
+ /* Don't pop up a notification if the focus is not in the this
+ * window. This is an easy way to know from which window was the
+ * unstart operation made */
+ if (eel_uri_is_starred (g_file_get_uri (location)) &&
+ gtk_window_is_active (GTK_WINDOW (window)) &&
+ !nautilus_file_undo_info_starred_is_starred (NAUTILUS_FILE_UNDO_INFO_STARRED (undo_info)))
+ {
+ popup_toast = TRUE;
+ label = toast_undo_unstar_get_label (undo_info);
+ }
+ }
+
+ if (popup_toast)
+ {
+ toast = adw_toast_new (label);
+ adw_toast_set_button_label (toast, _("Undo"));
+ adw_toast_set_action_name (toast, "win.undo");
+ adw_toast_set_priority (toast, ADW_TOAST_PRIORITY_HIGH);
+ adw_toast_overlay_add_toast (window->toast_overlay, toast);
+ }
+ }
+}
+
+void
+nautilus_window_show_operation_notification (NautilusWindow *window,
+ gchar *main_label,
+ GFile *folder_to_open)
+{
+ gchar *button_label;
+ gchar *folder_name;
+ NautilusFile *folder;
+ GVariant *target;
+ GFile *current_location;
+ AdwToast *toast;
+
+ if (window->active_slot == NULL)
+ {
+ return;
+ }
+
+ toast = adw_toast_new (main_label);
+ adw_toast_set_priority (toast, ADW_TOAST_PRIORITY_HIGH);
+
+ current_location = nautilus_window_slot_get_location (window->active_slot);
+ if (gtk_window_is_active (GTK_WINDOW (window)))
+ {
+ if (!g_file_equal (folder_to_open, current_location))
+ {
+ target = g_variant_new_take_string (g_file_get_uri (folder_to_open));
+ folder = nautilus_file_get (folder_to_open);
+ folder_name = nautilus_file_get_display_name (folder);
+ button_label = g_strdup_printf (_("Open %s"), folder_name);
+ adw_toast_set_button_label (toast, button_label);
+ adw_toast_set_action_name (toast, "win.open-location");
+ adw_toast_set_action_target_value (toast, target);
+ nautilus_file_unref (folder);
+ g_free (folder_name);
+ g_free (button_label);
+ }
+
+ adw_toast_overlay_add_toast (window->toast_overlay, toast);
+ }
+}
+
+static void
+on_path_bar_open_location (NautilusWindow *window,
+ GFile *location,
+ NautilusOpenFlags open_flags)
+{
+ if (open_flags & NAUTILUS_OPEN_FLAG_NEW_WINDOW)
+ {
+ nautilus_application_open_location_full (NAUTILUS_APPLICATION (g_application_get_default ()),
+ location, NAUTILUS_OPEN_FLAG_NEW_WINDOW, NULL, NULL, NULL);
+ }
+ else
+ {
+ nautilus_window_open_location_full (window, location, open_flags, NULL, NULL);
+ }
+}
+
+GtkWidget *
+nautilus_window_get_toolbar (NautilusWindow *window)
+{
+ g_return_val_if_fail (NAUTILUS_IS_WINDOW (window), NULL);
+
+ return window->toolbar;
+}
+
+static void
+setup_toolbar (NautilusWindow *window)
+{
+ GtkWidget *path_bar;
+ GtkWidget *location_entry;
+
+ /* connect to the pathbar signals */
+ path_bar = nautilus_toolbar_get_path_bar (NAUTILUS_TOOLBAR (window->toolbar));
+
+ g_signal_connect_swapped (path_bar, "open-location",
+ G_CALLBACK (on_path_bar_open_location), window);
+
+ /* connect to the location entry signals */
+ location_entry = nautilus_toolbar_get_location_entry (NAUTILUS_TOOLBAR (window->toolbar));
+
+ g_signal_connect_object (location_entry, "location-changed",
+ G_CALLBACK (location_entry_location_changed_callback), window, 0);
+ g_signal_connect_object (location_entry, "cancel",
+ G_CALLBACK (location_entry_cancel_callback), window, 0);
+}
+
+static gboolean
+tab_view_close_page_cb (AdwTabView *view,
+ AdwTabPage *page,
+ NautilusWindow *window)
+{
+ NautilusWindowSlot *slot;
+
+ slot = NAUTILUS_WINDOW_SLOT (adw_tab_page_get_child (page));
+
+ nautilus_window_slot_close (window, slot);
+
+ return GDK_EVENT_PROPAGATE;
+}
+
+static void
+tab_view_page_detached_cb (AdwTabView *tab_view,
+ AdwTabPage *page,
+ gint position,
+ NautilusWindow *window)
+{
+ NautilusWindowSlot *slot = NAUTILUS_WINDOW_SLOT (adw_tab_page_get_child (page));
+
+ /* If the tab has been moved to another window, we need to remove the slot
+ * from the current window here. Otherwise, if the tab has been closed, then
+ * we have*/
+ if (g_list_find (window->slots, slot))
+ {
+ remove_slot_from_window (slot, window);
+ }
+}
+
+static void
+tab_view_page_attached_cb (AdwTabView *tab_view,
+ AdwTabPage *page,
+ gint position,
+ NautilusWindow *window)
+{
+ NautilusWindowSlot *slot = NAUTILUS_WINDOW_SLOT (adw_tab_page_get_child (page));
+
+ nautilus_window_slot_set_window (slot, window);
+ window->slots = g_list_append (window->slots, slot);
+ g_signal_emit (window, signals[SLOT_ADDED], 0, slot);
+}
+
+static AdwTabView *
+tab_view_create_window_cb (AdwTabView *tab_view,
+ NautilusWindow *window)
+{
+ NautilusApplication *app;
+ NautilusWindow *new_window;
+
+ app = NAUTILUS_APPLICATION (g_application_get_default ());
+ new_window = nautilus_application_create_window (app);
+ gtk_window_set_display (GTK_WINDOW (new_window),
+ gtk_widget_get_display (GTK_WIDGET (tab_view)));
+
+ gtk_window_present (GTK_WINDOW (new_window));
+
+ return new_window->tab_view;
+}
+
+static void
+setup_tab_view (NautilusWindow *window)
+{
+ g_signal_connect (window->tab_view, "close-page",
+ G_CALLBACK (tab_view_close_page_cb),
+ window);
+ g_signal_connect (window->tab_view, "setup-menu",
+ G_CALLBACK (tab_view_setup_menu_cb),
+ window);
+ g_signal_connect (window->tab_view, "notify::selected-page",
+ G_CALLBACK (tab_view_notify_selected_page_cb),
+ window);
+ g_signal_connect (window->tab_view, "create-window",
+ G_CALLBACK (tab_view_create_window_cb),
+ window);
+ g_signal_connect (window->tab_view, "page-attached",
+ G_CALLBACK (tab_view_page_attached_cb),
+ window);
+ g_signal_connect (window->tab_view, "page-detached",
+ G_CALLBACK (tab_view_page_detached_cb),
+ window);
+}
+
+const GActionEntry win_entries[] =
+{
+ { "back", action_back },
+ { "forward", action_forward },
+ { "back-n", action_back_n, "u" },
+ { "forward-n", action_forward_n, "u" },
+ { "up", action_up },
+ { "view-menu", action_toggle_state_view_button, NULL, "false", NULL },
+ { "current-location-menu", action_show_current_location_menu },
+ { "open-location", action_open_location, "s" },
+ { "reload", action_reload },
+ { "stop", action_stop },
+ { "new-tab", action_new_tab },
+ { "enter-location", action_enter_location },
+ { "bookmark-current-location", action_bookmark_current_location },
+ { "undo", action_undo },
+ { "redo", action_redo },
+ /* Only accesible by shorcuts */
+ { "close-current-view", action_close_current_view },
+ { "go-home", action_go_home },
+ { "go-starred", action_go_starred },
+ { "tab-move-left", action_tab_move_left },
+ { "tab-move-right", action_tab_move_right },
+ { "prompt-root-location", action_prompt_for_location_root },
+ { "prompt-home-location", action_prompt_for_location_home },
+ { "go-to-tab", NULL, "i", "0", action_go_to_tab },
+ { "restore-tab", action_restore_tab },
+ { "toggle-sidebar", action_toggle_sidebar },
+};
+
+static void
+nautilus_window_initialize_actions (NautilusWindow *window)
+{
+ GApplication *app;
+ GAction *action;
+ gchar detailed_action[80];
+ gchar accel[80];
+ gint i;
+
+ g_action_map_add_action_entries (G_ACTION_MAP (window),
+ win_entries, G_N_ELEMENTS (win_entries),
+ window);
+
+#define ACCELS(...) ((const char *[]) { __VA_ARGS__, NULL })
+
+ app = g_application_get_default ();
+ nautilus_application_set_accelerators (app, "win.back", ACCELS ("<alt>Left", "Back"));
+ nautilus_application_set_accelerators (app, "win.forward", ACCELS ("<alt>Right", "Forward"));
+ nautilus_application_set_accelerators (app, "win.enter-location", ACCELS ("<control>l", "Go", "OpenURL"));
+ nautilus_application_set_accelerator (app, "win.new-tab", "<control>t");
+ nautilus_application_set_accelerator (app, "win.close-current-view", "<control>w");
+
+ /* Special case reload, since users are used to use two shortcuts instead of one */
+ nautilus_application_set_accelerators (app, "win.reload", ACCELS ("F5", "<ctrl>r", "Refresh", "Reload"));
+ nautilus_application_set_accelerator (app, "win.stop", "Stop");
+
+ nautilus_application_set_accelerator (app, "win.undo", "<control>z");
+ nautilus_application_set_accelerator (app, "win.redo", "<shift><control>z");
+ /* Only accesible by shorcuts */
+ nautilus_application_set_accelerators (app, "win.bookmark-current-location", ACCELS ("<control>d", "AddFavorite"));
+ nautilus_application_set_accelerator (app, "win.up", "<alt>Up");
+ nautilus_application_set_accelerators (app, "win.go-home", ACCELS ("<alt>Home", "HomePage", "Start"));
+ nautilus_application_set_accelerator (app, "win.go-starred", "Favorites");
+ nautilus_application_set_accelerator (app, "win.tab-move-left", "<shift><control>Page_Up");
+ nautilus_application_set_accelerator (app, "win.tab-move-right", "<shift><control>Page_Down");
+ nautilus_application_set_accelerators (app, "win.prompt-root-location", ACCELS ("slash", "KP_Divide"));
+ /* Support keyboard layouts which have a dead tilde key but not a tilde key. */
+ nautilus_application_set_accelerators (app, "win.prompt-home-location", ACCELS ("asciitilde", "dead_tilde"));
+ nautilus_application_set_accelerator (app, "win.current-location-menu", "F10");
+ nautilus_application_set_accelerator (app, "win.restore-tab", "<shift><control>t");
+ nautilus_application_set_accelerator (app, "win.toggle-sidebar", "F9");
+
+ /* Alt+N for the first 9 tabs */
+ for (i = 0; i < 9; ++i)
+ {
+ g_snprintf (detailed_action, sizeof (detailed_action), "win.go-to-tab(%i)", i);
+ g_snprintf (accel, sizeof (accel), "<alt>%i", i + 1);
+ nautilus_application_set_accelerator (app, detailed_action, accel);
+ }
+
+#undef ACCELS
+
+ action = g_action_map_lookup_action (G_ACTION_MAP (window), "toggle-sidebar");
+ g_object_bind_property (window->content_flap, "folded",
+ action, "enabled", G_BINDING_SYNC_CREATE);
+}
+
+
+static void
+nautilus_window_constructed (GObject *self)
+{
+ NautilusWindow *window;
+ NautilusApplication *application;
+
+ window = NAUTILUS_WINDOW (self);
+
+ nautilus_profile_start (NULL);
+
+ G_OBJECT_CLASS (nautilus_window_parent_class)->constructed (self);
+
+ application = NAUTILUS_APPLICATION (g_application_get_default ());
+ gtk_window_set_application (GTK_WINDOW (window), GTK_APPLICATION (application));
+
+ setup_toolbar (window);
+
+ gtk_window_set_default_size (GTK_WINDOW (window),
+ NAUTILUS_WINDOW_DEFAULT_WIDTH,
+ NAUTILUS_WINDOW_DEFAULT_HEIGHT);
+
+ setup_tab_view (window);
+ nautilus_window_set_up_sidebar (window);
+
+
+ g_signal_connect_object (nautilus_file_undo_manager_get (), "undo-changed",
+ G_CALLBACK (nautilus_window_on_undo_changed), self,
+ G_CONNECT_AFTER);
+
+ /* Is required that the UI is constructed before initializating the actions, since
+ * some actions trigger UI widgets to show/hide. */
+ nautilus_window_initialize_actions (window);
+
+ window->bookmarks_id = g_signal_connect_object (nautilus_application_get_bookmarks (application),
+ "changed",
+ G_CALLBACK (nautilus_window_sync_bookmarks),
+ window, G_CONNECT_SWAPPED);
+
+ nautilus_toolbar_on_window_constructed (NAUTILUS_TOOLBAR (window->toolbar));
+
+ nautilus_profile_end (NULL);
+}
+
+static void
+nautilus_window_dispose (GObject *object)
+{
+ NautilusWindow *window;
+ GtkApplication *application;
+ GList *slots_copy;
+
+ window = NAUTILUS_WINDOW (object);
+ application = gtk_window_get_application (GTK_WINDOW (window));
+
+ DEBUG ("Destroying window");
+
+ /* close all slots safely */
+ slots_copy = g_list_copy (window->slots);
+ g_list_foreach (slots_copy, (GFunc) remove_slot_from_window, window);
+ g_list_free (slots_copy);
+
+ /* the slots list should now be empty */
+ g_assert (window->slots == NULL);
+
+ g_clear_weak_pointer (&window->active_slot);
+
+ if (application != NULL)
+ {
+ g_clear_signal_handler (&window->bookmarks_id,
+ nautilus_application_get_bookmarks (NAUTILUS_APPLICATION (application)));
+ }
+
+ nautilus_window_unexport_handle (window);
+
+ G_OBJECT_CLASS (nautilus_window_parent_class)->dispose (object);
+}
+
+static void
+nautilus_window_finalize (GObject *object)
+{
+ NautilusWindow *window;
+
+ window = NAUTILUS_WINDOW (object);
+
+ if (window->sidebar_width_handler_id != 0)
+ {
+ g_source_remove (window->sidebar_width_handler_id);
+ window->sidebar_width_handler_id = 0;
+ }
+
+ g_clear_object (&window->selected_file);
+ g_clear_object (&window->selected_volume);
+
+ g_signal_handlers_disconnect_by_func (nautilus_file_undo_manager_get (),
+ G_CALLBACK (nautilus_window_on_undo_changed),
+ window);
+
+ g_queue_free_full (window->tab_data_queue, free_navigation_state);
+
+ /* nautilus_window_close() should have run */
+ g_assert (window->slots == NULL);
+
+ G_OBJECT_CLASS (nautilus_window_parent_class)->finalize (object);
+}
+
+static void
+nautilus_window_save_geometry (NautilusWindow *window)
+{
+ gint width;
+ gint height;
+ GVariant *initial_size;
+
+ gtk_window_get_default_size (GTK_WINDOW (window), &width, &height);
+ initial_size = g_variant_new_parsed ("(%i, %i)", width, height);
+
+ g_settings_set_value (nautilus_window_state,
+ NAUTILUS_WINDOW_STATE_INITIAL_SIZE,
+ initial_size);
+}
+
+void
+nautilus_window_close (NautilusWindow *window)
+{
+ g_return_if_fail (NAUTILUS_IS_WINDOW (window));
+
+ nautilus_window_save_geometry (window);
+ nautilus_window_set_active_slot (window, NULL);
+
+ /* The pad controller hold a reference to the window, creating a cycle.
+ * Usually, reference cycles are resolved in dispose(), but GTK removes the
+ * controllers in finalize(), so our only option is to manually remove it
+ * here before starting the destruction of the window. */
+ if (window->pad_controller != NULL)
+ {
+ gtk_widget_remove_controller (GTK_WIDGET (window),
+ GTK_EVENT_CONTROLLER (window->pad_controller));
+ g_clear_weak_pointer (&window->pad_controller);
+ }
+
+ gtk_window_destroy (GTK_WINDOW (window));
+}
+
+void
+nautilus_window_set_active_slot (NautilusWindow *window,
+ NautilusWindowSlot *new_slot)
+{
+ NautilusWindowSlot *old_slot;
+
+ g_assert (NAUTILUS_IS_WINDOW (window));
+
+ if (new_slot)
+ {
+ g_assert ((window == nautilus_window_slot_get_window (new_slot)));
+ }
+
+ old_slot = nautilus_window_get_active_slot (window);
+
+ if (old_slot == new_slot)
+ {
+ return;
+ }
+
+ DEBUG ("Setting new slot %p as active, old slot inactive %p", new_slot, old_slot);
+
+ /* make old slot inactive if it exists (may be NULL after init, for example) */
+ if (old_slot != NULL)
+ {
+ /* inform slot & view */
+ nautilus_window_slot_set_active (old_slot, FALSE);
+ }
+
+ g_set_weak_pointer (&window->active_slot, new_slot);
+
+ /* make new slot active, if it exists */
+ if (new_slot)
+ {
+ /* inform slot & view */
+ nautilus_window_slot_set_active (new_slot, TRUE);
+
+ on_location_changed (window);
+ }
+
+ g_object_notify_by_pspec (G_OBJECT (window), properties[PROP_ACTIVE_SLOT]);
+}
+
+static void
+nautilus_window_realize (GtkWidget *widget)
+{
+ GTK_WIDGET_CLASS (nautilus_window_parent_class)->realize (widget);
+ update_cursor (NAUTILUS_WINDOW (widget));
+}
+
+static gboolean
+nautilus_window_key_capture (GtkEventControllerKey *controller,
+ unsigned int keyval,
+ unsigned int keycode,
+ GdkModifierType state,
+ gpointer user_data)
+{
+ GtkWidget *widget;
+ GtkWidget *focus_widget;
+
+ widget = gtk_event_controller_get_widget (GTK_EVENT_CONTROLLER (controller));
+ focus_widget = gtk_window_get_focus (GTK_WINDOW (widget));
+ if (focus_widget != NULL && GTK_IS_EDITABLE (focus_widget))
+ {
+ /* if we have input focus on a GtkEditable (e.g. a GtkEntry), forward
+ * the event to it before activating accelerators. This allows, e.g.,
+ * typing a tilde without activating the prompt-home-location action.
+ */
+ if (gtk_event_controller_key_forward (controller, focus_widget))
+ {
+ return GDK_EVENT_STOP;
+ }
+ }
+
+ return GDK_EVENT_PROPAGATE;
+}
+
+static gboolean
+nautilus_window_key_bubble (GtkEventControllerKey *controller,
+ unsigned int keyval,
+ unsigned int keycode,
+ GdkModifierType state,
+ gpointer user_data)
+{
+ GtkWidget *widget;
+ NautilusWindow *window;
+
+ widget = gtk_event_controller_get_widget (GTK_EVENT_CONTROLLER (controller));
+ window = NAUTILUS_WINDOW (widget);
+ if (window->active_slot != NULL &&
+ nautilus_window_slot_handle_event (window->active_slot, controller, keyval, state))
+ {
+ return GDK_EVENT_STOP;
+ }
+
+ return GDK_EVENT_PROPAGATE;
+}
+
+void
+nautilus_window_sync_title (NautilusWindow *window,
+ NautilusWindowSlot *slot)
+{
+ g_return_if_fail (NAUTILUS_IS_WINDOW (window));
+ g_return_if_fail (NAUTILUS_IS_WINDOW_SLOT (slot));
+
+ if (slot == nautilus_window_get_active_slot (window))
+ {
+ gtk_window_set_title (GTK_WINDOW (window), nautilus_window_slot_get_title (slot));
+ }
+}
+
+#ifdef GDK_WINDOWING_WAYLAND
+typedef struct
+{
+ NautilusWindow *window;
+ NautilusWindowHandleExported callback;
+ gpointer user_data;
+} WaylandWindowHandleExportedData;
+
+static void
+wayland_window_handle_exported (GdkToplevel *toplevel,
+ const char *wayland_handle_str,
+ gpointer user_data)
+{
+ WaylandWindowHandleExportedData *data = user_data;
+
+ data->window->export_handle = g_strdup_printf ("wayland:%s", wayland_handle_str);
+ data->callback (data->window, data->window->export_handle, 0, data->user_data);
+}
+#endif
+
+gboolean
+nautilus_window_export_handle (NautilusWindow *window,
+ NautilusWindowHandleExported callback,
+ gpointer user_data)
+{
+ guint xid = get_window_xid (window);
+
+ if (window->export_handle != NULL)
+ {
+ callback (window, window->export_handle, xid, user_data);
+ return TRUE;
+ }
+
+#ifdef GDK_WINDOWING_X11
+ if (GDK_IS_X11_DISPLAY (gtk_widget_get_display (GTK_WIDGET (window))))
+ {
+ window->export_handle = g_strdup_printf ("x11:%x", xid);
+ callback (window, window->export_handle, xid, user_data);
+
+ return TRUE;
+ }
+#endif
+#ifdef GDK_WINDOWING_WAYLAND
+ if (GDK_IS_WAYLAND_DISPLAY (gtk_widget_get_display (GTK_WIDGET (window))))
+ {
+ GdkSurface *gdk_surface = gtk_native_get_surface (GTK_NATIVE (window));
+ WaylandWindowHandleExportedData *data;
+
+ data = g_new0 (WaylandWindowHandleExportedData, 1);
+ data->window = window;
+ data->callback = callback;
+ data->user_data = user_data;
+
+ if (!gdk_wayland_toplevel_export_handle (GDK_WAYLAND_TOPLEVEL (gdk_surface),
+ wayland_window_handle_exported,
+ data,
+ g_free))
+ {
+ g_free (data);
+ return FALSE;
+ }
+ else
+ {
+ return TRUE;
+ }
+ }
+#endif
+
+ g_warning ("Couldn't export handle, unsupported windowing system");
+
+ return FALSE;
+}
+
+void
+nautilus_window_unexport_handle (NautilusWindow *window)
+{
+ if (window->export_handle == NULL)
+ {
+ return;
+ }
+
+#ifdef GDK_WINDOWING_WAYLAND
+ if (GDK_IS_WAYLAND_DISPLAY (gtk_widget_get_display (GTK_WIDGET (window))))
+ {
+ GdkSurface *gdk_surface = gtk_native_get_surface (GTK_NATIVE (window));
+ if (GDK_IS_WAYLAND_TOPLEVEL (gdk_surface))
+ {
+ gdk_wayland_toplevel_unexport_handle (GDK_WAYLAND_TOPLEVEL (gdk_surface));
+ }
+ }
+#endif
+
+ g_clear_pointer (&window->export_handle, g_free);
+}
+
+/**
+ * nautilus_window_show:
+ * @widget: GtkWidget
+ *
+ * Call parent and then show/hide window items
+ * base on user prefs.
+ */
+static void
+nautilus_window_show (GtkWidget *widget)
+{
+ GTK_WIDGET_CLASS (nautilus_window_parent_class)->show (widget);
+}
+
+NautilusWindowSlot *
+nautilus_window_get_active_slot (NautilusWindow *window)
+{
+ g_assert (NAUTILUS_IS_WINDOW (window));
+
+ return window->active_slot;
+}
+
+GList *
+nautilus_window_get_slots (NautilusWindow *window)
+{
+ g_assert (NAUTILUS_IS_WINDOW (window));
+
+ return window->slots;
+}
+
+static void
+on_is_maximized_changed (GObject *object,
+ GParamSpec *pspec,
+ gpointer user_data)
+{
+ gboolean is_maximized;
+
+ is_maximized = gtk_window_is_maximized (GTK_WINDOW (object));
+
+ g_settings_set_boolean (nautilus_window_state,
+ NAUTILUS_WINDOW_STATE_MAXIMIZED, is_maximized);
+}
+
+static gboolean
+nautilus_window_close_request (GtkWindow *window)
+{
+ nautilus_window_close (NAUTILUS_WINDOW (window));
+ return FALSE;
+}
+
+static void
+nautilus_window_back_or_forward (NautilusWindow *window,
+ gboolean back,
+ guint distance)
+{
+ NautilusWindowSlot *slot;
+
+ slot = nautilus_window_get_active_slot (window);
+
+ if (slot != NULL)
+ {
+ nautilus_window_slot_back_or_forward (slot, back, distance);
+ }
+}
+
+void
+nautilus_window_back_or_forward_in_new_tab (NautilusWindow *window,
+ NautilusNavigationDirection direction)
+{
+ GFile *location;
+ NautilusWindowSlot *window_slot;
+ NautilusWindowSlot *new_slot;
+ NautilusNavigationState *state;
+
+ window_slot = nautilus_window_get_active_slot (window);
+ new_slot = nautilus_window_slot_new (window);
+ state = nautilus_window_slot_get_navigation_state (window_slot);
+
+ /* Manually fix up the back / forward lists and location.
+ * This way we don't have to unnecessary load the current location
+ * and then load back / forward */
+ switch (direction)
+ {
+ case NAUTILUS_NAVIGATION_DIRECTION_BACK:
+ {
+ state->forward_list = g_list_prepend (state->forward_list, state->current_location_bookmark);
+ state->current_location_bookmark = state->back_list->data;
+ state->back_list = state->back_list->next;
+ }
+ break;
+
+ case NAUTILUS_NAVIGATION_DIRECTION_FORWARD:
+ {
+ state->back_list = g_list_prepend (state->back_list, state->current_location_bookmark);
+ state->current_location_bookmark = state->forward_list->data;
+ state->forward_list = state->forward_list->next;
+ }
+ break;
+
+ default:
+ {
+ g_assert_not_reached ();
+ }
+ }
+
+ location = nautilus_bookmark_get_location (state->current_location_bookmark);
+ nautilus_window_initialize_slot (window, new_slot, NAUTILUS_OPEN_FLAG_NEW_TAB);
+ nautilus_window_slot_open_location_full (new_slot, location, 0, NULL);
+ nautilus_window_slot_restore_navigation_state (new_slot, state);
+
+ free_navigation_state (state);
+}
+
+static void
+on_click_gesture_pressed (GtkGestureClick *gesture,
+ gint n_press,
+ gdouble x,
+ gdouble y,
+ gpointer user_data)
+{
+ GtkWidget *widget;
+ NautilusWindow *window;
+ guint button;
+
+ widget = gtk_event_controller_get_widget (GTK_EVENT_CONTROLLER (gesture));
+ window = NAUTILUS_WINDOW (widget);
+ button = gtk_gesture_single_get_current_button (GTK_GESTURE_SINGLE (gesture));
+
+ if (mouse_extra_buttons && (button == mouse_back_button))
+ {
+ nautilus_window_back_or_forward (window, TRUE, 0);
+ }
+ else if (mouse_extra_buttons && (button == mouse_forward_button))
+ {
+ nautilus_window_back_or_forward (window, FALSE, 0);
+ }
+}
+
+static void
+mouse_back_button_changed (gpointer callback_data)
+{
+ int new_back_button;
+
+ new_back_button = g_settings_get_int (nautilus_preferences, NAUTILUS_PREFERENCES_MOUSE_BACK_BUTTON);
+
+ /* Bounds checking */
+ if (new_back_button < 6 || new_back_button > UPPER_MOUSE_LIMIT)
+ {
+ return;
+ }
+
+ mouse_back_button = new_back_button;
+}
+
+static void
+mouse_forward_button_changed (gpointer callback_data)
+{
+ int new_forward_button;
+
+ new_forward_button = g_settings_get_int (nautilus_preferences, NAUTILUS_PREFERENCES_MOUSE_FORWARD_BUTTON);
+
+ /* Bounds checking */
+ if (new_forward_button < 6 || new_forward_button > UPPER_MOUSE_LIMIT)
+ {
+ return;
+ }
+
+ mouse_forward_button = new_forward_button;
+}
+
+static void
+use_extra_mouse_buttons_changed (gpointer callback_data)
+{
+ mouse_extra_buttons = g_settings_get_boolean (nautilus_preferences, NAUTILUS_PREFERENCES_MOUSE_USE_EXTRA_BUTTONS);
+}
+
+static void
+nautilus_window_init (NautilusWindow *window)
+{
+ GtkWindowGroup *window_group;
+ GtkEventController *controller;
+ GtkPadController *pad_controller;
+
+ g_type_ensure (NAUTILUS_TYPE_TOOLBAR);
+ g_type_ensure (NAUTILUS_TYPE_GTK_PLACES_SIDEBAR);
+ gtk_widget_init_template (GTK_WIDGET (window));
+
+ g_signal_connect_object (window->places_sidebar,
+ "show-other-locations-with-flags",
+ G_CALLBACK (places_sidebar_show_other_locations_with_flags),
+ window,
+ G_CONNECT_SWAPPED);
+ g_signal_connect_object (window->places_sidebar,
+ "show-starred-location",
+ G_CALLBACK (places_sidebar_show_starred_location),
+ window,
+ G_CONNECT_SWAPPED);
+
+ g_signal_connect (window, "notify::maximized",
+ G_CALLBACK (on_is_maximized_changed), NULL);
+
+ window->slots = NULL;
+ window->active_slot = NULL;
+
+ gtk_style_context_add_class (gtk_widget_get_style_context (GTK_WIDGET (window)),
+ "nautilus-window");
+
+ window_group = gtk_window_group_new ();
+ gtk_window_group_add_window (window_group, GTK_WINDOW (window));
+ g_object_unref (window_group);
+
+ window->tab_data_queue = g_queue_new ();
+
+ /* Attention: this creates a reference cycle: the pad controller owns a
+ * reference to the window (as an action group) and the window (as a widget)
+ * owns a reference to the pad controller. To break this, we must remove
+ * the controller from the window before destroying the window. But we need
+ * to know the controller is still alive before trying to remove it, so a
+ * weak reference is added. */
+ pad_controller = gtk_pad_controller_new (G_ACTION_GROUP (window), NULL);
+ g_set_weak_pointer (&window->pad_controller, pad_controller);
+ gtk_pad_controller_set_action_entries (window->pad_controller,
+ pad_actions, G_N_ELEMENTS (pad_actions));
+ gtk_widget_add_controller (GTK_WIDGET (window),
+ GTK_EVENT_CONTROLLER (window->pad_controller));
+
+ controller = GTK_EVENT_CONTROLLER (gtk_gesture_click_new ());
+ gtk_widget_add_controller (GTK_WIDGET (window), controller);
+ gtk_event_controller_set_propagation_phase (controller, GTK_PHASE_CAPTURE);
+ gtk_gesture_single_set_button (GTK_GESTURE_SINGLE (controller), 0);
+ g_signal_connect (controller, "pressed",
+ G_CALLBACK (on_click_gesture_pressed), NULL);
+
+ controller = gtk_event_controller_key_new ();
+ gtk_widget_add_controller (GTK_WIDGET (window), controller);
+ gtk_event_controller_set_propagation_phase (controller, GTK_PHASE_CAPTURE);
+ g_signal_connect (controller, "key-pressed",
+ G_CALLBACK (nautilus_window_key_capture), NULL);
+
+ controller = gtk_event_controller_key_new ();
+ gtk_widget_add_controller (GTK_WIDGET (window), controller);
+ gtk_event_controller_set_propagation_phase (controller, GTK_PHASE_BUBBLE);
+ g_signal_connect (controller, "key-pressed",
+ G_CALLBACK (nautilus_window_key_bubble), NULL);
+}
+
+static void
+nautilus_window_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ NautilusWindow *self = NAUTILUS_WINDOW (object);
+
+ switch (prop_id)
+ {
+ case PROP_ACTIVE_SLOT:
+ {
+ g_value_set_object (value, G_OBJECT (nautilus_window_get_active_slot (self)));
+ }
+ break;
+
+ default:
+ {
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+ }
+}
+
+static void
+nautilus_window_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ NautilusWindow *self = NAUTILUS_WINDOW (object);
+
+ switch (prop_id)
+ {
+ case PROP_ACTIVE_SLOT:
+ {
+ nautilus_window_set_active_slot (self, NAUTILUS_WINDOW_SLOT (g_value_get_object (value)));
+ }
+ break;
+
+ default:
+ {
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+ }
+}
+
+static void
+nautilus_window_class_init (NautilusWindowClass *class)
+{
+ GObjectClass *oclass = G_OBJECT_CLASS (class);
+ GtkWidgetClass *wclass = GTK_WIDGET_CLASS (class);
+ GtkWindowClass *winclass = GTK_WINDOW_CLASS (class);
+
+ oclass->dispose = nautilus_window_dispose;
+ oclass->finalize = nautilus_window_finalize;
+ oclass->constructed = nautilus_window_constructed;
+ oclass->get_property = nautilus_window_get_property;
+ oclass->set_property = nautilus_window_set_property;
+
+ wclass->show = nautilus_window_show;
+ wclass->realize = nautilus_window_realize;
+ wclass->grab_focus = nautilus_window_grab_focus;
+
+ winclass->close_request = nautilus_window_close_request;
+
+ properties[PROP_ACTIVE_SLOT] =
+ g_param_spec_object ("active-slot",
+ NULL, NULL,
+ NAUTILUS_TYPE_WINDOW_SLOT,
+ G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
+
+ g_object_class_install_properties (oclass, N_PROPS, properties);
+
+ gtk_widget_class_set_template_from_resource (wclass,
+ "/org/gnome/nautilus/ui/nautilus-window.ui");
+ gtk_widget_class_bind_template_child (wclass, NautilusWindow, toolbar);
+ gtk_widget_class_bind_template_child (wclass, NautilusWindow, content_flap);
+ gtk_widget_class_bind_template_child (wclass, NautilusWindow, places_sidebar);
+ gtk_widget_class_bind_template_child (wclass, NautilusWindow, toast_overlay);
+ gtk_widget_class_bind_template_child (wclass, NautilusWindow, tab_view);
+
+ signals[SLOT_ADDED] =
+ g_signal_new ("slot-added",
+ G_TYPE_FROM_CLASS (class),
+ G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
+ 0,
+ NULL, NULL,
+ g_cclosure_marshal_VOID__OBJECT,
+ G_TYPE_NONE, 1, NAUTILUS_TYPE_WINDOW_SLOT);
+ signals[SLOT_REMOVED] =
+ g_signal_new ("slot-removed",
+ G_TYPE_FROM_CLASS (class),
+ G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
+ 0,
+ NULL, NULL,
+ g_cclosure_marshal_VOID__OBJECT,
+ G_TYPE_NONE, 1, NAUTILUS_TYPE_WINDOW_SLOT);
+
+ g_signal_connect_swapped (nautilus_preferences,
+ "changed::" NAUTILUS_PREFERENCES_MOUSE_BACK_BUTTON,
+ G_CALLBACK (mouse_back_button_changed),
+ NULL);
+
+ g_signal_connect_swapped (nautilus_preferences,
+ "changed::" NAUTILUS_PREFERENCES_MOUSE_FORWARD_BUTTON,
+ G_CALLBACK (mouse_forward_button_changed),
+ NULL);
+
+ g_signal_connect_swapped (nautilus_preferences,
+ "changed::" NAUTILUS_PREFERENCES_MOUSE_USE_EXTRA_BUTTONS,
+ G_CALLBACK (use_extra_mouse_buttons_changed),
+ NULL);
+}
+
+NautilusWindow *
+nautilus_window_new (void)
+{
+ return g_object_new (NAUTILUS_TYPE_WINDOW,
+ "icon-name", APPLICATION_ID,
+ NULL);
+}
+
+void
+nautilus_window_show_about_dialog (NautilusWindow *window)
+{
+ g_autofree gchar *module_names = nautilus_module_get_installed_module_names ();
+ g_autofree gchar *debug_info = NULL;
+
+ const gchar *designers[] =
+ {
+ "The GNOME Project",
+ NULL
+ };
+ const gchar *developers[] =
+ {
+ "The contributors to the Nautilus project",
+ NULL
+ };
+ const gchar *documenters[] =
+ {
+ "GNOME Documentation Team",
+ "Sun Microsystems",
+ NULL
+ };
+
+ if (module_names == NULL)
+ {
+ debug_info = g_strdup (_("No plugins currently installed."));
+ }
+ else
+ {
+ debug_info = g_strconcat (_("Currently installed plugins:"), "\n\n",
+ module_names, "\n\n",
+ _("For bug testing only, the following command can be used:"), "\n"
+ "NAUTILUS_DISABLE_PLUGINS=TRUE nautilus", NULL);
+ }
+
+ adw_show_about_window (window ? GTK_WINDOW (window) : NULL,
+ "application-name", _("Files"),
+ "application-icon", APPLICATION_ID,
+ "developer-name", _("The GNOME Project"),
+ "version", VERSION,
+ "website", "https://wiki.gnome.org/action/show/Apps/Files",
+ "issue-url", "https://gitlab.gnome.org/GNOME/nautilus/-/issues/new",
+ "debug-info", debug_info,
+ "copyright", "© 1999 The Files Authors",
+ "license-type", GTK_LICENSE_GPL_3_0,
+ "designers", designers,
+ "developers", developers,
+ "documenters", documenters,
+ /* Translators should localize the following string
+ * which will be displayed at the bottom of the about
+ * box to give credit to the translator(s).
+ */
+ "translator-credits", _("translator-credits"),
+ NULL);
+}
+
+void
+nautilus_window_search (NautilusWindow *window,
+ NautilusQuery *query)
+{
+ NautilusWindowSlot *active_slot;
+
+ active_slot = nautilus_window_get_active_slot (window);
+ if (active_slot)
+ {
+ nautilus_window_slot_search (active_slot, query);
+ }
+ else
+ {
+ g_warning ("Trying search on a slot but no active slot present");
+ }
+}
diff --git a/src/nautilus-window.h b/src/nautilus-window.h
new file mode 100644
index 0000000..2f7add9
--- /dev/null
+++ b/src/nautilus-window.h
@@ -0,0 +1,121 @@
+
+/*
+ * Nautilus
+ *
+ * Copyright (C) 1999, 2000 Red Hat, Inc.
+ * Copyright (C) 1999, 2000, 2001 Eazel, Inc.
+ *
+ * Nautilus is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * Nautilus is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Elliot Lee <sopwith@redhat.com>
+ * Darin Adler <darin@bentspoon.com>
+ *
+ */
+/* nautilus-window.h: Interface of the main window object */
+
+#pragma once
+
+#include <gtk/gtk.h>
+#include <libadwaita-1/adwaita.h>
+
+#include "nautilus-types.h"
+
+G_BEGIN_DECLS
+
+#define NAUTILUS_TYPE_WINDOW (nautilus_window_get_type ())
+G_DECLARE_FINAL_TYPE (NautilusWindow, nautilus_window, NAUTILUS, WINDOW, AdwApplicationWindow);
+
+typedef gboolean (* NautilusWindowGoToCallback) (NautilusWindow *window,
+ GFile *location,
+ GError *error,
+ gpointer user_data);
+
+typedef void (* NautilusWindowHandleExported) (NautilusWindow *window,
+ const char *handle,
+ guint xid,
+ gpointer user_data);
+
+/* window geometry */
+/* Min values are very small, and a Nautilus window at this tiny size is *almost*
+ * completely unusable. However, if all the extra bits (sidebar, location bar, etc)
+ * are turned off, you can see an icon or two at this size. See bug 5946.
+ */
+
+#define NAUTILUS_WINDOW_MIN_WIDTH 200
+#define NAUTILUS_WINDOW_MIN_HEIGHT 200
+#define NAUTILUS_WINDOW_DEFAULT_WIDTH 890
+#define NAUTILUS_WINDOW_DEFAULT_HEIGHT 550
+
+typedef enum
+{
+ NAUTILUS_NAVIGATION_DIRECTION_NONE,
+ NAUTILUS_NAVIGATION_DIRECTION_BACK,
+ NAUTILUS_NAVIGATION_DIRECTION_FORWARD
+} NautilusNavigationDirection;
+
+NautilusWindow * nautilus_window_new (void);
+void nautilus_window_close (NautilusWindow *window);
+
+void nautilus_window_open_location_full (NautilusWindow *window,
+ GFile *location,
+ NautilusOpenFlags flags,
+ GList *selection,
+ NautilusWindowSlot *target_slot);
+
+void nautilus_window_new_tab (NautilusWindow *window);
+NautilusWindowSlot * nautilus_window_get_active_slot (NautilusWindow *window);
+void nautilus_window_set_active_slot (NautilusWindow *window,
+ NautilusWindowSlot *slot);
+GList * nautilus_window_get_slots (NautilusWindow *window);
+void nautilus_window_slot_close (NautilusWindow *window,
+ NautilusWindowSlot *slot);
+
+void nautilus_window_sync_location_widgets (NautilusWindow *window);
+
+void nautilus_window_reset_menus (NautilusWindow *window);
+
+AdwTabView * nautilus_window_get_tab_view (NautilusWindow *window);
+
+void nautilus_window_show_about_dialog (NautilusWindow *window);
+
+GtkWidget *nautilus_window_get_toolbar (NautilusWindow *window);
+
+/* sync window GUI with current slot. Used when changing slots,
+ * and when updating the slot state.
+ */
+void nautilus_window_sync_allow_stop (NautilusWindow *window,
+ NautilusWindowSlot *slot);
+void nautilus_window_sync_title (NautilusWindow *window,
+ NautilusWindowSlot *slot);
+
+void nautilus_window_show_operation_notification (NautilusWindow *window,
+ gchar *main_label,
+ GFile *folder_to_open);
+
+void nautilus_window_search (NautilusWindow *window,
+ NautilusQuery *query);
+
+void nautilus_window_initialize_slot (NautilusWindow *window,
+ NautilusWindowSlot *slot,
+ NautilusOpenFlags flags);
+
+gboolean nautilus_window_export_handle (NautilusWindow *window,
+ NautilusWindowHandleExported callback,
+ gpointer user_data);
+void nautilus_window_unexport_handle (NautilusWindow *window);
+
+void nautilus_window_back_or_forward_in_new_tab (NautilusWindow *window,
+ NautilusNavigationDirection back);
+
+G_END_DECLS
diff --git a/src/nautilus-x-content-bar.c b/src/nautilus-x-content-bar.c
new file mode 100644
index 0000000..689e129
--- /dev/null
+++ b/src/nautilus-x-content-bar.c
@@ -0,0 +1,357 @@
+/*
+ * Copyright (C) 2008 Red Hat, Inc.
+ * Copyright (C) 2006 Paolo Borelli <pborelli@katamail.com>
+ *
+ * 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: David Zeuthen <davidz@redhat.com>
+ * Paolo Borelli <pborelli@katamail.com>
+ *
+ */
+
+#include "config.h"
+
+#include <glib/gi18n.h>
+#include <gtk/gtk.h>
+#include <string.h>
+
+#include "nautilus-x-content-bar.h"
+#include "nautilus-icon-info.h"
+#include "nautilus-file-utilities.h"
+#include "nautilus-program-choosing.h"
+
+struct _NautilusXContentBar
+{
+ AdwBin parent_instance;
+ GtkWidget *label;
+
+ char **x_content_types;
+ GMount *mount;
+};
+
+enum
+{
+ PROP_0,
+ PROP_MOUNT,
+ PROP_X_CONTENT_TYPES,
+};
+
+enum
+{
+ CONTENT_BAR_RESPONSE_APP = 1
+};
+
+G_DEFINE_TYPE (NautilusXContentBar, nautilus_x_content_bar, ADW_TYPE_BIN)
+
+static void
+content_bar_response_cb (GtkInfoBar *infobar,
+ gint response_id,
+ gpointer user_data)
+{
+ GAppInfo *default_app;
+ NautilusXContentBar *bar = user_data;
+
+ if (response_id < 0)
+ {
+ return;
+ }
+
+ if (bar->x_content_types == NULL ||
+ bar->mount == NULL)
+ {
+ return;
+ }
+
+ /* FIXME */
+ default_app = g_app_info_get_default_for_type (bar->x_content_types[response_id], FALSE);
+ if (default_app != NULL)
+ {
+ nautilus_launch_application_for_mount (default_app, bar->mount,
+ GTK_WINDOW (gtk_widget_get_root (GTK_WIDGET (bar))));
+ g_object_unref (default_app);
+ }
+}
+
+static void
+nautilus_x_content_bar_set_x_content_types (NautilusXContentBar *bar,
+ const char * const *x_content_types)
+{
+ char *message = NULL;
+ guint num_types;
+ guint n;
+ GPtrArray *types;
+ GPtrArray *apps;
+ GAppInfo *default_app;
+
+ g_strfreev (bar->x_content_types);
+
+ if (!should_handle_content_types (x_content_types))
+ {
+ g_warning ("Content types in content types bar cannot be handled. Check before creating the content bar if they can be handled.");
+ return;
+ }
+
+ types = g_ptr_array_new ();
+ apps = g_ptr_array_new ();
+ g_ptr_array_set_free_func (apps, g_object_unref);
+ for (n = 0; x_content_types[n] != NULL; n++)
+ {
+ if (!should_handle_content_type (x_content_types[n]))
+ {
+ continue;
+ }
+
+ default_app = g_app_info_get_default_for_type (x_content_types[n], FALSE);
+ g_ptr_array_add (types, g_strdup (x_content_types[n]));
+ g_ptr_array_add (apps, default_app);
+ }
+
+ num_types = types->len;
+ g_ptr_array_add (types, NULL);
+
+ bar->x_content_types = (char **) g_ptr_array_free (types, FALSE);
+
+ switch (num_types)
+ {
+ case 1:
+ {
+ message = get_message_for_content_type (bar->x_content_types[0]);
+ }
+ break;
+
+ case 2:
+ {
+ message = get_message_for_two_content_types ((const char * const *) bar->x_content_types);
+ }
+ break;
+
+ default:
+ {
+ message = g_strdup (_("Open with:"));
+ }
+ break;
+ }
+
+ gtk_label_set_text (GTK_LABEL (bar->label), message);
+ g_free (message);
+
+ gtk_widget_show (bar->label);
+
+ for (n = 0; bar->x_content_types[n] != NULL; n++)
+ {
+ const char *name;
+ GIcon *icon;
+ GtkWidget *image;
+ GtkWidget *info_bar;
+ GtkWidget *button;
+ GAppInfo *app;
+ gboolean has_app;
+ guint i;
+ GtkWidget *box;
+
+ default_app = g_ptr_array_index (apps, n);
+ has_app = FALSE;
+
+ for (i = 0; i < n; i++)
+ {
+ app = g_ptr_array_index (apps, i);
+ if (g_app_info_equal (app, default_app))
+ {
+ has_app = TRUE;
+ break;
+ }
+ }
+
+ if (has_app)
+ {
+ continue;
+ }
+
+ icon = g_app_info_get_icon (default_app);
+ if (icon != NULL)
+ {
+ image = gtk_image_new_from_gicon (icon);
+ }
+ else
+ {
+ image = NULL;
+ }
+
+ name = g_app_info_get_name (default_app);
+ info_bar = adw_bin_get_child (ADW_BIN (bar));
+ button = gtk_info_bar_add_button (GTK_INFO_BAR (info_bar), name, n);
+ box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 6);
+
+ if (image != NULL)
+ {
+ gtk_box_append (GTK_BOX (box), image);
+ }
+ gtk_box_append (GTK_BOX (box), gtk_label_new (name));
+
+ gtk_button_set_child (GTK_BUTTON (button), box);
+
+ gtk_widget_show (button);
+ }
+
+ g_ptr_array_free (apps, TRUE);
+}
+
+static void
+nautilus_x_content_bar_set_mount (NautilusXContentBar *bar,
+ GMount *mount)
+{
+ if (bar->mount != NULL)
+ {
+ g_object_unref (bar->mount);
+ }
+ bar->mount = mount != NULL ? g_object_ref (mount) : NULL;
+}
+
+
+static void
+nautilus_x_content_bar_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ NautilusXContentBar *bar = NAUTILUS_X_CONTENT_BAR (object);
+
+ switch (prop_id)
+ {
+ case PROP_MOUNT:
+ {
+ nautilus_x_content_bar_set_mount (bar, G_MOUNT (g_value_get_object (value)));
+ }
+ break;
+
+ case PROP_X_CONTENT_TYPES:
+ {
+ nautilus_x_content_bar_set_x_content_types (bar, g_value_get_boxed (value));
+ }
+ break;
+
+ default:
+ {
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+ break;
+ }
+}
+
+static void
+nautilus_x_content_bar_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ NautilusXContentBar *bar = NAUTILUS_X_CONTENT_BAR (object);
+
+ switch (prop_id)
+ {
+ case PROP_MOUNT:
+ {
+ g_value_set_object (value, bar->mount);
+ }
+ break;
+
+ case PROP_X_CONTENT_TYPES:
+ {
+ g_value_set_boxed (value, &bar->x_content_types);
+ }
+ break;
+
+ default:
+ {
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+ break;
+ }
+}
+
+static void
+nautilus_x_content_bar_finalize (GObject *object)
+{
+ NautilusXContentBar *bar = NAUTILUS_X_CONTENT_BAR (object);
+
+ g_strfreev (bar->x_content_types);
+ if (bar->mount != NULL)
+ {
+ g_object_unref (bar->mount);
+ }
+
+ G_OBJECT_CLASS (nautilus_x_content_bar_parent_class)->finalize (object);
+}
+
+static void
+nautilus_x_content_bar_class_init (NautilusXContentBarClass *klass)
+{
+ GObjectClass *object_class;
+
+ object_class = G_OBJECT_CLASS (klass);
+ object_class->get_property = nautilus_x_content_bar_get_property;
+ object_class->set_property = nautilus_x_content_bar_set_property;
+ object_class->finalize = nautilus_x_content_bar_finalize;
+
+ g_object_class_install_property (object_class,
+ PROP_MOUNT,
+ g_param_spec_object (
+ "mount",
+ "The GMount to run programs for",
+ "The GMount to run programs for",
+ G_TYPE_MOUNT,
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT));
+
+ g_object_class_install_property (object_class,
+ PROP_X_CONTENT_TYPES,
+ g_param_spec_boxed ("x-content-types",
+ "The x-content types for the cluebar",
+ "The x-content types for the cluebar",
+ G_TYPE_STRV,
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT));
+}
+
+static void
+nautilus_x_content_bar_init (NautilusXContentBar *bar)
+{
+ GtkWidget *info_bar;
+ PangoAttrList *attrs;
+
+ info_bar = gtk_info_bar_new ();
+ gtk_info_bar_set_message_type (GTK_INFO_BAR (info_bar), GTK_MESSAGE_QUESTION);
+ gtk_widget_show (info_bar);
+ adw_bin_set_child (ADW_BIN (bar), info_bar);
+
+ attrs = pango_attr_list_new ();
+ pango_attr_list_insert (attrs, pango_attr_weight_new (PANGO_WEIGHT_BOLD));
+ bar->label = gtk_label_new (NULL);
+ gtk_label_set_attributes (GTK_LABEL (bar->label), attrs);
+ pango_attr_list_unref (attrs);
+
+ gtk_label_set_ellipsize (GTK_LABEL (bar->label), PANGO_ELLIPSIZE_END);
+ gtk_info_bar_add_child (GTK_INFO_BAR (info_bar), bar->label);
+
+ g_signal_connect (info_bar, "response",
+ G_CALLBACK (content_bar_response_cb),
+ bar);
+}
+
+GtkWidget *
+nautilus_x_content_bar_new (GMount *mount,
+ const char * const *x_content_types)
+{
+ return g_object_new (NAUTILUS_TYPE_X_CONTENT_BAR,
+ "mount", mount,
+ "x-content-types", x_content_types,
+ NULL);
+}
diff --git a/src/nautilus-x-content-bar.h b/src/nautilus-x-content-bar.h
new file mode 100644
index 0000000..11f05ec
--- /dev/null
+++ b/src/nautilus-x-content-bar.h
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2008 Red Hat, Inc.
+ * Copyright (C) 2006 Paolo Borelli <pborelli@katamail.com>
+ *
+ * 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: David Zeuthen <davidz@redhat.com>
+ * Paolo Borelli <pborelli@katamail.com>
+ *
+ */
+
+#pragma once
+
+#include <gtk/gtk.h>
+#include <gio/gio.h>
+#include <libadwaita-1/adwaita.h>
+
+G_BEGIN_DECLS
+
+#define NAUTILUS_TYPE_X_CONTENT_BAR (nautilus_x_content_bar_get_type ())
+
+G_DECLARE_FINAL_TYPE (NautilusXContentBar, nautilus_x_content_bar, NAUTILUS, X_CONTENT_BAR, AdwBin)
+
+GtkWidget *nautilus_x_content_bar_new (GMount *mount,
+ const char * const *x_content_types);
+
+G_END_DECLS
diff --git a/src/resources/Checkerboard.png b/src/resources/Checkerboard.png
new file mode 100644
index 0000000..3e43acd
--- /dev/null
+++ b/src/resources/Checkerboard.png
Binary files differ
diff --git a/src/resources/gtk/help-overlay.ui b/src/resources/gtk/help-overlay.ui
new file mode 100644
index 0000000..42bad9f
--- /dev/null
+++ b/src/resources/gtk/help-overlay.ui
@@ -0,0 +1,406 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+ <object class="GtkShortcutsWindow" id="help_overlay">
+ <property name="modal">True</property>
+ <child>
+ <object class="GtkShortcutsSection">
+ <property name="visible">True</property>
+ <property name="section-name">shortcuts</property>
+ <property name="max-height">12</property>
+ <child>
+ <object class="GtkShortcutsGroup">
+ <property name="visible">True</property>
+ <property name="title" translatable="yes" context="shortcut window">General</property>
+ <child>
+ <object class="GtkShortcutsShortcut">
+ <property name="visible">True</property>
+ <property name="title" translatable="yes" context="shortcut window">New window</property>
+ <property name="accelerator">&lt;Primary&gt;N</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkShortcutsShortcut">
+ <property name="visible">True</property>
+ <property name="title" translatable="yes" context="shortcut window">Close window or tab</property>
+ <property name="accelerator">&lt;Primary&gt;W</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkShortcutsShortcut">
+ <property name="visible">True</property>
+ <property name="title" translatable="yes" context="shortcut window">Quit</property>
+ <property name="accelerator">&lt;Primary&gt;Q</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkShortcutsShortcut">
+ <property name="visible">True</property>
+ <property name="title" translatable="yes" context="shortcut window">Search</property>
+ <property name="accelerator">&lt;Primary&gt;F</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkShortcutsShortcut">
+ <property name="visible">True</property>
+ <property name="title" translatable="yes" context="shortcut window">Bookmark current location</property>
+ <property name="accelerator">&lt;Primary&gt;D</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkShortcutsShortcut">
+ <property name="visible">True</property>
+ <property name="title" translatable="yes" context="shortcut window">Show help</property>
+ <property name="accelerator">F1</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkShortcutsShortcut">
+ <property name="visible">True</property>
+ <property name="title" translatable="yes" context="shortcut window">Shortcuts</property>
+ <property name="accelerator">&lt;Primary&gt;question</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkShortcutsShortcut">
+ <property name="visible">True</property>
+ <property name="title" translatable="yes" context="shortcut window">Undo</property>
+ <property name="accelerator">&lt;Primary&gt;z</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkShortcutsShortcut">
+ <property name="visible">True</property>
+ <property name="title" translatable="yes" context="shortcut window">Redo</property>
+ <property name="accelerator">&lt;shift&gt;&lt;Primary&gt;z</property>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child>
+ <object class="GtkShortcutsGroup">
+ <property name="visible">True</property>
+ <property name="title" translatable="yes" context="shortcut window">Opening</property>
+ <child>
+ <object class="GtkShortcutsShortcut">
+ <property name="visible">True</property>
+ <property name="title" translatable="yes" context="shortcut window">Open</property>
+ <property name="accelerator">Return &lt;Primary&gt;O</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkShortcutsShortcut">
+ <property name="visible">True</property>
+ <property name="title" translatable="yes" context="shortcut window">Open in new tab</property>
+ <property name="accelerator">&lt;Primary&gt;Return</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkShortcutsShortcut">
+ <property name="visible">True</property>
+ <property name="title" translatable="yes" context="shortcut window">Open in new window</property>
+ <property name="accelerator">&lt;shift&gt;Return</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkShortcutsShortcut">
+ <property name="visible">True</property>
+ <property name="title" translatable="yes" context="shortcut window">Open item location (search and recent only)</property>
+ <property name="accelerator">&lt;alt&gt;&lt;Primary&gt;O</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkShortcutsShortcut">
+ <property name="visible">True</property>
+ <property name="title" translatable="yes" context="shortcut window">Open with default application</property>
+ <property name="accelerator">&lt;Primary&gt;O &lt;alt&gt;Down</property>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child>
+ <object class="GtkShortcutsGroup">
+ <property name="visible">True</property>
+ <property name="title" translatable="yes" context="shortcut window">Tabs</property>
+ <child>
+ <object class="GtkShortcutsShortcut">
+ <property name="visible">True</property>
+ <property name="title" translatable="yes" context="shortcut window">New tab</property>
+ <property name="accelerator">&lt;Primary&gt;T</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkShortcutsShortcut">
+ <property name="visible">True</property>
+ <property name="title" translatable="yes" context="shortcut window">Go to previous tab</property>
+ <property name="accelerator">&lt;Primary&gt;Page_Up</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkShortcutsShortcut">
+ <property name="visible">True</property>
+ <property name="title" translatable="yes" context="shortcut window">Go to next tab</property>
+ <property name="accelerator">&lt;Primary&gt;Page_Down</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkShortcutsShortcut">
+ <property name="visible">True</property>
+ <property name="title" translatable="yes" context="shortcut window">Open tab</property>
+ <property name="accelerator">&lt;alt&gt;0...8</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkShortcutsShortcut">
+ <property name="visible">True</property>
+ <property name="title" translatable="yes" context="shortcut window">Move tab left</property>
+ <property name="accelerator">&lt;shift&gt;&lt;Primary&gt;Page_Up</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkShortcutsShortcut">
+ <property name="visible">True</property>
+ <property name="title" translatable="yes" context="shortcut window">Move tab right</property>
+ <property name="accelerator">&lt;shift&gt;&lt;Primary&gt;Page_Down</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkShortcutsShortcut">
+ <property name="visible">True</property>
+ <property name="title" translatable="yes" context="shortcut window">Restore tab</property>
+ <property name="accelerator">&lt;shift&gt;&lt;Primary&gt;T</property>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child>
+ <object class="GtkShortcutsGroup">
+ <property name="visible">True</property>
+ <property name="title" translatable="yes" context="shortcut window">Navigation</property>
+ <child>
+ <object class="GtkShortcutsShortcut">
+ <property name="visible">True</property>
+ <property name="title" translatable="yes" context="shortcut window">Go back</property>
+ <property name="accelerator">&lt;alt&gt;Left</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkShortcutsShortcut">
+ <property name="visible">True</property>
+ <property name="title" translatable="yes" context="shortcut window">Go forward</property>
+ <property name="accelerator">&lt;alt&gt;Right</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkShortcutsShortcut">
+ <property name="visible">True</property>
+ <property name="title" translatable="yes" context="shortcut window">Go up</property>
+ <property name="accelerator">&lt;alt&gt;Up</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkShortcutsShortcut">
+ <property name="visible">True</property>
+ <property name="title" translatable="yes" context="shortcut window">Go down</property>
+ <property name="accelerator">&lt;alt&gt;Down</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkShortcutsShortcut">
+ <property name="visible">True</property>
+ <property name="title" translatable="yes" context="shortcut window">Go to home folder</property>
+ <property name="accelerator">&lt;alt&gt;Home</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkShortcutsShortcut">
+ <property name="visible">True</property>
+ <property name="title" translatable="yes" context="shortcut window">Enter location</property>
+ <property name="accelerator">&lt;Primary&gt;L</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkShortcutsShortcut">
+ <property name="visible">True</property>
+ <property name="title" translatable="yes" context="shortcut window">Location bar with root location</property>
+ <property name="accelerator">slash</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkShortcutsShortcut">
+ <property name="visible">True</property>
+ <property name="title" translatable="yes" context="shortcut window">Location bar with home location</property>
+ <property name="accelerator">asciitilde</property>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child>
+ <object class="GtkShortcutsGroup">
+ <property name="visible">True</property>
+ <property name="title" translatable="yes" context="shortcut window">View</property>
+ <child>
+ <object class="GtkShortcutsShortcut">
+ <property name="visible">True</property>
+ <property name="title" translatable="yes" context="shortcut window">Zoom in</property>
+ <property name="accelerator">&lt;Primary&gt;plus</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkShortcutsShortcut">
+ <property name="visible">True</property>
+ <property name="title" translatable="yes" context="shortcut window">Zoom out</property>
+ <property name="accelerator">&lt;Primary&gt;minus</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkShortcutsShortcut">
+ <property name="visible">True</property>
+ <property name="title" translatable="yes" context="shortcut window">Reset zoom</property>
+ <property name="accelerator">&lt;Primary&gt;0</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkShortcutsShortcut">
+ <property name="visible">True</property>
+ <property name="title" translatable="yes" context="shortcut window">Refresh view</property>
+ <property name="accelerator">F5 &lt;Primary&gt;R</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkShortcutsShortcut">
+ <property name="visible">True</property>
+ <property name="title" translatable="yes" context="shortcut window">Show/hide hidden files</property>
+ <property name="accelerator">&lt;Primary&gt;H</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkShortcutsShortcut">
+ <property name="visible">True</property>
+ <property name="title" translatable="yes" context="shortcut window">Show/hide sidebar</property>
+ <property name="accelerator">F9</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkShortcutsShortcut">
+ <property name="visible">True</property>
+ <property name="title" translatable="yes" context="shortcut window">Show/hide action menu</property>
+ <property name="accelerator">F10</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkShortcutsShortcut">
+ <property name="visible">True</property>
+ <property name="title" translatable="yes" context="shortcut window">List view</property>
+ <property name="accelerator">&lt;Primary&gt;1</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkShortcutsShortcut">
+ <property name="visible">True</property>
+ <property name="title" translatable="yes" context="shortcut window">Grid view</property>
+ <property name="accelerator">&lt;Primary&gt;2</property>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child>
+ <object class="GtkShortcutsGroup">
+ <property name="visible">True</property>
+ <property name="title" translatable="yes" context="shortcut window">Editing</property>
+ <child>
+ <object class="GtkShortcutsShortcut">
+ <property name="visible">True</property>
+ <property name="title" translatable="yes" context="shortcut window">Create folder</property>
+ <property name="accelerator">&lt;shift&gt;&lt;Primary&gt;N</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkShortcutsShortcut">
+ <property name="visible">True</property>
+ <property name="title" translatable="yes" context="shortcut window">Rename</property>
+ <property name="accelerator">F2</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkShortcutsShortcut">
+ <property name="visible">True</property>
+ <property name="title" translatable="yes" context="shortcut window">Move to trash</property>
+ <property name="accelerator">Delete</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkShortcutsShortcut">
+ <property name="visible">True</property>
+ <property name="title" translatable="yes" context="shortcut window">Delete permanently</property>
+ <property name="accelerator">&lt;shift&gt;Delete</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkShortcutsShortcut">
+ <property name="visible">True</property>
+ <property name="title" translatable="yes" context="shortcut window">Create link to copied item</property>
+ <property name="accelerator">&lt;Primary&gt;M</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkShortcutsShortcut">
+ <property name="visible">True</property>
+ <property name="title" translatable="yes" context="shortcut window">Create link to selected item</property>
+ <property name="accelerator">&lt;Primary&gt;&lt;shift&gt;M</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkShortcutsShortcut">
+ <property name="visible">True</property>
+ <property name="title" translatable="yes" context="shortcut window">Cut</property>
+ <property name="accelerator">&lt;Primary&gt;X</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkShortcutsShortcut">
+ <property name="visible">True</property>
+ <property name="title" translatable="yes" context="shortcut window">Copy</property>
+ <property name="accelerator">&lt;Primary&gt;C</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkShortcutsShortcut">
+ <property name="visible">True</property>
+ <property name="title" translatable="yes" context="shortcut window">Paste</property>
+ <property name="accelerator">&lt;Primary&gt;V</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkShortcutsShortcut">
+ <property name="visible">True</property>
+ <property name="title" translatable="yes" context="shortcut window">Select all</property>
+ <property name="accelerator">&lt;Primary&gt;A</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkShortcutsShortcut">
+ <property name="visible">True</property>
+ <property name="title" translatable="yes" context="shortcut window">Invert selection</property>
+ <property name="accelerator">&lt;shift&gt;&lt;Primary&gt;I</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkShortcutsShortcut">
+ <property name="visible">True</property>
+ <property name="title" translatable="yes" context="shortcut window">Select items matching</property>
+ <property name="accelerator">&lt;Primary&gt;S</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkShortcutsShortcut">
+ <property name="visible">True</property>
+ <property name="title" translatable="yes" context="shortcut window">Show item properties</property>
+ <property name="accelerator">&lt;Primary&gt;I &lt;alt&gt;Return</property>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+</interface>
diff --git a/src/resources/icons/external-link-symbolic.svg b/src/resources/icons/external-link-symbolic.svg
new file mode 100644
index 0000000..a24ed4b
--- /dev/null
+++ b/src/resources/icons/external-link-symbolic.svg
@@ -0,0 +1,2 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" height="16px" viewBox="0 0 16 16" width="16px"><g fill="#2e3436"><path d="m 2 3 v 11 h 11 v -4 h -2 v 2 h -7 v -7 h 2 v -2 z m 0 0"/><path d="m 9 2 c -0.550781 0 -1 0.449219 -1 1 s 0.449219 1 1 1 h 3 v 3 c 0 0.550781 0.449219 1 1 1 s 1 -0.449219 1 -1 v -4 c 0 -0.550781 -0.449219 -1 -1 -1 z m 0 0"/><path d="m 13 2 h 1 v 1 h -1 z m 0 0"/><path d="m 12.292969 2.289062 l -4.5 4.46875 c -0.390625 0.390626 -0.390625 1.027344 0 1.414063 c 0.390625 0.394531 1.023437 0.394531 1.414062 0.007813 l 4.5 -4.46875 c 0.390625 -0.390626 0.390625 -1.027344 0 -1.414063 c -0.386719 -0.394531 -1.019531 -0.394531 -1.414062 -0.007813 z m 0 0"/><path d="m 13 7 h 1 v 1 h -1 z m 0 0"/><path d="m 8 2 h 1 v 1 h -1 z m 0 0"/></g></svg>
diff --git a/src/resources/icons/funnel-symbolic.svg b/src/resources/icons/funnel-symbolic.svg
new file mode 100644
index 0000000..6e01001
--- /dev/null
+++ b/src/resources/icons/funnel-symbolic.svg
@@ -0,0 +1,2 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" height="16px" viewBox="0 0 16 16" width="16px"><path d="m 0 1.007812 h 15 l -6 7 v 6 l -3 2 v -8 z m 0 0" fill="#222222"/></svg>
diff --git a/src/resources/icons/quotation-symbolic.svg b/src/resources/icons/quotation-symbolic.svg
new file mode 100644
index 0000000..cc92cdd
--- /dev/null
+++ b/src/resources/icons/quotation-symbolic.svg
@@ -0,0 +1,2 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" height="16px" viewBox="0 0 16 16" width="16px"><path d="m 1 3 l -1 3 v 2.5 c 0 0.277344 0.222656 0.5 0.5 0.5 h 2 c 0.277344 0 0.5 -0.222656 0.5 -0.5 v -2 c 0 -0.277344 -0.222656 -0.5 -0.5 -0.5 h -0.5 l 1 -3 z m 4 0 l -1 3 v 2.5 c 0 0.277344 0.222656 0.5 0.5 0.5 h 2 c 0.277344 0 0.5 -0.222656 0.5 -0.5 v -2 c 0 -0.277344 -0.222656 -0.5 -0.5 -0.5 h -0.5 l 1 -3 z m 4.5 4 c -0.277344 0 -0.5 0.222656 -0.5 0.5 v 2 c 0 0.277344 0.222656 0.5 0.5 0.5 h 0.5 l -1 3 h 2 l 1 -3 v -2.5 c 0 -0.277344 -0.222656 -0.5 -0.5 -0.5 z m 4 0 c -0.277344 0 -0.5 0.222656 -0.5 0.5 v 2 c 0 0.277344 0.222656 0.5 0.5 0.5 h 0.5 l -1 3 h 2 l 1 -3 v -2.5 c 0 -0.277344 -0.222656 -0.5 -0.5 -0.5 z m 0 0" fill="#222222"/></svg>
diff --git a/src/resources/nautilus.gresource.xml b/src/resources/nautilus.gresource.xml
new file mode 100644
index 0000000..bfe8b2b
--- /dev/null
+++ b/src/resources/nautilus.gresource.xml
@@ -0,0 +1,45 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<gresources>
+ <gresource prefix="/org/gnome/nautilus">
+ <file compressed="true">ui/nautilus-preferences-window.ui</file>
+ <file compressed="true">ui/nautilus-search-popover.ui</file>
+ <file>ui/nautilus-app-chooser.ui</file>
+ <file>ui/nautilus-pathbar-context-menu.ui</file>
+ <file>ui/nautilus-toolbar.ui</file>
+ <file>ui/nautilus-history-controls.ui</file>
+ <file>ui/nautilus-progress-indicator.ui</file>
+ <file>ui/nautilus-view-controls.ui</file>
+ <file>ui/nautilus-toolbar-view-menu.ui</file>
+ <file>ui/nautilus-column-chooser.ui</file>
+ <file>ui/nautilus-list-view-column-editor.ui</file>
+ <file>ui/nautilus-create-folder-dialog.ui</file>
+ <file>ui/nautilus-compress-dialog.ui</file>
+ <file>ui/nautilus-rename-file-popover.ui</file>
+ <file>ui/nautilus-files-view-context-menus.ui</file>
+ <file>ui/nautilus-progress-info-widget.ui</file>
+ <file>ui/nautilus-window.ui</file>
+ <file>gtk/help-overlay.ui</file>
+ <file>ui/nautilus-batch-rename-dialog.ui</file>
+ <file>ui/nautilus-properties-window.ui</file>
+ <file>ui/nautilus-file-properties-change-permissions.ui</file>
+ <file>ui/nautilus-file-conflict-dialog.ui</file>
+ <file>ui/nautilus-files-view-select-items.ui</file>
+ <file>ui/nautilus-files-view.ui</file>
+ <file>ui/nautilus-operations-ui-manager-request-passphrase.ui</file>
+ <file>ui/nautilus-grid-cell.ui</file>
+ <file>ui/nautilus-name-cell.ui</file>
+ <file alias="gtk/ui/nautilusgtksidebarrow.ui">../gtk/nautilusgtksidebarrow.ui</file>
+ <file alias="gtk/ui/nautilusgtkplacesview.ui">../gtk/nautilusgtkplacesview.ui</file>
+ <file alias="gtk/ui/nautilusgtkplacesviewrow.ui">../gtk/nautilusgtkplacesviewrow.ui</file>
+ <file alias="icons/filmholes.png">../../icons/filmholes.png</file>
+ <file>style.css</file>
+ <file>style-hc.css</file>
+ <file>text-x-preview.png</file>
+ <file>Checkerboard.png</file>
+ </gresource>
+ <gresource prefix="/org/gnome/nautilus/icons/scalable/actions/">
+ <file alias="quotation-symbolic.svg" preprocess="xml-stripblanks">icons/quotation-symbolic.svg</file>
+ <file alias="funnel-symbolic.svg" preprocess="xml-stripblanks">icons/funnel-symbolic.svg</file>
+ <file alias="external-link-symbolic.svg" preprocess="xml-stripblanks">icons/external-link-symbolic.svg</file>
+ </gresource>
+</gresources>
diff --git a/src/resources/style-hc.css b/src/resources/style-hc.css
new file mode 100644
index 0000000..54e2cff
--- /dev/null
+++ b/src/resources/style-hc.css
@@ -0,0 +1,16 @@
+#NautilusPathBar {
+ box-shadow: inset 0 0 0 1px @borders;
+}
+
+#NautilusPathButton:not(:hover):not(:drop(active)),
+#NautilusPathButton.current-dir:not(:drop(active)) {
+ box-shadow: none;
+}
+
+#NautilusQueryEditorTag {
+ box-shadow: inset 0 0 0 1px @borders;
+}
+
+.disk-space-free {
+ color: alpha(currentColor, 0.3);
+}
diff --git a/src/resources/style.css b/src/resources/style.css
new file mode 100644
index 0000000..54cf229
--- /dev/null
+++ b/src/resources/style.css
@@ -0,0 +1,301 @@
+/* Toolbar */
+
+@keyframes needs_attention_keyframes {
+ 0% { }
+ 10% { background-color: @accent_bg_color; border-radius: 999999px; }
+ 100% { }
+}
+
+.nautilus-operations-button-needs-attention {
+ animation: needs_attention_keyframes 2s ease-in-out;
+}
+.nautilus-operations-button-needs-attention-multiple {
+ animation: needs_attention_keyframes 3s ease-in-out;
+ animation-iteration-count: 3;
+}
+
+/* Remove white background and highlight on hover which GTK adds by default
+ * to GtkListBox. TODO: Switch to GtkListView and drop this CSS hack. */
+.operations-list,
+.operations-list > :hover {
+ background: none;
+}
+
+/* Path bar */
+
+#NautilusPathBar {
+ background-color: alpha(currentColor, 0.1);
+ border-radius: 6px;
+}
+#NautilusPathBar > menubutton {
+ margin: 0px;
+}
+
+#NautilusPathBar > scrolledwindow undershoot.left {
+ background: linear-gradient(to right, @headerbar_shade_color 6px, alpha(@headerbar_shade_color, 0) 24px);
+ border-left: solid 1px @borders;
+}
+#NautilusPathBar > scrolledwindow undershoot.right {
+ background: linear-gradient(to left, @headerbar_shade_color 6px, alpha(@headerbar_shade_color, 0) 24px);
+ border-right: solid 1px @borders;
+}
+
+/* Match sidebar's rounded corners on the "start" side. */
+#NautilusPathBar > scrolledwindow:dir(ltr) undershoot.left {
+ border-radius: 6px 0px 0px 6px;
+}
+#NautilusPathBar > scrolledwindow:dir(rtl) undershoot.right {
+ border-radius: 0px 6px 6px 0px;
+}
+
+#NautilusPathButton {
+ margin: 3px;
+ border-radius: 4px;
+ padding-top: 0px;
+ padding-bottom: 0px;
+}
+
+#NautilusPathButton:not(:hover),
+#NautilusPathButton.current-dir
+{
+ background: none;
+}
+
+#NautilusPathButton:not(.current-dir):not(:backdrop):hover label,
+#NautilusPathButton:not(.current-dir):not(:backdrop):hover image {
+ opacity: 1;
+}
+
+/* Search bar */
+
+#NautilusQueryEditor > * {
+ margin-top: 5px;
+ margin-bottom: 5px;
+}
+
+#NautilusQueryEditorTag > button,
+#NautilusQueryEditor > menubutton > button {
+ min-width: 24px;
+ min-height: 24px;
+ margin: 0px;
+}
+
+#NautilusQueryEditorTag {
+ background-color: alpha(currentColor, 0.1);
+ border-radius: 100px;
+}
+
+/* Mimic the style of GtkEntry icons, but keep button background if pressed. */
+#NautilusQueryEditor > menubutton > button:not(:checked) {
+ background: none;
+}
+#NautilusQueryEditor > menubutton > button:not(:hover):not(:checked) {
+ opacity: 0.7;
+}
+
+/* Floating status bar */
+.floating-bar {
+ padding: 3px;
+ background-color: @view_bg_color;
+ box-shadow: 0 0 0 1px @borders;
+ border-radius: 8px 0 0 0;
+}
+
+.floating-bar.bottom.left { /* axes left border and border radius */
+ border-top-left-radius: 0;
+}
+.floating-bar.bottom.right { /* axes right border and border radius */
+ border-top-right-radius: 0;
+}
+
+.floating-bar:backdrop {
+ background-color: @view_bg_color;
+ border-color: @unfocused_borders;
+}
+
+.floating-bar button {
+ padding: 0px;
+}
+
+.disk-space-free {
+ color: alpha(currentColor, 0.15);
+}
+.disk-space-used {
+ color: @accent_bg_color;
+}
+
+.search-information {
+ background-color: @accent_bg_color;
+ color:white;
+ padding:2px;
+}
+
+.batch-rename-preview {
+ border-top: solid @borders 1px;
+}
+.conflict-row {
+ background: @warning_bg_color;
+ color: @warning_fg_color;
+}
+
+/* Grid view */
+.nautilus-grid-view gridview {
+ padding: 15px;
+}
+
+.nautilus-grid-view gridview > child {
+ padding: 0px;
+ border-radius: 12px;
+}
+.nautilus-grid-view #NautilusViewCell {
+ padding: 6px;
+ border-radius: 12px;
+}
+
+/* Column view */
+
+/* Setup padding on the list. Horizontal padding must be set on the columnview
+ * for it to calculate column widths correctly. */
+.nautilus-list-view columnview {
+ padding-left: 24px;
+ padding-right: 24px;
+}
+.nautilus-list-view columnview > listview {
+ padding-top: 12px;
+ padding-bottom: 24px;
+}
+
+/* Use negative margins to extend rubberbanding area into the columnview's
+ * padding, then apply positive margin on rows to reestablish positioning. */
+.nautilus-list-view columnview > listview {
+ margin-left: -24px;
+ margin-right: -24px;
+}
+.nautilus-list-view columnview > listview > row {
+ margin-left: 24px;
+ margin-right: 24px;
+}
+
+.nautilus-list-view columnview > listview > row {
+ border-radius: 6px;
+ margin-top: 4px;
+ margin-bottom: 4px;
+}
+
+.nautilus-list-view.compact columnview > listview > row {
+ margin-top: 2px;
+ margin-bottom: 2px;
+}
+
+/* GTK unconditionally sets padding on GtkColumnViewCell, even with .data-table.
+ * We don't want this to hpappen because we have event controllers on the child,
+ * which should thus cover the whole area of the row. */
+.nautilus-list-view columnview > listview > row > cell {
+ padding: 0px;
+}
+
+.nautilus-list-view #NautilusViewCell {
+ padding: 6px;
+}
+
+/* We want drop feedback on the whole row. Disable per-cell feedback */
+.nautilus-list-view #NautilusViewCell:drop(active) {
+ box-shadow: none;
+}
+
+.nautilus-list-view.compact #NautilusViewCell {
+ padding-top: 3px;
+ padding-bottom: 3px;
+}
+
+.nautilus-list-view:not(.compact) image.star {
+ padding: 6px;
+}
+
+.nautilus-list-view menubutton.fts-snippet > button {
+ border-radius: 100px;
+ padding-top: 2px;
+ padding-bottom: 2px;
+ padding-left: 12px;
+ padding-right: 12px;
+}
+.nautilus-list-view menubutton.fts-snippet > popover > * {
+ padding: 18px;
+}
+
+/* Both views */
+.nautilus-list-view:drop(active),
+.nautilus-grid-view:drop(active) {
+ box-shadow: none;
+}
+
+.nautilus-list-view columnview > listview > row.activatable:hover,
+.nautilus-grid-view gridview > child.activatable:hover {
+ background-color: alpha(currentColor, .04);
+}
+
+.nautilus-list-view columnview > listview > row.activatable:active,
+.nautilus-grid-view gridview > child.activatable:active {
+ background-color: alpha(currentColor, .08);
+}
+
+.nautilus-list-view columnview > listview > row:selected,
+.nautilus-grid-view gridview > child:selected {
+ background-color: alpha(@accent_bg_color, .15);
+}
+
+.nautilus-list-view columnview > listview > row.activatable:selected:hover,
+.nautilus-grid-view gridview > child.activatable:selected:hover {
+ background-color: alpha(@accent_bg_color, .20);
+}
+
+.nautilus-list-view columnview > listview > row.activatable:selected:active,
+.nautilus-grid-view gridview > child.activatable:selected:active {
+ background-color: alpha(@accent_bg_color, .25);
+}
+
+.view .thumbnail {
+ background: url('/org/gnome/nautilus/Checkerboard.png') repeat;
+ border-radius: 2px;
+ /* Draw a shin and outline to meld better with full-color icons */
+ box-shadow: 0px 0px 0px 1px @shade_color,
+ 0px 2px 0px 0px @shade_color;
+}
+
+.view picture {
+ filter: drop-shadow(0px 1px 1px rgba(0,0,0,0.3));
+}
+
+.view statuspage {
+ opacity: 0.50;
+}
+
+.view .cut {
+ opacity: 0.55;
+}
+
+.view image.star:hover {
+ opacity: 1;
+}
+
+@keyframes rotate_star {
+ from { -gtk-icon-transform: rotate(-72deg); }
+ to {}
+}
+
+.view image.star.added {
+ animation: rotate_star 0.4s ease;
+}
+
+#NautilusAppChooser treeview {
+ min-height: 36px;
+ -gtk-icon-size: 32px;
+}
+
+label.encrypted_zip {
+ background-image: -gtk-icontheme('system-lock-screen-symbolic');
+ background-position: right center;
+ background-repeat: no-repeat;
+ background-size: 16px 16px;
+ padding-right: 22px;
+}
diff --git a/src/resources/text-x-preview.png b/src/resources/text-x-preview.png
new file mode 100644
index 0000000..0d45ff9
--- /dev/null
+++ b/src/resources/text-x-preview.png
Binary files differ
diff --git a/src/resources/ui/nautilus-app-chooser.ui b/src/resources/ui/nautilus-app-chooser.ui
new file mode 100644
index 0000000..e30beac
--- /dev/null
+++ b/src/resources/ui/nautilus-app-chooser.ui
@@ -0,0 +1,120 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+ <requires lib="gtk" version="4.0"/>
+ <template class="NautilusAppChooser" parent="GtkDialog">
+ <property name="title" translatable="yes">Open File</property>
+ <property name="focusable">False</property>
+ <property name="destroy-with-parent">True</property>
+ <property name="modal">True</property>
+ <property name="default-width">420</property>
+ <property name="default-height">560</property>
+ <child internal-child="content_area">
+ <object class="GtkBox" id="content_area">
+ <property name="orientation">vertical</property>
+ <child>
+ <object class="GtkStack">
+ <property name="hexpand">True</property>
+ <child>
+ <object class="GtkStackPage">
+ <property name="name">list</property>
+ <property name="child">
+ <object class="GtkBox">
+ <property name="orientation">vertical</property>
+ <child>
+ <object class="GtkScrolledWindow">
+ <property name="hscrollbar-policy">never</property>
+ <property name="vscrollbar-policy">never</property>
+ <property name="vexpand">true</property>
+ <style>
+ <class name="background"/>
+ </style>
+ <property name="child">
+ <object class="AdwClamp">
+ <property name="margin-top">18</property>
+ <property name="margin-bottom">18</property>
+ <property name="margin-start">18</property>
+ <property name="margin-end">18</property>
+ <property name="child">
+ <object class="GtkBox">
+ <property name="orientation">vertical</property>
+ <property name="spacing">12</property>
+ <child>
+ <object class="GtkLabel" id="label_description">
+ <property name="wrap">True</property>
+ <property name="wrap-mode">PANGO_WRAP_WORD_CHAR</property>
+ <property name="justify">center</property>
+ <property name="label" translatable="yes">Choose an application to open the selected files.</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkBox" id="app_chooser_widget_box">
+ <property name="halign">center</property>
+ <property name="vexpand">True</property>
+ </object>
+ </child>
+ </object>
+ </property>
+ </object>
+ </property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkBox" id="set_default_box">
+ <property name="orientation">vertical</property>
+ <style>
+ <class name="background"/>
+ </style>
+ <child>
+ <object class="GtkSeparator"/>
+ </child>
+ <child>
+ <object class="GtkListBox">
+ <style>
+ <class name="background"/>
+ </style>
+ <child>
+ <object class="AdwActionRow" id="set_default_row">
+ <property name="hexpand">true</property>
+ <property name="selectable">false</property>
+ <property name="activatable-widget">set_as_default_switch</property>
+ <property name="title" translatable="yes">Always use for this file type</property>
+ <child>
+ <object class="GtkSwitch" id="set_as_default_switch">
+ <property name="halign">end</property>
+ <property name="valign">center</property>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ </property>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child type="action">
+ <object class="GtkButton" id="cancel_button">
+ <property name="label" translatable="yes">_Cancel</property>
+ <property name="use-underline">True</property>
+ </object>
+ </child>
+ <child type="action">
+ <object class="GtkButton" id="ok_button">
+ <property name="label" translatable="yes">_Open</property>
+ <property name="use-underline">True</property>
+ <property name="sensitive">False</property>
+ <signal name="clicked" object="NautilusAppChooser" handler="open_cb" swapped="yes"/>
+ </object>
+ </child>
+ <action-widgets>
+ <action-widget response="ok" default="true">ok_button</action-widget>
+ <action-widget response="cancel">cancel_button</action-widget>
+ </action-widgets>
+ </template>
+</interface>
diff --git a/src/resources/ui/nautilus-batch-rename-dialog.ui b/src/resources/ui/nautilus-batch-rename-dialog.ui
new file mode 100644
index 0000000..7da1e95
--- /dev/null
+++ b/src/resources/ui/nautilus-batch-rename-dialog.ui
@@ -0,0 +1,395 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+ <requires lib="gtk" version="4.0"/>
+ <menu id="add_tag_menu">
+ <section>
+ <attribute name="label" translatable="yes">Automatic Numbers</attribute>
+ <item>
+ <attribute name="label" translatable="yes">1, 2, 3, 4</attribute>
+ <attribute name="action">dialog.add-numbering-no-zero-pad-tag</attribute>
+ </item>
+ <item>
+ <attribute name="label" translatable="yes">01, 02, 03, 04</attribute>
+ <attribute name="action">dialog.add-numbering-one-zero-pad-tag</attribute>
+ </item>
+ <item>
+ <attribute name="label" translatable="yes">001, 002, 003, 004</attribute>
+ <attribute name="action">dialog.add-numbering-two-zero-pad-tag</attribute>
+ </item>
+ </section>
+ <section>
+ <attribute name="label" translatable="yes">Metadata</attribute>
+ <item>
+ <attribute name="label" translatable="yes">Creation Date</attribute>
+ <attribute name="action">dialog.add-creation-date-tag</attribute>
+ <attribute name="hidden-when">action-disabled</attribute>
+ </item>
+ <item>
+ <attribute name="label" translatable="yes">Camera Model</attribute>
+ <attribute name="action">dialog.add-equipment-tag</attribute>
+ <attribute name="hidden-when">action-disabled</attribute>
+ </item>
+ <item>
+ <attribute name="label" translatable="yes">Season Number</attribute>
+ <attribute name="action">dialog.add-season-tag</attribute>
+ <attribute name="hidden-when">action-disabled</attribute>
+ </item>
+ <item>
+ <attribute name="label" translatable="yes">Episode Number</attribute>
+ <attribute name="action">dialog.add-episode-tag</attribute>
+ <attribute name="hidden-when">action-disabled</attribute>
+ </item>
+ <item>
+ <attribute name="label" translatable="yes">Track Number</attribute>
+ <attribute name="action">dialog.add-track-number-tag</attribute>
+ <attribute name="hidden-when">action-disabled</attribute>
+ </item>
+ <item>
+ <attribute name="label" translatable="yes">Artist Name</attribute>
+ <attribute name="action">dialog.add-artist-name-tag</attribute>
+ <attribute name="hidden-when">action-disabled</attribute>
+ </item>
+ <item>
+ <attribute name="label" translatable="yes">Title</attribute>
+ <attribute name="action">dialog.add-title-tag</attribute>
+ <attribute name="hidden-when">action-disabled</attribute>
+ </item>
+ <item>
+ <attribute name="label" translatable="yes">Album Name</attribute>
+ <attribute name="action">dialog.add-album-name-tag</attribute>
+ <attribute name="hidden-when">action-disabled</attribute>
+ </item>
+ </section>
+ <section>
+ <item>
+ <attribute name="label" translatable="yes">Original File Name</attribute>
+ <attribute name="action">dialog.add-original-file-name-tag</attribute>
+ </item>
+ </section>
+ </menu>
+ <menu id="numbering_order_menu">
+ <section>
+ <item>
+ <attribute name="label" translatable="yes">Original Name (Ascending)</attribute>
+ <attribute name="action">dialog.numbering-order-changed</attribute>
+ <attribute name="target">name-ascending</attribute>
+ </item>
+ <item>
+ <attribute name="label" translatable="yes">Original Name (Descending)</attribute>
+ <attribute name="action">dialog.numbering-order-changed</attribute>
+ <attribute name="target">name-descending</attribute>
+ </item>
+ <item>
+ <attribute name="label" translatable="yes">First Modified</attribute>
+ <attribute name="action">dialog.numbering-order-changed</attribute>
+ <attribute name="target">first-modified</attribute>
+ </item>
+ <item>
+ <attribute name="label" translatable="yes">Last Modified</attribute>
+ <attribute name="action">dialog.numbering-order-changed</attribute>
+ <attribute name="target">last-modified</attribute>
+ </item>
+ </section>
+ </menu>
+ <template class="NautilusBatchRenameDialog" parent="GtkDialog">
+ <property name="modal">True</property>
+ <property name="height-request">563</property>
+ <property name="destroy_with_parent">True</property>
+ <signal name="response" handler="batch_rename_dialog_on_response"/>
+ <child type="action">
+ <object class="GtkButton" id="cancel_button">
+ <property name="label" translatable="yes">_Cancel</property>
+ <property name="use_underline">True</property>
+ </object>
+ </child>
+ <child type="action">
+ <object class="GtkButton" id="rename_button">
+ <property name="label" translatable="yes">_Rename</property>
+ <property name="use_underline">True</property>
+ <style>
+ <class name="suggested-action"/>
+ </style>
+ </object>
+ </child>
+ <action-widgets>
+ <action-widget response="ok" default="true">rename_button</action-widget>
+ <action-widget response="cancel">cancel_button</action-widget>
+ </action-widgets>
+ <child internal-child="content_area">
+ <object class="GtkBox" id="content_area">
+ <child>
+ <object class="GtkGrid" id="grid">
+ <property name="row-spacing">6</property>
+ <property name="column-spacing">6</property>
+ <property name="hexpand">True</property>
+ <child>
+ <object class="GtkBox">
+ <property name="spacing">15</property>
+ <property name="hexpand">True</property>
+ <property name="halign">center</property>
+ <property name="margin-top">20</property>
+ <property name="margin-bottom">20</property>
+ <property name="margin-start">20</property>
+ <property name="margin-end">20</property>
+ <child>
+ <object class="GtkCheckButton" id="format_mode_button">
+ <property name="label" translatable="yes">Rename _using a template</property>
+ <property name="use_underline">True</property>
+ <property name="active">True</property>
+ <signal name="toggled" handler="batch_rename_dialog_mode_changed" swapped="yes" />
+ </object>
+ </child>
+ <child>
+ <object class="GtkCheckButton" id="replace_mode_button">
+ <property name="label" translatable="yes">Find and replace _text</property>
+ <property name="use_underline">True</property>
+ <property name="group">format_mode_button</property>
+ <signal name="toggled" handler="batch_rename_dialog_mode_changed" swapped="yes"/>
+ </object>
+ </child>
+ <layout>
+ <property name="column">3</property>
+ <property name="row">0</property>
+ </layout>
+ </object>
+ </child>
+ <child>
+ <object class="GtkStack" id="mode_stack">
+ <property name="vhomogeneous">False</property>
+ <property name="hhomogeneous">True</property>
+ <property name="transition_type">crossfade</property>
+ <property name="transition_duration">100</property>
+ <child>
+ <object class="GtkStackPage">
+ <property name="name">format</property>
+ <property name="title" translatable="yes" comments="Translators: This is a noun, not a verb">Format</property>
+ <property name="child">
+ <object class="GtkGrid" id="format_stack_child">
+ <property name="margin-start">40</property>
+ <property name="margin-end">40</property>
+ <property name="margin-bottom">10</property>
+ <property name="row-spacing">15</property>
+ <property name="column-spacing">6</property>
+ <child>
+ <object class="GtkBox">
+ <property name="halign">center</property>
+ <child>
+ <object class="GtkEntry" id="name_entry">
+ <property name="width_request">400</property>
+ <property name="hexpand">True</property>
+ <property name="activates-default">True</property>
+ <signal name="activate" handler="file_names_widget_on_activate" swapped="yes"/>
+ </object>
+ </child>
+ <child>
+ <object class="GtkMenuButton">
+ <property name="menu_model">add_tag_menu</property>
+ <property name="child">
+ <object class="AdwButtonContent">
+ <property name="icon-name">list-add-symbolic</property>
+ <property name="label" translatable="yes">Add</property>
+ </object>
+ </property>
+ </object>
+ </child>
+ <style>
+ <class name="linked"/>
+ </style>
+ <layout>
+ <property name="column">0</property>
+ <property name="row">0</property>
+ <property name="column-span">5</property>
+ </layout>
+ </object>
+ </child>
+ <child>
+ <object class="GtkRevealer" id="numbering_revealer">
+ <property name="halign">center</property>
+ <property name="child">
+ <object class="GtkBox" id="numbering_box">
+ <property name="spacing">10</property>
+ <child>
+ <object class="GtkLabel" id="numbering_label">
+ <property name="label" translatable="yes">Automatic Numbering Order</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkMenuButton" id="numbering_order_button">
+ <property name="menu-model">numbering_order_menu</property>
+ <property name="always-show-arrow">True</property>
+ </object>
+ </child>
+ </object>
+ </property>
+ <layout>
+ <property name="column">0</property>
+ <property name="row">1</property>
+ <property name="column-span">5</property>
+ </layout>
+ </object>
+ </child>
+ </object>
+ </property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkStackPage">
+ <property name="name">replace</property>
+ <property name="title" translatable="yes" context="title">Replace</property>
+ <property name="child">
+ <object class="GtkGrid" id="replace_stack_child">
+ <property name="margin-start">40</property>
+ <property name="margin-end">40</property>
+ <property name="margin-bottom">10</property>
+ <property name="hexpand">True</property>
+ <property name="halign">center</property>
+ <property name="row-spacing">15</property>
+ <property name="column-spacing">6</property>
+ <child>
+ <object class="GtkLabel" id="existing_text_label">
+ <property name="label" translatable="yes">Existing Text</property>
+ <property name="sensitive">False</property>
+ <layout>
+ <property name="column">0</property>
+ <property name="row">0</property>
+ </layout>
+ </object>
+ </child>
+ <child>
+ <object class="GtkEntry" id="find_entry">
+ <property name="width_request">375</property>
+ <property name="activates-default">True</property>
+ <signal name="changed" handler="file_names_widget_entry_on_changed" swapped="yes"/>
+ <signal name="activate" handler="file_names_widget_on_activate" swapped="yes"/>
+ <layout>
+ <property name="column">1</property>
+ <property name="row">0</property>
+ <property name="column-span">3</property>
+ </layout>
+ </object>
+ </child>
+ <child>
+ <object class="GtkLabel" id="replace_label">
+ <property name="label" translatable="yes">Replace With</property>
+ <property name="sensitive">False</property>
+ <layout>
+ <property name="column">0</property>
+ <property name="row">1</property>
+ </layout>
+ </object>
+ </child>
+ <child>
+ <object class="GtkEntry" id="replace_entry">
+ <property name="width_request">375</property>
+ <signal name="changed" handler="file_names_widget_entry_on_changed" swapped="yes"/>
+ <signal name="activate" handler="file_names_widget_on_activate" swapped="yes"/>
+ <layout>
+ <property name="column">1</property>
+ <property name="row">1</property>
+ <property name="column-span">3</property>
+ </layout>
+ </object>
+ </child>
+ </object>
+ </property>
+ </object>
+ </child>
+ <layout>
+ <property name="column">0</property>
+ <property name="row">1</property>
+ <property name="column-span">8</property>
+ </layout>
+ </object>
+ </child>
+ <child>
+ <object class="GtkScrolledWindow" id="scrolled_window">
+ <property name="hexpand">False</property>
+ <property name="vexpand">True</property>
+ <property name="max-content-height">250</property>
+ <property name="min-content-height">250</property>
+ <property name="max-content-width">600</property>
+ <property name="min-content-width">600</property>
+ <style>
+ <class name="batch-rename-preview"/>
+ </style>
+ <property name="child">
+ <object class="GtkViewport">
+ <property name="child">
+ <object class="GtkBox" id="a_box">
+ <child>
+ <object class="GtkListBox" id="original_name_listbox">
+ <property name="hexpand">True</property>
+ <property name="selection_mode">GTK_SELECTION_NONE</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkListBox" id="arrow_listbox">
+ <property name="selection_mode">GTK_SELECTION_NONE</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkListBox" id="result_listbox">
+ <property name="hexpand">True</property>
+ <property name="selection_mode">GTK_SELECTION_NONE</property>
+ </object>
+ </child>
+ </object>
+ </property>
+ </object>
+ </property>
+ <layout>
+ <property name="column">0</property>
+ <property name="row">2</property>
+ <property name="column-span">8</property>
+ </layout>
+ </object>
+ </child>
+ <child>
+ <object class="GtkBox" id="conflict_box">
+ <property name="spacing">6</property>
+ <property name="visible">False</property>
+ <property name="margin-start">6</property>
+ <child>
+ <object class="GtkLabel" id="conflict_label">
+ <property name="hexpand">True</property>
+ <property name="xalign">0</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkBox">
+ <child>
+ <object class="GtkButton" id="conflict_down">
+ <property name="icon-name">go-down-symbolic</property>
+ <signal name="clicked" handler="select_next_conflict_down" swapped="yes"/>
+ <style>
+ <class name="flat"/>
+ </style>
+ </object>
+ </child>
+ <child>
+ <object class="GtkButton" id="conflict_up">
+ <property name="icon-name">go-up-symbolic</property>
+ <signal name="clicked" handler="select_next_conflict_up" swapped="yes"/>
+ <style>
+ <class name="flat"/>
+ </style>
+ </object>
+ </child>
+ </object>
+ </child>
+ <layout>
+ <property name="column">0</property>
+ <property name="row">3</property>
+ <property name="column-span">8</property>
+ </layout>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ </template>
+ <object class="GtkImage" id="done_image">
+ <property name="icon_name">object-select-symbolic</property>
+ </object>
+</interface>
diff --git a/src/resources/ui/nautilus-column-chooser.ui b/src/resources/ui/nautilus-column-chooser.ui
new file mode 100644
index 0000000..24cce3d
--- /dev/null
+++ b/src/resources/ui/nautilus-column-chooser.ui
@@ -0,0 +1,105 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+ <requires lib="gtk" version="4.0"/>
+ <object class="GtkListStore" id="store">
+ <columns>
+ <!-- column-name COLUMN_VISIBLE -->
+ <column type="gboolean"/>
+ <!-- column-name COLUMN_LABEL -->
+ <column type="gchararray"/>
+ <!-- column-name COLUMN_NAME -->
+ <column type="gchararray"/>
+ <!-- column-name COLUMN_SENSITIVE -->
+ <column type="gboolean"/>
+ </columns>
+ </object>
+ <template class="NautilusColumnChooser" parent="GtkBox">
+ <property name="orientation">vertical</property>
+ <property name="spacing">12</property>
+ <child>
+ <object class="GtkScrolledWindow">
+ <property name="child">
+ <object class="GtkTreeView" id="view">
+ <property name="vexpand">True</property>
+ <property name="model">store</property>
+ <property name="headers-visible">False</property>
+ <property name="reorderable">True</property>
+ <signal name="row-activated" handler="view_row_activated_callback" swapped="no"/>
+ <child internal-child="selection">
+ <object class="GtkTreeSelection">
+ <signal name="changed" handler="selection_changed_callback" swapped="no"/>
+ </object>
+ </child>
+ <child>
+ <object class="GtkTreeViewColumn" id="visible-column">
+ <child>
+ <object class="GtkCellRendererToggle">
+ <signal name="toggled" handler="visible_toggled_callback" swapped="no"/>
+ </object>
+ <attributes>
+ <attribute name="sensitive">3</attribute>
+ <attribute name="active">0</attribute>
+ </attributes>
+ </child>
+ </object>
+ </child>
+ <child>
+ <object class="GtkTreeViewColumn" id="label-column">
+ <child>
+ <object class="GtkCellRendererText"/>
+ <attributes>
+ <attribute name="sensitive">3</attribute>
+ <attribute name="text">1</attribute>
+ </attributes>
+ </child>
+ </object>
+ </child>
+ </object>
+ </property>
+ <style>
+ <class name="frame"/>
+ </style>
+ </object>
+ </child>
+ <child>
+ <object class="GtkBox">
+ <property name="spacing">12</property>
+ <child>
+ <object class="GtkBox">
+ <child>
+ <object class="GtkButton" id="move_up_button">
+ <property name="sensitive">False</property>
+ <property name="icon-name">go-up-symbolic</property>
+ <signal name="clicked" handler="move_up_clicked_callback" swapped="no"/>
+ </object>
+ </child>
+ <child>
+ <object class="GtkButton" id="move_down_button">
+ <property name="sensitive">False</property>
+ <property name="icon-name">go-down-symbolic</property>
+ <signal name="clicked" handler="move_down_clicked_callback" swapped="no"/>
+ </object>
+ </child>
+ <style>
+ <class name="linked"/>
+ </style>
+ </object>
+ </child>
+ <child>
+ <object class="GtkBox">
+ <child>
+ <object class="GtkButton" id="use_default_button">
+ <property name="hexpand">true</property>
+ <property name="halign">end</property>
+ <property name="label" translatable="yes">Reset to De_fault</property>
+ <property name="tooltip-text" translatable="yes">Replace the current List Columns settings with the default settings</property>
+ <property name="use-underline">True</property>
+ <signal name="clicked" handler="use_default_clicked_callback" swapped="no"/>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ </template>
+</interface>
diff --git a/src/resources/ui/nautilus-compress-dialog.ui b/src/resources/ui/nautilus-compress-dialog.ui
new file mode 100644
index 0000000..732afee
--- /dev/null
+++ b/src/resources/ui/nautilus-compress-dialog.ui
@@ -0,0 +1,98 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+ <requires lib="gtk" version="4.0"/>
+ <object class="GtkDialog" id="compress_dialog">
+ <property name="title" translatable="yes">Create Compressed Archive</property>
+ <property name="resizable">False</property>
+ <property name="modal">True</property>
+ <property name="destroy_with_parent">True</property>
+ <property name="use-header-bar">1</property>
+ <property name="default-width">500</property>
+ <property name="default-height">210</property>
+ <child internal-child="content_area">
+ <object class="GtkBox" id="content_area">
+ <property name="orientation">vertical</property>
+ <property name="margin-top">30</property>
+ <property name="margin-bottom">30</property>
+ <property name="margin-start">30</property>
+ <property name="margin-end">30</property>
+ <property name="width-request">390</property>
+ <property name="halign">center</property>
+ <property name="spacing">6</property>
+ <child>
+ <object class="GtkLabel" id="name_label">
+ <property name="label" translatable="yes">Archive name</property>
+ <property name="xalign">0</property>
+ <attributes>
+ <attribute name="weight" value="bold"></attribute>
+ </attributes>
+ </object>
+ </child>
+ <child>
+ <object class="GtkBox">
+ <property name="spacing">12</property>
+ <child>
+ <object class="GtkEntry" id="name_entry">
+ <accessibility>
+ <relation name="labelled-by">name_label</relation>
+ </accessibility>
+ <property name="hexpand">True</property>
+ <property name="width-chars">30</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkDropDown" id="extension_dropdown"/>
+ </child>
+ </object>
+ </child>
+ <child>
+ <object class="GtkRevealer" id="error_revealer">
+ <property name="child">
+ <object class="GtkLabel" id="error_label">
+ <property name="margin_top">4</property>
+ <property name="margin_bottom">4</property>
+ <property name="xalign">0</property>
+ </object>
+ </property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkLabel" id="passphrase_label">
+ <property name="visible">False</property>
+ <property name="label" translatable="yes">Password</property>
+ <property name="margin-top">6</property>
+ <property name="xalign">0</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkEntry" id="passphrase_entry">
+ <accessibility>
+ <relation name="labelled-by">passphrase_label</relation>
+ </accessibility>
+ <property name="visible">False</property>
+ <property name="placeholder-text" translatable="yes">Enter a password here.</property>
+ <property name="input-purpose">password</property>
+ <property name="visibility">False</property>
+ <property name="secondary-icon-name">view-conceal</property>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child type="action">
+ <object class="GtkButton" id="cancel_button">
+ <property name="label" translatable="yes">Cancel</property>
+ </object>
+ </child>
+ <child type="action">
+ <object class="GtkButton" id="activate_button">
+ <property name="label" translatable="yes">Create</property>
+ <property name="sensitive">False</property>
+ </object>
+ </child>
+ <action-widgets>
+ <action-widget response="ok" default="true">activate_button</action-widget>
+ <action-widget response="cancel">cancel_button</action-widget>
+ </action-widgets>
+ </object>
+ <object class="GtkSizeGroup" id="extension_sizegroup"/>
+</interface>
diff --git a/src/resources/ui/nautilus-create-folder-dialog.ui b/src/resources/ui/nautilus-create-folder-dialog.ui
new file mode 100644
index 0000000..699180d
--- /dev/null
+++ b/src/resources/ui/nautilus-create-folder-dialog.ui
@@ -0,0 +1,58 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+ <requires lib="gtk" version="4.0"/>
+ <object class="GtkDialog" id="create_folder_dialog">
+ <property name="resizable">False</property>
+ <property name="modal">True</property>
+ <property name="destroy_with_parent">True</property>
+ <property name="use-header-bar">1</property>
+ <property name="width_request">450</property>
+ <child internal-child="content_area">
+ <object class="GtkBox" id="content_area">
+ <property name="orientation">vertical</property>
+ <property name="margin_top">18</property>
+ <property name="margin_bottom">12</property>
+ <property name="margin_start">18</property>
+ <property name="margin_end">18</property>
+ <property name="spacing">6</property>
+ <child>
+ <object class="GtkLabel" id="name_label">
+ <property name="xalign">0</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkEntry" id="name_entry">
+ <accessibility>
+ <relation name="labelled-by">name_label</relation>
+ </accessibility>
+ </object>
+ </child>
+ <child>
+ <object class="GtkRevealer" id="error_revealer">
+ <property name="child">
+ <object class="GtkLabel" id="error_label">
+ <property name="margin_top">4</property>
+ <property name="margin_bottom">4</property>
+ <property name="xalign">0</property>
+ </object>
+ </property>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child type="action">
+ <object class="GtkButton" id="cancel_button">
+ <property name="label" translatable="yes">Cancel</property>
+ </object>
+ </child>
+ <child type="action">
+ <object class="GtkButton" id="ok_button">
+ <property name="sensitive">False</property>
+ </object>
+ </child>
+ <action-widgets>
+ <action-widget response="ok" default="true">ok_button</action-widget>
+ <action-widget response="cancel">cancel_button</action-widget>
+ </action-widgets>
+ </object>
+</interface>
diff --git a/src/resources/ui/nautilus-file-conflict-dialog.ui b/src/resources/ui/nautilus-file-conflict-dialog.ui
new file mode 100644
index 0000000..8993fb6
--- /dev/null
+++ b/src/resources/ui/nautilus-file-conflict-dialog.ui
@@ -0,0 +1,147 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+ <requires lib="gtk" version="4.0"/>
+ <template class="NautilusFileConflictDialog" parent="GtkDialog">
+ <property name="modal">True</property>
+ <property name="resizable">False</property>
+ <child internal-child="content_area">
+ <object class="GtkBox">
+ <property name="margin-top">18</property>
+ <property name="margin-bottom">18</property>
+ <property name="margin-start">18</property>
+ <property name="margin-end">18</property>
+ <property name="spacing">12</property>
+ <child>
+ <object class="GtkBox">
+ <property name="orientation">vertical</property>
+ <property name="spacing">12</property>
+ <child>
+ <object class="GtkLabel" id="primary_label">
+ <property name="justify">center</property>
+ <property name="halign">center</property>
+ <property name="max-width-chars">50</property>
+ <property name="wrap">True</property>
+ <property name="wrap-mode">word-char</property>
+ <style>
+ <class name="title-2"/>
+ </style>
+ </object>
+ </child>
+ <child>
+ <object class="GtkLabel" id="secondary_label">
+ <property name="justify">center</property>
+ <property name="halign">center</property>
+ <property name="max-width-chars">50</property>
+ <property name="wrap">True</property>
+ <property name="wrap-mode">word-char</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkBox">
+ <property name="halign">start</property>
+ <property name="orientation">vertical</property>
+ <property name="spacing">12</property>
+ <child>
+ <object class="GtkBox">
+ <property name="spacing">12</property>
+ <child>
+ <object class="GtkPicture" id="dest_icon"/>
+ </child>
+ <child>
+ <object class="GtkLabel" id="dest_label"/>
+ </child>
+ </object>
+ </child>
+ <child>
+ <object class="GtkBox">
+ <property name="spacing">12</property>
+ <child>
+ <object class="GtkPicture" id="src_icon"/>
+ </child>
+ <child>
+ <object class="GtkLabel" id="src_label"/>
+ </child>
+ </object>
+ </child>
+ <child>
+ <object class="GtkExpander" id="expander">
+ <property name="label" translatable="yes">_Select a new name for the destination</property>
+ <property name="use-underline">True</property>
+ <signal name="notify::expanded" handler="on_expanded_notify"/>
+ <child>
+ <object class="GtkBox">
+ <property name="margin-top">6</property>
+ <style>
+ <class name="linked"/>
+ </style>
+ <child>
+ <object class="GtkEntry" id="entry">
+ <property name="activates-default">True</property>
+ <property name="hexpand">True</property>
+ <signal name="changed" handler="entry_text_changed_cb"/>
+ </object>
+ </child>
+ <child>
+ <object class="GtkButton">
+ <property name="label" translatable="yes">_Reset</property>
+ <property name="use-underline">True</property>
+ <signal name="clicked" handler="reset_button_clicked_cb"/>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child>
+ <object class="GtkCheckButton" id="checkbox">
+ <signal name="toggled" handler="checkbox_toggled_cb"/>
+ <child>
+ <object class="GtkLabel">
+ <property name="label" translatable="yes">Apply this action to all files and folders</property>
+ <property name="use-underline">True</property>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child type="action">
+ <object class="GtkButton" id="cancel_button">
+ <property name="label" translatable="yes">_Cancel</property>
+ <property name="use-underline">True</property>
+ </object>
+ </child>
+ <child type="action">
+ <object class="GtkButton" id="rename_button">
+ <property name="visible">False</property>
+ <property name="label" translatable="yes">Re_name</property>
+ <property name="use-underline">True</property>
+ </object>
+ </child>
+ <child type="action">
+ <object class="GtkButton" id="replace_button">
+ <property name="label" translatable="yes">Re_place</property>
+ <property name="use-underline">True</property>
+ </object>
+ </child>
+ <child type="action">
+ <object class="GtkButton" id="skip_button">
+ <property name="label" translatable="yes">_Skip</property>
+ <property name="use-underline">True</property>
+ </object>
+ </child>
+ <action-widgets>
+ <action-widget response="cancel">cancel_button</action-widget>
+ <!-- 3 is CONFLICT_RESPONSE_RENAME -->
+ <action-widget response="3">rename_button</action-widget>
+ <!-- 2 is CONFLICT_RESPONSE_REPLACE -->
+ <action-widget response="2" default="true">replace_button</action-widget>
+ <!-- 1 is CONFLICT_RESPONSE_SKIP -->
+ <action-widget response="1">skip_button</action-widget>
+ </action-widgets>
+ </template>
+</interface>
diff --git a/src/resources/ui/nautilus-file-properties-change-permissions.ui b/src/resources/ui/nautilus-file-properties-change-permissions.ui
new file mode 100644
index 0000000..623b7db
--- /dev/null
+++ b/src/resources/ui/nautilus-file-properties-change-permissions.ui
@@ -0,0 +1,157 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+ <requires lib="gtk" version="4.0"/>
+ <object class="GtkDialog" id="change_permissions_dialog">
+ <property name="title" translatable="yes">Change Permissions for Enclosed Files</property>
+ <property name="modal">True</property>
+ <property name="destroy_with_parent">True</property>
+ <property name="use-header-bar">1</property>
+ <child type="action">
+ <object class="GtkButton" id="cancel">
+ <property name="label" translatable="yes">_Cancel</property>
+ <property name="use_underline">True</property>
+ </object>
+ </child>
+ <child type="action">
+ <object class="GtkButton" id="change">
+ <property name="label" translatable="yes">C_hange</property>
+ <property name="use_underline">True</property>
+ </object>
+ </child>
+ <child internal-child="content_area">
+ <object class="GtkBox">
+ <property name="orientation">vertical</property>
+ <style>
+ <class name="view"/>
+ </style>
+ <child>
+ <object class="GtkGrid" id="change_permissions_grid">
+ <property name="halign">center</property>
+ <property name="margin-top">6</property>
+ <property name="margin-bottom">6</property>
+ <property name="margin-start">6</property>
+ <property name="margin-end">6</property>
+ <property name="orientation">vertical</property>
+ <property name="row_spacing">6</property>
+ <property name="column_spacing">12</property>
+ <child>
+ <object class="GtkLabel">
+ <property name="label" translatable="yes">Files</property>
+ <style>
+ <class name="dim-label"/>
+ </style>
+ <layout>
+ <property name="column">1</property>
+ <property name="row">0</property>
+ </layout>
+ </object>
+ </child>
+ <child>
+ <object class="GtkLabel">
+ <property name="label" translatable="yes">Folders</property>
+ <style>
+ <class name="dim-label"/>
+ </style>
+ <layout>
+ <property name="column">2</property>
+ <property name="row">0</property>
+ </layout>
+ </object>
+ </child>
+ <child>
+ <object class="GtkLabel">
+ <property name="label" translatable="yes">Owner</property>
+ <property name="xalign">1</property>
+ <style>
+ <class name="dim-label"/>
+ </style>
+ <layout>
+ <property name="column">0</property>
+ <property name="row">1</property>
+ </layout>
+ </object>
+ </child>
+ <child>
+ <object class="GtkComboBox" id="file_owner_combo">
+ <layout>
+ <property name="column">1</property>
+ <property name="row">1</property>
+ </layout>
+ </object>
+ </child>
+ <child>
+ <object class="GtkComboBox" id="folder_owner_combo">
+ <layout>
+ <property name="column">2</property>
+ <property name="row">1</property>
+ </layout>
+ </object>
+ </child>
+ <child>
+ <object class="GtkLabel">
+ <property name="label" translatable="yes">Group</property>
+ <property name="xalign">1</property>
+ <style>
+ <class name="dim-label"/>
+ </style>
+ <layout>
+ <property name="column">0</property>
+ <property name="row">2</property>
+ </layout>
+ </object>
+ </child>
+ <child>
+ <object class="GtkComboBox" id="file_group_combo">
+ <layout>
+ <property name="column">1</property>
+ <property name="row">2</property>
+ </layout>
+ </object>
+ </child>
+ <child>
+ <object class="GtkComboBox" id="folder_group_combo">
+ <layout>
+ <property name="column">2</property>
+ <property name="row">2</property>
+ </layout>
+ </object>
+ </child>
+ <child>
+ <object class="GtkComboBox" id="file_other_combo">
+ <layout>
+ <property name="column">1</property>
+ <property name="row">3</property>
+ </layout>
+ </object>
+ </child>
+ <child>
+ <object class="GtkComboBox" id="folder_other_combo">
+ <layout>
+ <property name="column">2</property>
+ <property name="row">3</property>
+ </layout>
+ </object>
+ </child>
+ <child>
+ <object class="GtkLabel">
+ <property name="label" translatable="yes">Others</property>
+ <property name="xalign">1</property>
+ <style>
+ <class name="dim-label"/>
+ </style>
+ <layout>
+ <property name="column">0</property>
+ <property name="row">3</property>
+ </layout>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ <action-widgets>
+ <action-widget response="cancel">cancel</action-widget>
+ <action-widget response="ok">change</action-widget>
+ </action-widgets>
+ </object>
+</interface>
diff --git a/src/resources/ui/nautilus-files-view-context-menus.ui b/src/resources/ui/nautilus-files-view-context-menus.ui
new file mode 100644
index 0000000..87f0c41
--- /dev/null
+++ b/src/resources/ui/nautilus-files-view-context-menus.ui
@@ -0,0 +1,260 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+ <requires lib="gtk" version="4.0"/>
+ <menu id="background-menu">
+ <item>
+ <attribute name="label" translatable="yes">New _Folder…</attribute>
+ <attribute name="action">view.new-folder</attribute>
+ </item>
+ <item>
+ <attribute name="label" translatable="yes">New _Document</attribute>
+ <attribute name="nautilus-menu-item">templates-submenu</attribute>
+ <link name="submenu" id="templates-submenu"/>
+ </item>
+ <item>
+ <attribute name="label" translatable="yes">Open _With…</attribute>
+ <attribute name="action">view.open-current-directory-with-other-application</attribute>
+ </item>
+ <item>
+ <attribute name="label" translatable="yes">Open in Consol_e</attribute>
+ <attribute name="action">view.current-directory-console</attribute>
+ <attribute name="hidden-when">action-disabled</attribute>
+ </item>
+ <section>
+ <item>
+ <attribute name="label" translatable="yes">_Paste</attribute>
+ <attribute name="action">view.paste</attribute>
+ </item>
+ <item>
+ <attribute name="label" translatable="yes">Paste as _Link</attribute>
+ <attribute name="action">view.create-link</attribute>
+ <attribute name="hidden-when">action-disabled</attribute>
+ </item>
+ <item>
+ <attribute name="label" translatable="yes">Select _All</attribute>
+ <attribute name="action">view.select-all</attribute>
+ </item>
+ <item>
+ <attribute name="label" translatable="yes">_Visible Columns…</attribute>
+ <attribute name="action">view.visible-columns</attribute>
+ <attribute name="hidden-when">action-missing</attribute>
+ </item>
+ </section>
+ <section>
+ <item>
+ <attribute name="label" translatable="yes">Empty _Trash</attribute>
+ <attribute name="action">view.empty-trash</attribute>
+ <attribute name="hidden-when">action-disabled</attribute>
+ </item>
+ </section>
+ <section id="background-extensions-section"/>
+ <section>
+ <item>
+ <attribute name="label" translatable="yes">P_roperties</attribute>
+ <attribute name="action">view.current-directory-properties</attribute>
+ <attribute name="hidden-when">action-disabled</attribute>
+ </item>
+ </section>
+ </menu>
+ <menu id="selection-menu">
+ <section id="new-folder-with-selection-section"/>
+ <section id="open-with-application-section">
+ <item>
+ <attribute name="label" translatable="yes">_Extract</attribute>
+ <attribute name="action">view.extract-here</attribute>
+ <attribute name="hidden-when">action-disabled</attribute>
+ </item>
+ <item>
+ <attribute name="label" translatable="yes">E_xtract to…</attribute>
+ <attribute name="action">view.extract-to</attribute>
+ <attribute name="hidden-when">action-disabled</attribute>
+ </item>
+ <item>
+ <attribute name="label" translatable="yes">Open _With…</attribute>
+ <attribute name="action">view.open-with-other-application</attribute>
+ <attribute name="hidden-when">action-disabled</attribute>
+ <attribute name="nautilus-menu-item">open_with_in_main_menu</attribute>
+ </item>
+ <item>
+ <attribute name="label" translatable="yes">_Run as a Program</attribute>
+ <attribute name="action">view.run-in-terminal</attribute>
+ <attribute name="hidden-when">action-disabled</attribute>
+ </item>
+ <item>
+ <attribute name="label" translatable="yes">_Open</attribute>
+ <attribute name="nautilus-menu-item">open_in_view_submenu</attribute>
+ <link name="submenu">
+ <section>
+ <item>
+ <attribute name="label" translatable="yes">Open</attribute>
+ <attribute name="action">view.open-with-default-application</attribute>
+ <attribute name="hidden-when">action-disabled</attribute>
+ </item>
+ <item>
+ <attribute name="label" translatable="yes">Open In New _Tab</attribute>
+ <attribute name="action">view.open-item-new-tab</attribute>
+ <attribute name="hidden-when">action-disabled</attribute>
+ </item>
+ <item>
+ <attribute name="label" translatable="yes">Open In New _Window</attribute>
+ <attribute name="action">view.open-item-new-window</attribute>
+ <attribute name="hidden-when">action-disabled</attribute>
+ </item>
+ </section>
+ <section>
+ <item>
+ <attribute name="label" translatable="yes">Open _With…</attribute>
+ <attribute name="action">view.open-with-other-application</attribute>
+ <attribute name="hidden-when">action-disabled</attribute>
+ </item>
+ <item>
+ <attribute name="label" translatable="yes">Open in Consol_e</attribute>
+ <attribute name="action">view.console</attribute>
+ <attribute name="hidden-when">action-disabled</attribute>
+ </item>
+ </section>
+ </link>
+ </item>
+ <item>
+ <attribute name="label" translatable="yes">_Open Item Location</attribute>
+ <attribute name="action">view.open-item-location</attribute>
+ <attribute name="hidden-when">action-disabled</attribute>
+ </item>
+ <submenu>
+ <attribute name="label" translatable="yes">_Scripts</attribute>
+ <attribute name="nautilus-menu-item">scripts-submenu</attribute>
+ <section id="scripts-submenu-section"/>
+ <section>
+ <item>
+ <attribute name="label" translatable="yes">_Open Scripts Folder</attribute>
+ <attribute name="action">view.open-scripts-folder</attribute>
+ <attribute name="hidden-when">action-disabled</attribute>
+ </item>
+ </section>
+ </submenu>
+ </section>
+ <section id="drive-section">
+ <item>
+ <attribute name="label" translatable="yes">_Mount</attribute>
+ <attribute name="action">view.mount-volume</attribute>
+ <attribute name="hidden-when">action-disabled</attribute>
+ </item>
+ <item>
+ <attribute name="label" translatable="yes">_Unmount</attribute>
+ <attribute name="action">view.unmount-volume</attribute>
+ <attribute name="hidden-when">action-disabled</attribute>
+ </item>
+ <item>
+ <attribute name="label" translatable="yes">_Eject</attribute>
+ <attribute name="action">view.eject-volume</attribute>
+ <attribute name="hidden-when">action-disabled</attribute>
+ </item>
+ <item>
+ <attribute name="label" translatable="yes">_Start</attribute>
+ <attribute name="action">view.start-volume</attribute>
+ <attribute name="hidden-when">action-disabled</attribute>
+ </item>
+ <item>
+ <attribute name="label" translatable="yes">_Stop</attribute>
+ <attribute name="action">view.stop-volume</attribute>
+ <attribute name="hidden-when">action-disabled</attribute>
+ </item>
+ <item>
+ <attribute name="label" translatable="yes">_Detect Media</attribute>
+ <attribute name="action">view.detect-media</attribute>
+ <attribute name="hidden-when">action-disabled</attribute>
+ </item>
+ </section>
+ <section>
+ <item>
+ <attribute name="label" translatable="yes">Cu_t</attribute>
+ <attribute name="action">view.cut</attribute>
+ </item>
+ <item>
+ <attribute name="label" translatable="yes">_Copy</attribute>
+ <attribute name="action">view.copy</attribute>
+ </item>
+ <item>
+ <attribute name="label" translatable="yes">Move to…</attribute>
+ <attribute name="action">view.move-to</attribute>
+ </item>
+ <item>
+ <attribute name="label" translatable="yes">Copy to…</attribute>
+ <attribute name="action">view.copy-to</attribute>
+ </item>
+ </section>
+ <section>
+ <item>
+ <attribute name="label" translatable="yes">Rena_me…</attribute>
+ <attribute name="action">view.rename</attribute>
+ </item>
+ <item>
+ <attribute name="label" translatable="yes">_Paste Into Folder</attribute>
+ <attribute name="action">view.paste-into</attribute>
+ <attribute name="hidden-when">action-disabled</attribute>
+ </item>
+ <item>
+ <attribute name="label" translatable="yes">Create _Link</attribute>
+ <attribute name="action">view.create-link-in-place</attribute>
+ <attribute name="hidden-when">action-disabled</attribute>
+ </item>
+ <item>
+ <attribute name="label" translatable="yes">C_ompress…</attribute>
+ <attribute name="action">view.compress</attribute>
+ <attribute name="hidden-when">action-disabled</attribute>
+ </item>
+ <item>
+ <attribute name="label" translatable="yes">Set as Background…</attribute>
+ <attribute name="action">view.set-as-wallpaper</attribute>
+ <attribute name="hidden-when">action-disabled</attribute>
+ </item>
+ <item>
+ <attribute name="label" translatable="yes" comments="Translators: This is the transitive verb meaning 'to send via email' (e.g. 'email this document to Angela).">Email…</attribute>
+ <attribute name="action">view.send-email</attribute>
+ <attribute name="hidden-when">action-disabled</attribute>
+ </item>
+ <item>
+ <attribute name="label" translatable="yes">Mo_ve to Trash</attribute>
+ <attribute name="action">view.move-to-trash</attribute>
+ <attribute name="hidden-when">action-disabled</attribute>
+ </item>
+ <item>
+ <attribute name="label" translatable="yes">_Delete from Trash</attribute>
+ <attribute name="action">view.delete-from-trash</attribute>
+ <attribute name="hidden-when">action-disabled</attribute>
+ </item>
+ <item>
+ <attribute name="label" translatable="yes">_Delete Permanently…</attribute>
+ <attribute name="action">view.delete-permanently-menu-item</attribute>
+ <attribute name="hidden-when">action-disabled</attribute>
+ </item>
+ <item>
+ <attribute name="label" translatable="yes">_Delete Permanently…</attribute>
+ <attribute name="action">view.permanent-delete-permanently-menu-item</attribute>
+ <attribute name="hidden-when">action-disabled</attribute>
+ </item>
+ <item>
+ <attribute name="label" translatable="yes">_Restore From Trash</attribute>
+ <attribute name="action">view.restore-from-trash</attribute>
+ <attribute name="hidden-when">action-disabled</attribute>
+ </item>
+ <item>
+ <attribute name="label" translatable="yes">_Remove from Recent</attribute>
+ <attribute name="action">view.remove-from-recent</attribute>
+ <attribute name="hidden-when">action-disabled</attribute>
+ </item>
+ <item>
+ <attribute name="label" translatable="yes" comments="Unmarks a file as starred (starred)">Unstar</attribute>
+ <attribute name="action">view.unstar</attribute>
+ <attribute name="hidden-when">action-disabled</attribute>
+ </item>
+ </section>
+ <section id="selection-extensions-section"/>
+ <section>
+ <item>
+ <attribute name="label" translatable="yes">P_roperties</attribute>
+ <attribute name="action">view.properties</attribute>
+ </item>
+ </section>
+ </menu>
+</interface>
diff --git a/src/resources/ui/nautilus-files-view-select-items.ui b/src/resources/ui/nautilus-files-view-select-items.ui
new file mode 100644
index 0000000..614ed2a
--- /dev/null
+++ b/src/resources/ui/nautilus-files-view-select-items.ui
@@ -0,0 +1,54 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+ <requires lib="gtk" version="4.0"/>
+ <object class="GtkDialog" id="select_items_dialog">
+ <property name="title" translatable="yes">Select Items Matching</property>
+ <property name="modal">True</property>
+ <property name="use-header-bar">1</property>
+ <child type="action">
+ <object class="GtkButton" id="cancel">
+ <property name="label" translatable="yes">_Cancel</property>
+ <property name="use-underline">True</property>
+ </object>
+ </child>
+ <child type="action">
+ <object class="GtkButton" id="select">
+ <property name="label" translatable="yes">_Select</property>
+ <property name="use-underline">True</property>
+ </object>
+ </child>
+ <child internal-child="content_area">
+ <object class="GtkBox">
+ <property name="margin-top">18</property>
+ <property name="margin-bottom">18</property>
+ <property name="margin-start">18</property>
+ <property name="margin-end">18</property>
+ <property name="spacing">6</property>
+ <child>
+ <object class="GtkLabel">
+ <property name="halign">start</property>
+ <property name="label" translatable="yes">Pattern</property>
+ <attributes>
+ <attribute name="weight" value="bold"></attribute>
+ </attributes>
+ </object>
+ </child>
+ <child>
+ <object class="GtkEntry" id="pattern_entry">
+ <property name="hexpand">True</property>
+ <property name="activates_default">True</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkLabel" id="example">
+ <property name="halign">start</property>
+ </object>
+ </child>
+ </object>
+ </child>
+ <action-widgets>
+ <action-widget response="cancel">cancel</action-widget>
+ <action-widget response="ok" default="true">select</action-widget>
+ </action-widgets>
+ </object>
+</interface>
diff --git a/src/resources/ui/nautilus-files-view.ui b/src/resources/ui/nautilus-files-view.ui
new file mode 100644
index 0000000..eab5bdd
--- /dev/null
+++ b/src/resources/ui/nautilus-files-view.ui
@@ -0,0 +1,42 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+ <requires lib="gtk" version="4.0"/>
+ <template class="NautilusFilesView" parent="AdwBin">
+ <accessibility>
+ <property name="label" translatable="yes">Content View</property>
+ <property name="description" translatable="yes">View of the current folder</property>
+ </accessibility>
+ <child>
+ <object class="GtkOverlay" id="overlay">
+ <property name="hexpand">True</property>
+ <property name="vexpand">True</property>
+ <child>
+ <object class="GtkStack" id="stack">
+ <property name="hexpand">True</property>
+ <child>
+ <object class="GtkStackPage">
+ <property name="child">
+ <object class="AdwStatusPage" id="empty_view_page"/>
+ </property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkStackPage">
+ <property name="child">
+ <object class="GtkScrolledWindow" id="scrolled_window"/>
+ </property>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child type="overlay">
+ <object class="NautilusFloatingBar" id="floating_bar">
+ <property name="halign">end</property>
+ <property name="valign">end</property>
+ <property name="spacing">8</property>
+ </object>
+ </child>
+ </object>
+ </child>
+ </template>
+</interface>
diff --git a/src/resources/ui/nautilus-grid-cell.ui b/src/resources/ui/nautilus-grid-cell.ui
new file mode 100644
index 0000000..1a92040
--- /dev/null
+++ b/src/resources/ui/nautilus-grid-cell.ui
@@ -0,0 +1,109 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+ <requires lib="gtk" version="4.0"/>
+ <template class="NautilusGridCell" parent="NautilusViewCell">
+ <property name="valign">start</property>
+ <accessibility>
+ <relation name="labelled-by">label</relation>
+ </accessibility>
+ <child>
+ <object class="AdwClamp">
+ <property name="maximum-size">0</property>
+ <property name="child">
+ <object class="GtkBox">
+ <property name="orientation">vertical</property>
+ <property name="spacing">6</property>
+ <child>
+ <object class="GtkBox" id="fixed_height_box">
+ <property name="orientation">horizontal</property>
+ <property name="halign">center</property>
+ <property name="margin-start">18</property>
+ <child>
+ <object class="AdwClamp">
+ <property name="maximum-size">0</property>
+ <property name="child">
+ <object class="GtkPicture" id="icon">
+ <property name="halign">center</property>
+ <property name="hexpand">True</property>
+ <property name="valign">center</property>
+ <property name="vexpand">True</property>
+ <property name="can-shrink">False</property>
+ </object>
+ </property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkBox" id="emblems_box">
+ <property name="orientation">vertical</property>
+ <property name="halign">end</property>
+ <property name="spacing">6</property>
+ <property name="margin-start">2</property>
+ <property name="width-request">16</property>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child>
+ <object class="GtkBox">
+ <property name="orientation">vertical</property>
+ <style>
+ <class name="icon-ui-labels-box"/>
+ </style>
+ <child>
+ <object class="GtkLabel" id="label">
+ <property name="has-tooltip">true</property>
+ <property name="ellipsize">middle</property>
+ <property name="justify">center</property>
+ <property name="lines">3</property>
+ <property name="wrap">True</property>
+ <property name="wrap-mode">word-char</property>
+ <attributes>
+ <attribute name="insert-hyphens" value="false"></attribute>
+ </attributes>
+ </object>
+ </child>
+ <child>
+ <object class="GtkLabel" id="first_caption">
+ <property name="ellipsize">end</property>
+ <property name="justify">center</property>
+ <property name="lines">2</property>
+ <property name="wrap">True</property>
+ <property name="wrap-mode">word-char</property>
+ <property name="valign">start</property>
+ <style>
+ <class name="caption"/>
+ </style>
+ </object>
+ </child>
+ <child>
+ <object class="GtkLabel" id="second_caption">
+ <property name="ellipsize">end</property>
+ <property name="justify">center</property>
+ <property name="lines">2</property>
+ <property name="wrap">True</property>
+ <property name="wrap-mode">word-char</property>
+ <style>
+ <class name="caption"/>
+ </style>
+ </object>
+ </child>
+ <child>
+ <object class="GtkLabel" id="third_caption">
+ <property name="ellipsize">end</property>
+ <property name="justify">center</property>
+ <property name="lines">2</property>
+ <property name="wrap">True</property>
+ <property name="wrap-mode">word-char</property>
+ <style>
+ <class name="caption"/>
+ </style>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ </property>
+ </object>
+ </child>
+ </template>
+</interface>
diff --git a/src/resources/ui/nautilus-history-controls.ui b/src/resources/ui/nautilus-history-controls.ui
new file mode 100644
index 0000000..0fd0ce5
--- /dev/null
+++ b/src/resources/ui/nautilus-history-controls.ui
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+ <object class="GtkPopoverMenu" id="back_menu"/>
+ <object class="GtkPopoverMenu" id="forward_menu"/>
+ <template class="NautilusHistoryControls" parent="AdwBin">
+ <property name="child">
+ <object class="GtkBox">
+ <property name="spacing">6</property>
+ <child>
+ <object class="GtkButton" id="back_button">
+ <property name="tooltip_text" translatable="yes">Back</property>
+ <property name="valign">center</property>
+ <property name="action_name">win.back</property>
+ <property name="icon_name">go-previous-symbolic</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkButton" id="forward_button">
+ <property name="tooltip_text" translatable="yes">Forward</property>
+ <property name="valign">center</property>
+ <property name="action_name">win.forward</property>
+ <property name="icon_name">go-next-symbolic</property>
+ </object>
+ </child>
+ </object>
+ </property>
+ </template>
+</interface>
diff --git a/src/resources/ui/nautilus-list-view-column-editor.ui b/src/resources/ui/nautilus-list-view-column-editor.ui
new file mode 100644
index 0000000..c4cab58
--- /dev/null
+++ b/src/resources/ui/nautilus-list-view-column-editor.ui
@@ -0,0 +1,41 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+ <requires lib="gtk" version="4.0"/>
+ <object class="AdwWindow" id="window">
+ <property name="modal">True</property>
+ <property name="default-width">360</property>
+ <property name="default-height">440</property>
+ <child>
+ <object class="GtkBox">
+ <property name="orientation">vertical</property>
+ <child>
+ <object class="AdwHeaderBar">
+ <child type="title">
+ <object class="AdwWindowTitle" id="window_title">
+ <property name="title" translatable="true">Visible Columns</property>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child>
+ <object class="GtkBox" id="box">
+ <property name="margin-top">18</property>
+ <property name="margin-bottom">18</property>
+ <property name="margin-start">18</property>
+ <property name="margin-end">18</property>
+ <property name="orientation">vertical</property>
+ <property name="spacing">18</property>
+ <child>
+ <object class="GtkLabel">
+ <property name="label" translatable="yes">Choose the order of information to appear in this folder:</property>
+ <property name="xalign">0</property>
+ <property name="yalign">0</property>
+ <property name="wrap">True</property>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+</interface>
diff --git a/src/resources/ui/nautilus-name-cell.ui b/src/resources/ui/nautilus-name-cell.ui
new file mode 100644
index 0000000..246aa3a
--- /dev/null
+++ b/src/resources/ui/nautilus-name-cell.ui
@@ -0,0 +1,112 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+ <requires lib="gtk" version="4.0"/>
+ <template class="NautilusNameCell" parent="NautilusViewCell">
+ <accessibility>
+ <relation name="labelled-by">label</relation>
+ </accessibility>
+ <child>
+ <object class="GtkBox">
+ <property name="spacing">6</property>
+ <property name="orientation">horizontal</property>
+ <property name="halign">fill</property>
+ <property name="valign">center</property>
+ <child>
+ <object class="GtkBox" id="fixed_height_box">
+ <property name="orientation">vertical</property>
+ <property name="halign">center</property>
+ <property name="height-request">16</property>
+ <property name="valign">center</property>
+ <child>
+ <object class="GtkPicture" id="icon">
+ <property name="halign">center</property>
+ <property name="valign">center</property>
+ <property name="can-shrink">False</property>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child>
+ <object class="GtkBox">
+ <property name="orientation">vertical</property>
+ <property name="halign">fill</property>
+ <property name="hexpand">True</property>
+ <property name="valign">center</property>
+ <style>
+ <class name="column-name-labels-box"/>
+ </style>
+ <child>
+ <object class="GtkBox">
+ <property name="orientation">horizontal</property>
+ <property name="halign">fill</property>
+ <property name="hexpand">True</property>
+ <property name="spacing">6</property>
+ <child>
+ <object class="GtkLabel" id="label">
+ <property name="ellipsize">middle</property>
+ <property name="lines">1</property>
+ <property name="max-width-chars">-1</property>
+ <property name="wrap">False</property>
+ <property name="wrap-mode">word-char</property>
+ <property name="halign">start</property>
+ <attributes>
+ <attribute name="insert-hyphens" value="false"></attribute>
+ </attributes>
+ </object>
+ </child>
+ <child>
+ <object class="GtkBox" id="emblems_box">
+ <property name="orientation">horizontal</property>
+ <property name="halign">start</property>
+ <property name="spacing">6</property>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child>
+ <object class="GtkLabel" id="path">
+ <property name="visible">False</property>
+ <property name="ellipsize">start</property>
+ <property name="justify">left</property>
+ <property name="halign">fill</property>
+ <property name="xalign">0.0</property>
+ <attributes>
+ <attribute name="insert-hyphens" value="false"></attribute>
+ </attributes>
+ <style>
+ <class name="caption"/>
+ <class name="dim-label"/>
+ </style>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child>
+ <object class="GtkMenuButton" id="snippet_button">
+ <property name="tooltip-text" translatable="yes">Full text match</property>
+ <property name="visible">False</property>
+ <property name="icon-name">quotation-symbolic</property>
+ <property name="valign">center</property>
+ <style>
+ <class name="fts-snippet"/>
+ </style>
+ <property name="popover">
+ <object class="GtkPopover">
+ <child>
+ <object class="GtkLabel" id="snippet">
+ <property name="ellipsize">none</property>
+ <property name="justify">left</property>
+ <property name="max-width-chars">65</property>
+ <property name="lines">10</property>
+ <property name="wrap">True</property>
+ <property name="wrap-mode">word</property>
+ </object>
+ </child>
+ </object>
+ </property>
+ </object>
+ </child>
+ </object>
+ </child>
+ </template>
+</interface>
diff --git a/src/resources/ui/nautilus-operations-ui-manager-request-passphrase.ui b/src/resources/ui/nautilus-operations-ui-manager-request-passphrase.ui
new file mode 100644
index 0000000..8c87ed1
--- /dev/null
+++ b/src/resources/ui/nautilus-operations-ui-manager-request-passphrase.ui
@@ -0,0 +1,49 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+ <requires lib="gtk" version="4.0"/>
+ <object class="GtkDialog" id="request_passphrase_dialog">
+ <property name="title" translatable="yes">Password Required</property>
+ <property name="modal">True</property>
+ <property name="destroy-with-parent">True</property>
+ <property name="use-header-bar">1</property>
+ <child internal-child="content_area">
+ <object class="GtkBox">
+ <property name="margin-top">20</property>
+ <property name="margin-bottom">20</property>
+ <property name="margin-start">20</property>
+ <property name="margin-end">20</property>
+ <child>
+ <object class="GtkLabel" id="label">
+ <property name="max-width-chars">60</property>
+ <property name="wrap">True</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkEntry" id="entry">
+ <property name="activates-default">True</property>
+ <property name="valign">end</property>
+ <property name="vexpand">True</property>
+ <property name="placeholder-text" translatable="yes">Enter password…</property>
+ <property name="visibility">False</property>
+ <property name="input-purpose">password</property>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child type="action">
+ <object class="GtkButton" id="cancel_button">
+ <property name="label" translatable="yes">_Cancel</property>
+ <property name="use-underline">True</property>
+ </object>
+ </child>
+ <child type="action">
+ <object class="GtkButton" id="extract_button">
+ <property name="label" translatable="yes">Extract</property>
+ </object>
+ </child>
+ <action-widgets>
+ <action-widget response="cancel">cancel_button</action-widget>
+ <action-widget response="ok" default="true">extract_button</action-widget>
+ </action-widgets>
+ </object>
+</interface>
diff --git a/src/resources/ui/nautilus-pathbar-context-menu.ui b/src/resources/ui/nautilus-pathbar-context-menu.ui
new file mode 100644
index 0000000..c1f9a2c
--- /dev/null
+++ b/src/resources/ui/nautilus-pathbar-context-menu.ui
@@ -0,0 +1,73 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+ <requires lib="gtk" version="4.0"/>
+ <menu id="button-menu">
+ <item>
+ <attribute name="label" translatable="yes">Open in New _Window</attribute>
+ <attribute name="action">pathbar.open-item-new-window</attribute>
+ </item>
+ <item>
+ <attribute name="label" translatable="yes">Open in New _Tab</attribute>
+ <attribute name="action">pathbar.open-item-new-tab</attribute>
+ </item>
+ <item>
+ <attribute name="label" translatable="yes">_Properties</attribute>
+ <attribute name="action">pathbar.properties</attribute>
+ </item>
+ </menu>
+ <menu id="current-view-menu">
+ <item>
+ <attribute name="label" translatable="yes">New _Folder…</attribute>
+ <attribute name="action">view.new-folder</attribute>
+ </item>
+ <item>
+ <attribute name="label" translatable="yes">New _Document</attribute>
+ <attribute name="nautilus-menu-item">templates-submenu</attribute>
+ <link name="submenu" id="templates-submenu"/>
+ </item>
+ <item>
+ <attribute name="label" translatable="yes">Open _With…</attribute>
+ <attribute name="action">view.open-current-directory-with-other-application</attribute>
+ </item>
+ <item>
+ <attribute name="label" translatable="yes">Open in Consol_e</attribute>
+ <attribute name="action">view.current-directory-console</attribute>
+ <attribute name="hidden-when">action-disabled</attribute>
+ </item>
+ <section>
+ <item>
+ <attribute name="label" translatable="yes">R_eload</attribute>
+ <attribute name="action">win.reload</attribute>
+ <attribute name="hidden-when">action-disabled</attribute>
+ </item>
+ <item>
+ <attribute name="label" translatable="yes">St_op</attribute>
+ <attribute name="action">win.stop</attribute>
+ <attribute name="hidden-when">action-disabled</attribute>
+ </item>
+ <item>
+ <attribute name="label" translatable="yes">Add to _Bookmarks</attribute>
+ <attribute name="action">win.bookmark-current-location</attribute>
+ </item>
+ <item>
+ <attribute name="label" translatable="yes">_Copy Location</attribute>
+ <attribute name="action">view.copy-current-location</attribute>
+ </item>
+ </section>
+ <section id="background-extensions-section"/>
+ <section>
+ <item>
+ <attribute name="label" translatable="yes">Empty _Trash</attribute>
+ <attribute name="action">view.empty-trash</attribute>
+ <attribute name="hidden-when">action-disabled</attribute>
+ </item>
+ </section>
+ <section>
+ <item>
+ <attribute name="label" translatable="yes">P_roperties</attribute>
+ <attribute name="action">view.current-directory-properties</attribute>
+ <attribute name="hidden-when">action-disabled</attribute>
+ </item>
+ </section>
+ </menu>
+</interface>
diff --git a/src/resources/ui/nautilus-preferences-window.ui b/src/resources/ui/nautilus-preferences-window.ui
new file mode 100644
index 0000000..abee4f2
--- /dev/null
+++ b/src/resources/ui/nautilus-preferences-window.ui
@@ -0,0 +1,164 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+ <requires lib="gtk" version="4.0"/>
+ <object class="AdwPreferencesWindow" id="preferences_window">
+ <property name="search_enabled">False</property>
+ <property name="hide_on_close">True</property>
+ <child>
+ <object class="AdwPreferencesPage">
+ <property name="title" translatable="yes">General</property>
+ <property name="visible">True</property>
+ <child>
+ <object class="AdwPreferencesGroup">
+ <property name="title" translatable="yes">General</property>
+ <property name="visible">True</property>
+ <child>
+ <object class="AdwActionRow">
+ <property name="activatable_widget">sort_folders_first_switch</property>
+ <property name="subtitle_lines">0</property>
+ <property name="title" translatable="yes">Sort _Folders Before Files</property>
+ <property name="title_lines">0</property>
+ <property name="use_underline">True</property>
+ <property name="visible">True</property>
+ <child>
+ <object class="GtkSwitch" id="sort_folders_first_switch">
+ <property name="valign">center</property>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child>
+ <object class="AdwActionRow">
+ <property name="activatable_widget">use_tree_view_switch</property>
+ <property name="subtitle_lines">0</property>
+ <property name="title" translatable="yes">_Expandable Folders in List View</property>
+ <property name="title_lines">0</property>
+ <property name="use_underline">True</property>
+ <property name="visible">False</property>
+ <child>
+ <object class="GtkSwitch" id="use_tree_view_switch">
+ <property name="valign">center</property>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child>
+ <object class="AdwComboRow" id="open_action_row">
+ <property name="subtitle_lines">0</property>
+ <property name="title" translatable="yes">Action to Open Items</property>
+ <property name="title_lines">0</property>
+ <property name="use_underline">True</property>
+ <property name="visible">True</property>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child>
+ <object class="AdwPreferencesGroup">
+ <property name="title" translatable="yes">Optional Context Menu Actions</property>
+ <property name="description" translatable="yes">Show more actions in the menus. Keyboard shortcuts can be used even if the actions are not shown.</property>
+ <property name="visible">True</property>
+ <child>
+ <object class="AdwActionRow">
+ <property name="activatable_widget">show_create_link_switch</property>
+ <property name="subtitle_lines">0</property>
+ <property name="title" translatable="yes">Create _Link</property>
+ <property name="title_lines">0</property>
+ <property name="use_underline">True</property>
+ <property name="visible">True</property>
+ <child>
+ <object class="GtkSwitch" id="show_create_link_switch">
+ <property name="valign">center</property>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child>
+ <object class="AdwActionRow">
+ <property name="activatable_widget">show_delete_permanently_switch</property>
+ <property name="subtitle_lines">0</property>
+ <property name="title" translatable="yes">_Delete Permanently</property>
+ <property name="title_lines">0</property>
+ <property name="use_underline">True</property>
+ <property name="visible">True</property>
+ <child>
+ <object class="GtkSwitch" id="show_delete_permanently_switch">
+ <property name="valign">center</property>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child>
+ <object class="AdwPreferencesGroup">
+ <property name="title" translatable="yes">Performance</property>
+ <property name="description" translatable="yes">These features may cause slowdowns and excess network usage, especially when browsing files outside this computer, such as on a remote server.</property>
+ <property name="visible">True</property>
+ <child>
+ <object class="AdwComboRow" id="search_recursive_row">
+ <property name="subtitle_lines">0</property>
+ <property name="title" translatable="yes">Search in Subfolders</property>
+ <property name="title_lines">0</property>
+ <property name="use_underline">True</property>
+ <property name="visible">True</property>
+ </object>
+ </child>
+ <child>
+ <object class="AdwComboRow" id="thumbnails_row">
+ <property name="subtitle_lines">0</property>
+ <property name="title" translatable="yes">Show Thumbnails</property>
+ <property name="title_lines">0</property>
+ <property name="use_underline">True</property>
+ <property name="visible">True</property>
+ </object>
+ </child>
+ <child>
+ <object class="AdwComboRow" id="count_row">
+ <property name="subtitle_lines">0</property>
+ <property name="title" translatable="yes">Count Number of Files in Folders</property>
+ <property name="title_lines">0</property>
+ <property name="use_underline">True</property>
+ <property name="visible">True</property>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child>
+ <object class="AdwPreferencesGroup">
+ <property name="description" translatable="yes">Add information to be displayed beneath file and folder names. More information will appear when zooming closer.</property>
+ <property name="title" translatable="yes">Grid View Captions</property>
+ <property name="visible">True</property>
+ <child>
+ <object class="AdwComboRow" id="captions_0_comborow">
+ <property name="subtitle_lines">0</property>
+ <property name="title" translatable="yes" context="the n-th position of an icon caption" comments="Translators: This is an ordinal number">First</property>
+ <property name="title_lines">0</property>
+ <property name="use_underline">True</property>
+ <property name="visible">True</property>
+ </object>
+ </child>
+ <child>
+ <object class="AdwComboRow" id="captions_1_comborow">
+ <property name="subtitle_lines">0</property>
+ <property name="title" translatable="yes" context="the n-th position of an icon caption" comments="Translators: This is an ordinal number">Second</property>
+ <property name="title_lines">0</property>
+ <property name="use_underline">True</property>
+ <property name="visible">True</property>
+ </object>
+ </child>
+ <child>
+ <object class="AdwComboRow" id="captions_2_comborow">
+ <property name="subtitle_lines">0</property>
+ <property name="title" translatable="yes" context="the n-th position of an icon caption" comments="Translators: This is an ordinal number">Third</property>
+ <property name="title_lines">0</property>
+ <property name="use_underline">True</property>
+ <property name="visible">True</property>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+</interface>
diff --git a/src/resources/ui/nautilus-progress-indicator.ui b/src/resources/ui/nautilus-progress-indicator.ui
new file mode 100644
index 0000000..a44f8d1
--- /dev/null
+++ b/src/resources/ui/nautilus-progress-indicator.ui
@@ -0,0 +1,70 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+ <menu id="view_menu">
+ <section>
+ <attribute name="label" translatable="yes" context="menu item" comments="Translators: a menu item in a group of sorting options in a toolbar menu, with criterions such as &quot;A-Z&quot; or &quot;Last Modified&quot;.">Sort</attribute>
+ <!--
+ Sort section.
+
+ The toolbar code assumes this is the second item of this menu model.
+ Its contents is provided by the view.
+ -->
+ </section>
+ <section>
+ <item>
+ <attribute name="label" translatable="yes">_Visible Columns…</attribute>
+ <attribute name="action">view.visible-columns</attribute>
+ <attribute name="hidden-when">action-missing</attribute>
+ </item>
+ </section>
+ </menu>
+ <object class="GtkPopover" id="operations_popover">
+ <property name="child">
+ <object class="GtkScrolledWindow">
+ <property name="hscrollbar_policy">never</property>
+ <property name="max_content_height">270</property>
+ <property name="propagate_natural_height">True</property>
+ <property name="child">
+ <object class="GtkListBox" id="operations_list">
+ <property name="margin_start">6</property>
+ <property name="margin_end">6</property>
+ <property name="margin_top">6</property>
+ <property name="margin_bottom">6</property>
+ <property name="selection-mode">none</property>
+ <property name="activate-on-single-click">False</property>
+ <style>
+ <class name="operations-list"/>
+ </style>
+ </object>
+ </property>
+ </object>
+ </property>
+ <signal name="notify::visible" handler="on_operations_popover_notify_visible" object="NautilusProgressIndicator" swapped="yes"/>
+ </object>
+ <template class="NautilusProgressIndicator" parent="AdwBin">
+ <property name="child">
+ <object class="GtkRevealer" id="operations_revealer">
+ <property name="halign">center</property>
+ <property name="valign">center</property>
+ <property name="transition_type">slide-right</property>
+ <property name="child">
+ <object class="GtkMenuButton" id="operations_button">
+ <property name="tooltip_text" translatable="yes">Show operations</property>
+ <property name="popover">operations_popover</property>
+ <child>
+ <object class="GtkDrawingArea" id="operations_icon">
+ <property name="width_request">16</property>
+ <property name="height_request">16</property>
+ <property name="halign">center</property>
+ <property name="valign">center</property>
+ </object>
+ </child>
+ <style>
+ <class name="image-button"/>
+ </style>
+ </object>
+ </property>
+ </object>
+ </property>
+ </template>
+</interface>
diff --git a/src/resources/ui/nautilus-progress-info-widget.ui b/src/resources/ui/nautilus-progress-info-widget.ui
new file mode 100644
index 0000000..db18d99
--- /dev/null
+++ b/src/resources/ui/nautilus-progress-info-widget.ui
@@ -0,0 +1,75 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+ <requires lib="gtk" version="4.0"/>
+ <object class="GtkImage" id="cancel_image">
+ <property name="icon_name">window-close-symbolic</property>
+ </object>
+ <template class="NautilusProgressInfoWidget" parent="GtkGrid">
+ <property name="margin_start">6</property>
+ <property name="margin_end">6</property>
+ <property name="margin_top">6</property>
+ <property name="margin_bottom">6</property>
+ <child>
+ <object class="GtkLabel" id="status">
+ <property name="width_request">300</property>
+ <property name="margin_bottom">6</property>
+ <property name="hexpand">True</property>
+ <property name="label">status</property>
+ <property name="ellipsize">middle</property>
+ <property name="max_width_chars">40</property>
+ <property name="xalign">0</property>
+ <layout>
+ <property name="column">0</property>
+ <property name="row">0</property>
+ </layout>
+ </object>
+ </child>
+ <child>
+ <object class="GtkProgressBar" id="progress_bar">
+ <property name="valign">center</property>
+ <property name="margin_start">2</property>
+ <property name="margin_bottom">4</property>
+ <property name="hexpand">True</property>
+ <property name="pulse_step">0.050000000000000003</property>
+ <layout>
+ <property name="column">0</property>
+ <property name="row">1</property>
+ </layout>
+ </object>
+ </child>
+ <child>
+ <object class="GtkButton" id="button">
+ <property name="valign">center</property>
+ <property name="margin_start">20</property>
+ <property name="icon-name">window-close-symbolic</property>
+ <style>
+ <class name="circular"/>
+ </style>
+ <layout>
+ <property name="column">1</property>
+ <property name="row">0</property>
+ <property name="row-span">3</property>
+ </layout>
+ </object>
+ </child>
+ <child>
+ <object class="GtkLabel" id="details">
+ <property name="label">label</property>
+ <property name="wrap">True</property>
+ <property name="wrap_mode">word-char</property>
+ <property name="ellipsize">end</property>
+ <property name="xalign">0</property>
+ <style>
+ <class name="dim-label"/>
+ </style>
+ <attributes>
+ <attribute name="font-features" value="tnum"></attribute>
+ </attributes>
+ <layout>
+ <property name="column">0</property>
+ <property name="row">2</property>
+ </layout>
+ </object>
+ </child>
+ </template>
+</interface>
diff --git a/src/resources/ui/nautilus-properties-window.ui b/src/resources/ui/nautilus-properties-window.ui
new file mode 100644
index 0000000..b72fae9
--- /dev/null
+++ b/src/resources/ui/nautilus-properties-window.ui
@@ -0,0 +1,913 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+ <requires lib="gtk" version="4.0"/>
+ <template class="NautilusPropertiesWindow" parent="AdwWindow">
+ <property name="title" translatable="yes">Properties</property>
+ <property name="focusable">False</property>
+ <property name="modal">True</property>
+ <property name="default-width">480</property>
+ <child>
+ <object class="GtkStack" id="page_stack">
+ <property name="hexpand">True</property>
+ <property name="transition-type">over-left-right</property>
+ <child>
+ <object class="GtkStackPage">
+ <property name="name">main</property>
+ <property name="child">
+ <object class="GtkBox">
+ <property name="orientation">vertical</property>
+ <child>
+ <object class="GtkHeaderBar"/>
+ </child>
+ <child>
+ <object class="GtkScrolledWindow">
+ <property name="hscrollbar-policy">never</property>
+ <property name="vexpand">true</property>
+ <property name="min-content-height">480</property>
+ <property name="propagate-natural-height">true</property>
+ <property name="child">
+ <object class="AdwClamp">
+ <property name="margin-top">18</property>
+ <property name="margin-bottom">18</property>
+ <property name="margin-start">18</property>
+ <property name="margin-end">18</property>
+ <style>
+ <class name="background"/>
+ </style>
+ <property name="child">
+ <object class="GtkBox" id="basic_box">
+ <property name="orientation">vertical</property>
+ <property name="spacing">12</property>
+ <child>
+ <object class="GtkCenterBox">
+ <child type="center">
+ <object class="GtkStack" id="icon_stack">
+ <property name="halign">center</property>
+ <property name="valign">start</property>
+ <child>
+ <object class="GtkStackPage">
+ <property name="name">icon_image</property>
+ <property name="child">
+ <object class="GtkImage" id="icon_image">
+ <property name="icon-name">image-missing</property>
+ </object>
+ </property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkStackPage">
+ <property name="name">icon_button</property>
+ <property name="child">
+ <object class="GtkButton" id="icon_button">
+ <property name="focusable">True</property>
+ <property name="receives_default">True</property>
+ <child>
+ <object class="GtkImage" id="icon_button_image">
+ <property name="icon-name">image-missing</property>
+ </object>
+ </child>
+ </object>
+ </property>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child type="end">
+ <object class="GtkButton" id="star_button">
+ <property name="visible">false</property>
+ <property name="valign">start</property>
+ <signal name="clicked" handler="star_clicked" swapped="yes"/>
+ <style>
+ <class name="circular"/>
+ <class name="flat"/>
+ </style>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child>
+ <object class="GtkBox">
+ <property name="orientation">vertical</property>
+ <property name="spacing">6</property>
+ <child>
+ <object class="GtkLabel" id="name_value_label">
+ <property name="selectable">True</property>
+ <property name="ellipsize">end</property>
+ <property name="max-width-chars">24</property>
+ <property name="wrap">true</property>
+ <property name="wrap-mode">PANGO_WRAP_WORD_CHAR</property>
+ <property name="lines">3</property>
+ <property name="justify">center</property>
+ <style>
+ <class name="title-3"/>
+ </style>
+ </object>
+ </child>
+ <child>
+ <object class="GtkLabel" id="type_value_label">
+ <property name="ellipsize">end</property>
+ <property name="max-width-chars">24</property>
+ <property name="selectable">True</property>
+ <property name="visible">False</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkLabel" id="type_file_system_label">
+ <property name="ellipsize">end</property>
+ <property name="label" translatable="yes">Unknown Filesystem</property>
+ <property name="max-width-chars">24</property>
+ <property name="selectable">True</property>
+ <property name="visible">False</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkLabel" id="size_value_label">
+ <property name="visible">False</property>
+ <property name="selectable">True</property>
+ <property name="ellipsize">end</property>
+ <property name="max-width-chars">40</property>
+ <style>
+ <class name="caption"/>
+ </style>
+ </object>
+ </child>
+ <child>
+ <object class="GtkBox" id="contents_box">
+ <property name="halign">center</property>
+ <property name="visible">False</property>
+ <property name="spacing">12</property>
+ <child>
+ <object class="GtkLabel" id="contents_value_label">
+ <property name="selectable">True</property>
+ <property name="ellipsize">end</property>
+ <property name="justify">center</property>
+ <property name="max-width-chars">40</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkSpinner" id="contents_spinner">
+ <property name="visible">False</property>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child>
+ <object class="GtkLabel" id="free_space_value_label">
+ <property name="visible">False</property>
+ <property name="selectable">True</property>
+ <property name="ellipsize">end</property>
+ <property name="max-width-chars">24</property>
+ <style>
+ <class name="caption"/>
+ </style>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child>
+ <object class="GtkListBox" id="disk_list_box">
+ <property name="selection-mode">none</property>
+ <property name="visible">False</property>
+ <signal name="row-activated" handler="open_in_disks" object="NautilusPropertiesWindow" swapped="yes"/>
+ <child>
+ <object class="GtkListBoxRow">
+ <property name="activatable">False</property>
+ <property name="selectable">False</property>
+ <child>
+ <object class="GtkBox">
+ <property name="margin-bottom">12</property>
+ <property name="margin-end">12</property>
+ <property name="margin-start">12</property>
+ <property name="margin-top">18</property>
+ <property name="spacing">12</property>
+ <property name="orientation">vertical</property>
+ <child>
+ <object class="GtkLevelBar" id="disk_space_level_bar">
+ <property name="hexpand">True</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkBox">
+ <property name="spacing">12</property>
+ <property name="halign">center</property>
+ <child>
+ <object class="GtkBox">
+ <property name="spacing">3</property>
+ <child>
+ <object class="GtkLabel" id="disk_space_capacity_value">
+ <style>
+ <class name="dim-label"/>
+ </style>
+ </object>
+ </child>
+ <child>
+ <object class="GtkLabel">
+ <property name="label" translatable="yes">total</property>
+ <style>
+ <class name="dim-label"/>
+ </style>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child>
+ <object class="GtkBox">
+ <property name="spacing">3</property>
+ <child>
+ <object class="GtkLabel">
+ <property name="label">●</property>
+ <style>
+ <class name="disk-space-used"/>
+ </style>
+ </object>
+ </child>
+ <child>
+ <object class="GtkLabel" id="disk_space_used_value">
+ <style>
+ <class name="caption-style"/>
+ <class name="dim-label"/>
+ </style>
+ </object>
+ </child>
+ <child>
+ <object class="GtkLabel">
+ <property name="label" translatable="yes" comments="Refers to the capacity of the filesystem">used</property>
+ <style>
+ <class name="caption-style"/>
+ <class name="dim-label"/>
+ </style>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child>
+ <object class="GtkBox">
+ <property name="spacing">3</property>
+ <child>
+ <object class="GtkLabel">
+ <property name="label">●</property>
+ <style>
+ <class name="disk-space-free"/>
+ </style>
+ </object>
+ </child>
+ <child>
+ <object class="GtkLabel" id="disk_space_free_value">
+ <style>
+ <class name="caption-style"/>
+ <class name="dim-label"/>
+ </style>
+ </object>
+ </child>
+ <child>
+ <object class="GtkLabel">
+ <property name="label" translatable="yes" comments="Refers to the capacity of the filesystem">free</property>
+ <style>
+ <class name="caption-style"/>
+ <class name="dim-label"/>
+ </style>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child>
+ <object class="GtkListBoxRow">
+ <property name="activatable">True</property>
+ <property name="selectable">False</property>
+ <child>
+ <object class="GtkBox">
+ <property name="margin-bottom">12</property>
+ <property name="margin-end">12</property>
+ <property name="margin-start">12</property>
+ <property name="margin-top">12</property>
+ <property name="halign">center</property>
+ <property name="spacing">12</property>
+ <child>
+ <object class="GtkLabel">
+ <property name="label" translatable="yes" comments="Disks refers to GNOME Disks.">Open in Disks</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkImage">
+ <property name="icon-name">external-link-symbolic</property>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ <style>
+ <class name="boxed-list"/>
+ </style>
+ </object>
+ </child>
+ <child>
+ <object class="GtkListBox" id="locations_list_box">
+ <property name="visible">False</property>
+ <property name="selection-mode">none</property>
+ <child>
+ <object class="AdwActionRow" id="link_target_row">
+ <property name="visible">False</property>
+ <property name="activatable">False</property>
+ <property name="selectable">False</property>
+ <child type="prefix">
+ <object class="GtkBox">
+ <property name="orientation">vertical</property>
+ <property name="spacing">3</property>
+ <property name="halign">start</property>
+ <property name="valign">center</property>
+ <child>
+ <object class="GtkLabel">
+ <property name="label" translatable="yes">Link Target</property>
+ <property name="hexpand">True</property>
+ <property name="ellipsize">end</property>
+ <property name="halign">start</property>
+ <style>
+ <class name="caption"/>
+ <class name="dim-label"/>
+ </style>
+ </object>
+ </child>
+ <child>
+ <object class="GtkLabel" id="link_target_value_label">
+ <property name="hexpand">True</property>
+ <property name="selectable">True</property>
+ <property name="ellipsize">start</property>
+ <property name="halign">start</property>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child type="suffix">
+ <object class="GtkButton">
+ <property name="icon-name">folder-open-symbolic</property>
+ <property name="valign">center</property>
+ <signal name="clicked" handler="open_link_target" swapped="yes"/>
+ <style>
+ <class name="flat"/>
+ </style>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child>
+ <object class="AdwActionRow" id="parent_folder_row">
+ <property name="visible">False</property>
+ <property name="activatable">False</property>
+ <property name="selectable">False</property>
+ <child type="prefix">
+ <object class="GtkBox">
+ <property name="orientation">vertical</property>
+ <property name="spacing">3</property>
+ <property name="halign">start</property>
+ <property name="valign">center</property>
+ <child>
+ <object class="GtkLabel">
+ <property name="label" translatable="yes">Parent Folder</property>
+ <property name="hexpand">True</property>
+ <property name="ellipsize">end</property>
+ <property name="halign">start</property>
+ <style>
+ <class name="caption"/>
+ <class name="dim-label"/>
+ </style>
+ </object>
+ </child>
+ <child>
+ <object class="GtkLabel" id="parent_folder_value_label">
+ <property name="hexpand">True</property>
+ <property name="selectable">True</property>
+ <property name="ellipsize">start</property>
+ <property name="halign">start</property>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child type="suffix">
+ <object class="GtkButton">
+ <property name="icon-name">folder-open-symbolic</property>
+ <property name="valign">center</property>
+ <signal name="clicked" handler="open_parent_folder" swapped="yes"/>
+ <style>
+ <class name="flat"/>
+ </style>
+ </object>
+ </child>
+ </object>
+ </child>
+ <style>
+ <class name="boxed-list"/>
+ </style>
+ </object>
+ </child>
+ <child>
+ <object class="GtkListBox" id="trashed_list_box">
+ <property name="selection-mode">none</property>
+ <property name="visible">False</property>
+ <child>
+ <object class="AdwActionRow">
+ <property name="activatable">False</property>
+ <property name="selectable">False</property>
+ <child type="prefix">
+ <object class="GtkBox">
+ <property name="orientation">vertical</property>
+ <property name="spacing">3</property>
+ <property name="halign">start</property>
+ <property name="valign">center</property>
+ <child>
+ <object class="GtkLabel">
+ <property name="label" translatable="yes">Original Folder</property>
+ <property name="hexpand">True</property>
+ <property name="ellipsize">end</property>
+ <property name="halign">start</property>
+ <style>
+ <class name="caption"/>
+ <class name="dim-label"/>
+ </style>
+ </object>
+ </child>
+ <child>
+ <object class="GtkLabel" id="original_folder_value_label">
+ <property name="hexpand">True</property>
+ <property name="selectable">True</property>
+ <property name="ellipsize">start</property>
+ <property name="halign">start</property>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child>
+ <object class="AdwActionRow">
+ <property name="activatable">False</property>
+ <property name="selectable">False</property>
+ <child type="prefix">
+ <object class="GtkBox">
+ <property name="orientation">vertical</property>
+ <property name="spacing">3</property>
+ <property name="halign">start</property>
+ <property name="valign">center</property>
+ <child>
+ <object class="GtkLabel">
+ <property name="label" translatable="yes">Trashed on</property>
+ <property name="hexpand">True</property>
+ <property name="ellipsize">end</property>
+ <property name="halign">start</property>
+ <style>
+ <class name="caption"/>
+ <class name="dim-label"/>
+ </style>
+ </object>
+ </child>
+ <child>
+ <object class="GtkLabel" id="trashed_on_value_label">
+ <property name="hexpand">True</property>
+ <property name="selectable">True</property>
+ <property name="ellipsize">end</property>
+ <property name="halign">start</property>
+ <style>
+ <class name="numeric"/>
+ </style>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ <style>
+ <class name="boxed-list"/>
+ </style>
+ </object>
+ </child>
+ <child>
+ <object class="GtkListBox" id="times_list_box">
+ <property name="selection-mode">none</property>
+ <property name="visible">False</property>
+ <child>
+ <object class="AdwActionRow" id="accessed_row">
+ <property name="visible">False</property>
+ <property name="activatable">False</property>
+ <property name="selectable">False</property>
+ <child type="prefix">
+ <object class="GtkBox">
+ <property name="orientation">vertical</property>
+ <property name="spacing">3</property>
+ <property name="halign">start</property>
+ <property name="valign">center</property>
+ <child>
+ <object class="GtkLabel">
+ <property name="label" translatable="yes">Accessed</property>
+ <property name="hexpand">True</property>
+ <property name="ellipsize">end</property>
+ <property name="halign">start</property>
+ <style>
+ <class name="caption"/>
+ <class name="dim-label"/>
+ </style>
+ </object>
+ </child>
+ <child>
+ <object class="GtkLabel" id="accessed_value_label">
+ <property name="hexpand">True</property>
+ <property name="selectable">True</property>
+ <property name="ellipsize">end</property>
+ <property name="halign">start</property>
+ <style>
+ <class name="numeric"/>
+ </style>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child>
+ <object class="AdwActionRow" id="modified_row">
+ <property name="visible">False</property>
+ <property name="activatable">False</property>
+ <property name="selectable">False</property>
+ <child type="prefix">
+ <object class="GtkBox">
+ <property name="orientation">vertical</property>
+ <property name="spacing">3</property>
+ <property name="halign">start</property>
+ <property name="valign">center</property>
+ <child>
+ <object class="GtkLabel">
+ <property name="label" translatable="yes">Modified</property>
+ <property name="hexpand">True</property>
+ <property name="ellipsize">end</property>
+ <property name="halign">start</property>
+ <style>
+ <class name="caption"/>
+ <class name="dim-label"/>
+ </style>
+ </object>
+ </child>
+ <child>
+ <object class="GtkLabel" id="modified_value_label">
+ <property name="hexpand">True</property>
+ <property name="selectable">True</property>
+ <property name="ellipsize">end</property>
+ <property name="halign">start</property>
+ <style>
+ <class name="numeric"/>
+ </style>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child>
+ <object class="AdwActionRow" id="created_row">
+ <property name="visible">False</property>
+ <property name="activatable">False</property>
+ <property name="selectable">False</property>
+ <child type="prefix">
+ <object class="GtkBox">
+ <property name="orientation">vertical</property>
+ <property name="spacing">3</property>
+ <property name="halign">start</property>
+ <property name="valign">center</property>
+ <child>
+ <object class="GtkLabel">
+ <property name="label" translatable="yes">Created</property>
+ <property name="hexpand">True</property>
+ <property name="ellipsize">end</property>
+ <property name="halign">start</property>
+ <style>
+ <class name="caption"/>
+ <class name="dim-label"/>
+ </style>
+ </object>
+ </child>
+ <child>
+ <object class="GtkLabel" id="created_value_label">
+ <property name="hexpand">True</property>
+ <property name="selectable">True</property>
+ <property name="ellipsize">end</property>
+ <property name="halign">start</property>
+ <style>
+ <class name="numeric"/>
+ </style>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ <style>
+ <class name="boxed-list"/>
+ </style>
+ </object>
+ </child>
+ <child>
+ <object class="GtkListBox" id="permissions_list_box">
+ <property name="selection-mode">none</property>
+ <child>
+ <object class="AdwActionRow" id="permissions_navigation_row">
+ <property name="activatable">True</property>
+ <property name="selectable">False</property>
+ <property name="title" translatable="yes">_Permissions</property>
+ <property name="use-underline">True</property>
+ <signal name="activated" handler="navigate_permissions_page" object="NautilusPropertiesWindow" swapped="yes"/>
+ <child>
+ <object class="GtkLabel" id="permissions_value_label">
+ <property name="ellipsize">end</property>
+ <style>
+ <class name="dim-label"/>
+ </style>
+ </object>
+ </child>
+ <child>
+ <object class="GtkImage">
+ <property name="icon-name">go-next-symbolic</property>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child>
+ <object class="AdwActionRow" id="execution_row">
+ <property name="visible">False</property>
+ <property name="activatable">True</property>
+ <property name="title" translatable="yes">_Executable as Program</property>
+ <property name="use-underline">True</property>
+ <child>
+ <object class="GtkSwitch" id="execution_switch">
+ <property name="halign">center</property>
+ <property name="valign">center</property>
+ </object>
+ </child>
+ </object>
+ </child>
+ <style>
+ <class name="boxed-list"/>
+ </style>
+ </object>
+ </child>
+ <child>
+ <object class="GtkListBox" id="extension_models_list_box">
+ <property name="selection-mode">none</property>
+ <style>
+ <class name="boxed-list"/>
+ </style>
+ </object>
+ </child>
+ </object>
+ </property>
+ </object>
+ </property>
+ </object>
+ </child>
+ </object>
+ </property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkStackPage">
+ <property name="name">permissions</property>
+ <property name="child">
+ <object class="GtkBox">
+ <property name="orientation">vertical</property>
+ <style>
+ <class name="background"/>
+ </style>
+ <child>
+ <object class="GtkHeaderBar">
+ <property name="title-widget">
+ <object class="AdwWindowTitle">
+ <property name="title" translatable="yes">Set Custom Permissions</property>
+ </object>
+ </property>
+ <child type="start">
+ <object class="GtkButton">
+ <property name="icon-name">go-previous-symbolic</property>
+ <signal name="clicked" handler="navigate_main_page" object="NautilusPropertiesWindow" swapped="yes"/>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child>
+ <object class="GtkScrolledWindow">
+ <property name="hscrollbar-policy">never</property>
+ <property name="vexpand">true</property>
+ <property name="child">
+ <object class="AdwClamp">
+ <property name="margin-top">18</property>
+ <property name="margin-bottom">18</property>
+ <property name="margin-start">18</property>
+ <property name="margin-end">18</property>
+ <property name="child">
+ <object class="GtkStack" id="permissions_stack">
+ <child>
+ <object class="GtkStackPage">
+ <property name="name">permission-indeterminable</property>
+ <property name="child">
+ <object class="AdwStatusPage" id="unknown_permissions_page">
+ <property name="icon-name">dialog-error-symbolic</property>
+ <property name="title" translatable="yes">Unknown Permissions</property>
+ <property name="description" translatable="yes">The permissions of the selected files could not be determined.</property>
+ </object>
+ </property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkStackPage">
+ <property name="name">permissions-box</property>
+ <property name="child">
+ <object class="GtkBox" id="permissions_box">
+ <property name="orientation">vertical</property>
+ <property name="spacing">12</property>
+ <child>
+ <object class="GtkLabel" id="not_the_owner_label">
+ <property name="visible">False</property>
+ <property name="label" translatable="yes">You are not the owner, so you cannot change these permissions.</property>
+ <property name="justify">center</property>
+ <property name="wrap">1</property>
+ <property name="max_width_chars">40</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkSeparator" id="bottom_prompt_seperator">
+ <property name="visible">False</property>
+ <property name="margin-top">12</property>
+ <property name="margin-bottom">12</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkLabel" id="permission_indeterminable_label">
+ <property name="visible">False</property>
+ <property name="label" translatable="yes">The permissions of the selected file could not be determined.</property>
+ <property name="justify">center</property>
+ <property name="wrap">1</property>
+ <property name="max_width_chars">40</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkListBox" id="owner_list_box">
+ <property name="selection-mode">none</property>
+ <child>
+ <object class="AdwComboRow" id="owner_row">
+ <property name="sensitive">False</property>
+ <property name="activatable">False</property>
+ <property name="title" translatable="yes">_Owner</property>
+ <property name="use-underline">True</property>
+ </object>
+ </child>
+ <child>
+ <object class="AdwComboRow" id="owner_access_row">
+ <property name="visible">False</property>
+ <property name="activatable">False</property>
+ <property name="title" translatable="yes">Access</property>
+ </object>
+ </child>
+ <child>
+ <object class="AdwComboRow" id="owner_folder_access_row">
+ <property name="visible">False</property>
+ <property name="activatable">False</property>
+ <property name="title" translatable="yes">Folder Access</property>
+ </object>
+ </child>
+ <child>
+ <object class="AdwComboRow" id="owner_file_access_row">
+ <property name="visible">False</property>
+ <property name="activatable">False</property>
+ <property name="title" translatable="yes">File Access</property>
+ </object>
+ </child>
+ <style>
+ <class name="boxed-list"/>
+ </style>
+ </object>
+ </child>
+ <child>
+ <object class="GtkListBox" id="group_list_box">
+ <property name="selection-mode">none</property>
+ <child>
+ <object class="AdwComboRow" id="group_row">
+ <property name="sensitive">False</property>
+ <property name="activatable">False</property>
+ <property name="title" translatable="yes">_Group</property>
+ <property name="use-underline">True</property>
+ </object>
+ </child>
+ <child>
+ <object class="AdwComboRow" id="group_access_row">
+ <property name="visible">False</property>
+ <property name="activatable">False</property>
+ <property name="title" translatable="yes">Access</property>
+ </object>
+ </child>
+ <child>
+ <object class="AdwComboRow" id="group_folder_access_row">
+ <property name="visible">False</property>
+ <property name="activatable">False</property>
+ <property name="title" translatable="yes">Folder Access</property>
+ </object>
+ </child>
+ <child>
+ <object class="AdwComboRow" id="group_file_access_row">
+ <property name="visible">False</property>
+ <property name="activatable">False</property>
+ <property name="title" translatable="yes">File Access</property>
+ </object>
+ </child>
+ <style>
+ <class name="boxed-list"/>
+ </style>
+ </object>
+ </child>
+ <child>
+ <object class="GtkListBox" id="others_list_box">
+ <property name="selection-mode">none</property>
+ <child>
+ <object class="AdwComboRow" id="others_row">
+ <property name="activatable">False</property>
+ <property name="title" translatable="yes">Others</property>
+ </object>
+ </child>
+ <child>
+ <object class="AdwComboRow" id="others_access_row">
+ <property name="visible">False</property>
+ <property name="activatable">False</property>
+ <property name="title" translatable="yes">Access</property>
+ </object>
+ </child>
+ <child>
+ <object class="AdwComboRow" id="others_folder_access_row">
+ <property name="visible">False</property>
+ <property name="activatable">False</property>
+ <property name="title" translatable="yes">Folder Access</property>
+ </object>
+ </child>
+ <child>
+ <object class="AdwComboRow" id="others_file_access_row">
+ <property name="visible">False</property>
+ <property name="activatable">False</property>
+ <property name="title" translatable="yes">File Access</property>
+ </object>
+ </child>
+ <style>
+ <class name="boxed-list"/>
+ </style>
+ </object>
+ </child>
+ <child>
+ <object class="GtkListBox" id="security_context_list_box">
+ <property name="visible">False</property>
+ <child>
+ <object class="AdwActionRow">
+ <property name="title" translatable="yes">Security Context</property>
+ <child>
+ <object class="GtkLabel" id="security_context_value_label">
+ <property name="selectable">True</property>
+ <property name="max-width-chars">24</property>
+ <property name="xalign">1</property>
+ </object>
+ </child>
+ </object>
+ </child>
+ <style>
+ <class name="boxed-list"/>
+ </style>
+ </object>
+ </child>
+ <child>
+ <object class="GtkBox" id="change_permissions_button_box">
+ <property name="visible">False</property>
+ <property name="margin_top">12</property>
+ <child>
+ <object class="GtkButton" id="change_permissions_button">
+ <property name="label" translatable="yes">Change Permissions for Enclosed Files…</property>
+ <property name="focusable">True</property>
+ <property name="receives_default">True</property>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ </property>
+ </object>
+ </child>
+ </object>
+ </property>
+ </object>
+ </property>
+ </object>
+ </child>
+ </object>
+ </property>
+ </object>
+ </child>
+ </object>
+ </child>
+ </template>
+</interface>
diff --git a/src/resources/ui/nautilus-rename-file-popover.ui b/src/resources/ui/nautilus-rename-file-popover.ui
new file mode 100644
index 0000000..205a85c
--- /dev/null
+++ b/src/resources/ui/nautilus-rename-file-popover.ui
@@ -0,0 +1,54 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+ <requires lib="gtk" version="4.0"/>
+ <object class="GtkPopover" id="rename_file_popover">
+ <property name="child">
+ <object class="GtkBox">
+ <property name="margin-start">18</property>
+ <property name="margin-end">18</property>
+ <property name="margin-top">18</property>
+ <property name="margin-bottom">18</property>
+ <property name="orientation">vertical</property>
+ <child>
+ <object class="GtkLabel" id="title_label">
+ <property name="margin-bottom">12</property>
+ <style>
+ <class name="title-2"/>
+ </style>
+ </object>
+ </child>
+ <child>
+ <object class="GtkEntry" id="name_entry">
+ <accessibility>
+ <property name="label" translatable="yes">New Filename</property>
+ </accessibility>
+ <property name="margin-bottom">12</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkRevealer" id="error_revealer">
+ <property name="child">
+ <object class="GtkLabel" id="error_label">
+ <property name="margin-bottom">12</property>
+ <property name="max-width-chars">0</property>
+ <property name="wrap">True</property>
+ <property name="xalign">0</property>
+ </object>
+ </property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkButton" id="rename_button">
+ <property name="label" translatable="yes">_Rename</property>
+ <property name="sensitive">False</property>
+ <property name="halign">end</property>
+ <property name="use-underline">True</property>
+ <style>
+ <class name="suggested-action"/>
+ </style>
+ </object>
+ </child>
+ </object>
+ </property>
+ </object>
+</interface>
diff --git a/src/resources/ui/nautilus-search-popover.ui b/src/resources/ui/nautilus-search-popover.ui
new file mode 100644
index 0000000..08ce86a
--- /dev/null
+++ b/src/resources/ui/nautilus-search-popover.ui
@@ -0,0 +1,336 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+ <requires lib="gtk" version="4.0"/>
+ <template class="NautilusSearchPopover" parent="GtkPopover">
+ <property name="child">
+ <object class="GtkGrid">
+ <property name="margin-top">20</property>
+ <property name="margin-bottom">20</property>
+ <property name="margin-start">20</property>
+ <property name="margin-end">20</property>
+ <property name="row_spacing">8</property>
+ <property name="column_spacing">18</property>
+ <child>
+ <object class="GtkLabel" id="when_dim_label">
+ <property name="label" translatable="yes">When</property>
+ <property name="xalign">0</property>
+ <style>
+ <class name="dim-label"/>
+ </style>
+ <layout>
+ <property name="column">0</property>
+ <property name="row">0</property>
+ <property name="column-span">2</property>
+ </layout>
+ </object>
+ </child>
+ <child>
+ <object class="GtkStack" id="date_stack">
+ <property name="transition_type">crossfade</property>
+ <property name="transition_duration">250</property>
+ <child>
+ <object class="GtkStackPage">
+ <property name="name">date-button</property>
+ <property name="child">
+ <object class="GtkBox">
+ <child>
+ <object class="GtkButton" id="select_date_button">
+ <property name="tooltip_text" translatable="yes">Select a date</property>
+ <property name="hexpand">True</property>
+ <child>
+ <object class="GtkLabel" id="select_date_button_label">
+ <property name="label" translatable="yes">Select Dates…</property>
+ <property name="xalign">0</property>
+ </object>
+ </child>
+ <signal name="clicked" handler="select_date_button_clicked" object="NautilusSearchPopover" swapped="no"/>
+ </object>
+ </child>
+ <child>
+ <object class="GtkButton" id="clear_date_button">
+ <property name="visible">False</property>
+ <property name="tooltip_text" translatable="yes">Clear the currently selected date</property>
+ <property name="icon_name">edit-clear-symbolic</property>
+ <signal name="clicked" handler="clear_date_button_clicked" object="NautilusSearchPopover" swapped="no"/>
+ </object>
+ </child>
+ <style>
+ <class name="linked"/>
+ </style>
+ </object>
+ </property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkStackPage">
+ <property name="name">date-entry</property>
+ <property name="child">
+ <object class="GtkEntry" id="date_entry">
+ <property name="secondary_icon_name">x-office-calendar-symbolic</property>
+ <property name="secondary_icon_tooltip_text" translatable="yes">Show a calendar to select the date</property>
+ <signal name="icon-release" handler="toggle_calendar_icon_clicked" object="NautilusSearchPopover" swapped="no"/>
+ <signal name="activate" handler="date_entry_activate" object="NautilusSearchPopover" swapped="no"/>
+ </object>
+ </property>
+ </object>
+ </child>
+ <layout>
+ <property name="column">0</property>
+ <property name="row">1</property>
+ <property name="column-span">2</property>
+ </layout>
+ </object>
+ </child>
+ <child>
+ <object class="GtkRevealer" id="around_revealer">
+ <property name="child">
+ <object class="GtkGrid">
+ <property name="row_spacing">8</property>
+ <property name="column_spacing">12</property>
+ <child>
+ <object class="GtkLabel" id="around_dim_label">
+ <property name="margin_top">10</property>
+ <property name="label" translatable="yes">Since…</property>
+ <property name="xalign">0</property>
+ <style>
+ <class name="dim-label"/>
+ </style>
+ <layout>
+ <property name="column">0</property>
+ <property name="row">0</property>
+ <property name="column-span">2</property>
+ </layout>
+ </object>
+ </child>
+ <child>
+ <object class="GtkStack" id="around_stack">
+ <property name="vhomogeneous">False</property>
+ <property name="transition_type">crossfade</property>
+ <property name="transition_duration">250</property>
+ <child>
+ <object class="GtkStackPage">
+ <property name="name">date-list</property>
+ <property name="child">
+ <object class="GtkScrolledWindow">
+ <property name="height_request">200</property>
+ <property name="hexpand">True</property>
+ <property name="child">
+ <object class="GtkViewport">
+ <property name="child">
+ <object class="GtkListBox" id="dates_listbox">
+ <property name="selection_mode">none</property>
+ <signal name="row-activated" handler="dates_listbox_row_activated" object="NautilusSearchPopover" swapped="no"/>
+ </object>
+ </property>
+ </object>
+ </property>
+ </object>
+ </property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkStackPage">
+ <property name="name">date-calendar</property>
+ <property name="child">
+ <object class="GtkCalendar" id="calendar">
+ <property name="valign">start</property>
+ <property name="show_week_numbers">True</property>
+ <signal name="day-selected" handler="calendar_day_selected" object="NautilusSearchPopover" swapped="no" />
+ </object>
+ </property>
+ </object>
+ </child>
+ <layout>
+ <property name="column">0</property>
+ <property name="row">1</property>
+ <property name="column-span">2</property>
+ </layout>
+ </object>
+ </child>
+ <child>
+ <object class="GtkCheckButton" id="last_modified_button">
+ <property name="label" translatable="yes">Last _modified</property>
+ <property name="use_underline">True</property>
+ <property name="active">True</property>
+ <signal name="toggled" handler="search_time_type_changed" object="NautilusSearchPopover" swapped="no"/>
+ <layout>
+ <property name="column">0</property>
+ <property name="row">2</property>
+ </layout>
+ </object>
+ </child>
+ <child>
+ <object class="GtkCheckButton" id="last_used_button">
+ <property name="label" translatable="yes">Last _used</property>
+ <property name="use_underline">True</property>
+ <property name="group">last_modified_button</property>
+ <signal name="toggled" handler="search_time_type_changed" object="NautilusSearchPopover" swapped="no"/>
+ <layout>
+ <property name="column">0</property>
+ <property name="row">3</property>
+ </layout>
+ </object>
+ </child>
+ <child>
+ <object class="GtkCheckButton" id="created_button">
+ <property name="label" translatable="yes">Created</property>
+ <property name="use-underline">True</property>
+ <property name="group">last_modified_button</property>
+ <signal name="toggled" handler="search_time_type_changed" object="NautilusSearchPopover" swapped="no"/>
+ <layout>
+ <property name="column">0</property>
+ <property name="row">4</property>
+ </layout>
+ </object>
+ </child>
+ </object>
+ </property>
+ <layout>
+ <property name="column">0</property>
+ <property name="row">2</property>
+ <property name="column-span">2</property>
+ </layout>
+ </object>
+ </child>
+ <child>
+ <object class="GtkLabel" id="what_dim_label">
+ <property name="margin_top">10</property>
+ <property name="label" translatable="yes">What</property>
+ <property name="xalign">0</property>
+ <style>
+ <class name="dim-label"/>
+ </style>
+ <layout>
+ <property name="column">0</property>
+ <property name="row">3</property>
+ <property name="column-span">2</property>
+ </layout>
+ </object>
+ </child>
+ <child>
+ <object class="GtkStack" id="type_stack">
+ <property name="vhomogeneous">False</property>
+ <property name="transition_type">crossfade</property>
+ <property name="transition_duration">250</property>
+ <child>
+ <object class="GtkStackPage">
+ <property name="name">type-button</property>
+ <property name="child">
+ <object class="GtkButton" id="select_type_button">
+ <property name="tooltip_text" translatable="yes">Which file types will be searched</property>
+ <signal name="clicked" handler="select_type_button_clicked" object="NautilusSearchPopover" swapped="no"/>
+ <child>
+ <object class="GtkBox">
+ <child>
+ <object class="GtkLabel" id="type_label">
+ <property name="hexpand">True</property>
+ <property name="label" translatable="yes">Anything</property>
+ <property name="width_chars">30</property>
+ <property name="xalign">0</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkImage">
+ <property name="icon_name">pan-down-symbolic</property>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ </property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkStackPage">
+ <property name="name">type-list</property>
+ <property name="child">
+ <object class="GtkScrolledWindow">
+ <property name="height_request">250</property>
+ <property name="child">
+ <object class="GtkViewport">
+ <property name="child">
+ <object class="GtkListBox" id="type_listbox">
+ <signal name="row-activated" handler="types_listbox_row_activated" object="NautilusSearchPopover" swapped="no"/>
+ </object>
+ </property>
+ </object>
+ </property>
+ </object>
+ </property>
+ </object>
+ </child>
+ <layout>
+ <property name="column">0</property>
+ <property name="row">4</property>
+ <property name="column-span">2</property>
+ </layout>
+ </object>
+ </child>
+ <child>
+ <object class="GtkLabel" id="search_dim_label">
+ <property name="margin_top">10</property>
+ <property name="label" translatable="yes">Search</property>
+ <property name="xalign">0</property>
+ <style>
+ <class name="dim-label"/>
+ </style>
+ <layout>
+ <property name="column">0</property>
+ <property name="row">5</property>
+ <property name="column-span">2</property>
+ </layout>
+ </object>
+ </child>
+ <child>
+ <object class="GtkBox">
+ <property name="hexpand">True</property>
+ <child>
+ <object class="GtkToggleButton" id="full_text_search_button">
+ <property name="label" translatable="yes">Full Text</property>
+ <property name="tooltip_text" translatable="yes">Search on the file content and name</property>
+ <property name="hexpand">True</property>
+ <property name="active">True</property>
+ <signal name="toggled" handler="search_fts_mode_changed" object="NautilusSearchPopover" swapped="no" />
+ </object>
+ </child>
+ <child>
+ <object class="GtkToggleButton" id="filename_search_button">
+ <property name="label" translatable="yes">File Name</property>
+ <property name="tooltip_text" translatable="yes">Search only on the file name</property>
+ <property name="hexpand">True</property>
+ <property name="group">full_text_search_button</property>
+ <property name="active">True</property>
+ <signal name="toggled" handler="search_fts_mode_changed" object="NautilusSearchPopover" swapped="no"/>
+ </object>
+ </child>
+ <style>
+ <class name="linked"/>
+ </style>
+ <layout>
+ <property name="column">0</property>
+ <property name="row">6</property>
+ <property name="column-span">2</property>
+ </layout>
+ </object>
+ </child>
+ </object>
+ </property>
+ </template>
+ <object class="GtkSizeGroup">
+ <property name="mode">vertical</property>
+ <widgets>
+ <widget name="full_text_search_button"/>
+ <widget name="filename_search_button"/>
+ <widget name="select_date_button"/>
+ <widget name="clear_date_button"/>
+ </widgets>
+ </object>
+ <object class="GtkSizeGroup">
+ <widgets>
+ <widget name="search_dim_label"/>
+ <widget name="when_dim_label"/>
+ <widget name="around_dim_label"/>
+ <widget name="what_dim_label"/>
+ </widgets>
+ </object>
+</interface>
diff --git a/src/resources/ui/nautilus-toolbar-view-menu.ui b/src/resources/ui/nautilus-toolbar-view-menu.ui
new file mode 100644
index 0000000..ffdd3ac
--- /dev/null
+++ b/src/resources/ui/nautilus-toolbar-view-menu.ui
@@ -0,0 +1,63 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+ <requires lib="gtk" version="4.0"/>
+ <menu id="sort_section">
+ <item>
+ <attribute name="action">view.sort</attribute>
+ <attribute name="target" type="(sb)">('name',false)</attribute>
+ <attribute name="label" translatable="yes" context="Sort Criterion" comments="This is used to sort by name in the toolbar view menu">_A-Z</attribute>
+ <attribute name="hidden-when">action-disabled</attribute>
+ </item>
+ <item>
+ <attribute name="action">view.sort</attribute>
+ <attribute name="target" type="(sb)">('name',true)</attribute>
+ <attribute name="label" translatable="yes" context="Sort Criterion" comments="This is used to sort by name, in descending order in the toolbar view menu">_Z-A</attribute>
+ <attribute name="hidden-when">action-disabled</attribute>
+ </item>
+ <item>
+ <attribute name="action">view.sort</attribute>
+ <attribute name="target" type="(sb)">('date_modified',true)</attribute>
+ <attribute name="label" translatable="yes">Last _Modified</attribute>
+ <attribute name="hidden-when">action-disabled</attribute>
+ </item>
+ <item>
+ <attribute name="action">view.sort</attribute>
+ <attribute name="target" type="(sb)">('date_modified',false)</attribute>
+ <attribute name="label" translatable="yes">_First Modified</attribute>
+ <attribute name="hidden-when">action-disabled</attribute>
+ </item>
+ <item>
+ <attribute name="action">view.sort</attribute>
+ <attribute name="target" type="(sb)">('size',true)</attribute>
+ <attribute name="label" translatable="yes">_Size</attribute>
+ <attribute name="hidden-when">action-disabled</attribute>
+ </item>
+ <item>
+ <attribute name="action">view.sort</attribute>
+ <attribute name="target" type="(sb)">('type',false)</attribute>
+ <attribute name="label" translatable="yes">_Type</attribute>
+ <attribute name="hidden-when">action-disabled</attribute>
+ </item>
+ <item>
+ <attribute name="action">view.sort</attribute>
+ <attribute name="target" type="(sb)">('trashed_on',true)</attribute>
+ <attribute name="label" translatable="yes">Last _Trashed</attribute>
+ <attribute name="hidden-when">action-disabled</attribute>
+ <attribute name="nautilus-menu-item">last_trashed</attribute>
+ </item>
+ <item>
+ <attribute name="action">view.sort</attribute>
+ <attribute name="target" type="(sb)">('recency',true)</attribute>
+ <attribute name="label" translatable="yes">Recency</attribute>
+ <attribute name="hidden-when">action-disabled</attribute>
+ <attribute name="nautilus-menu-item">recency</attribute>
+ </item>
+ <item>
+ <attribute name="action">view.sort</attribute>
+ <attribute name="target" type="(sb)">('search_relevance',true)</attribute>
+ <attribute name="label" translatable="yes">Relevance</attribute>
+ <attribute name="hidden-when">action-disabled</attribute>
+ <attribute name="nautilus-menu-item">relevance</attribute>
+ </item>
+ </menu>
+</interface>
diff --git a/src/resources/ui/nautilus-toolbar.ui b/src/resources/ui/nautilus-toolbar.ui
new file mode 100644
index 0000000..cdccb89
--- /dev/null
+++ b/src/resources/ui/nautilus-toolbar.ui
@@ -0,0 +1,227 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+ <requires lib="gtk" version="4.0"/>
+ <menu id="app_menu">
+ <section>
+ <item>
+ <attribute name="label" translatable="yes">New Window</attribute>
+ <attribute name="action">app.clone-window</attribute>
+ <attribute name="verb-icon">window-new-symbolic</attribute>
+ </item>
+ <item>
+ <attribute name="label" translatable="yes">New Tab</attribute>
+ <attribute name="action">win.new-tab</attribute>
+ <attribute name="verb-icon">tab-new-symbolic</attribute>
+ </item>
+ </section>
+ <section>
+ <attribute name="label" translatable="yes">Icon Size</attribute>
+ <attribute name="display-hint">inline-buttons</attribute>
+ <item>
+ <attribute name="custom">zoom-out</attribute>
+ </item>
+ <item>
+ <attribute name="custom">zoom-in</attribute>
+ </item>
+ </section>
+ <section id="undo_redo_section">
+ <!-- Note: This section is often recreated by undo_manager_changed() to
+ change the labels of the actions. If you change anything here,
+ remember to change in the code as well. -->
+ <item>
+ <attribute name="label" translatable="yes">_Undo</attribute>
+ <attribute name="action">win.undo</attribute>
+ </item>
+ <item>
+ <attribute name="label" translatable="yes">_Redo</attribute>
+ <attribute name="action">win.undo</attribute>
+ </item>
+ </section>
+ <section>
+ <item>
+ <attribute name="label" translatable="yes">Show _Hidden Files</attribute>
+ <attribute name="action">view.show-hidden-files</attribute>
+ </item>
+ </section>
+ <section>
+ <item>
+ <attribute name="label" translatable="yes">_Preferences</attribute>
+ <attribute name="action">app.preferences</attribute>
+ </item>
+ <item>
+ <attribute name="label" translatable="yes">_Keyboard Shortcuts</attribute>
+ <attribute name="action">app.show-help-overlay</attribute>
+ </item>
+ <item>
+ <attribute name="label" translatable="yes">_Help</attribute>
+ <attribute name="action">app.help</attribute>
+ </item>
+ <item>
+ <attribute name="label" translatable="yes">_About Files</attribute>
+ <attribute name="action">app.about</attribute>
+ </item>
+ </section>
+ </menu>
+ <template class="NautilusToolbar" parent="AdwBin">
+ <child>
+ <object class="AdwHeaderBar">
+ <child type="title">
+ <object class="GtkBox" id="header_toolbar">
+ <property name="spacing">6</property>
+ <child>
+ <object class="GtkStack" id="toolbar_switcher">
+ <property name="valign">center</property>
+ <property name="transition_type">crossfade</property>
+ <child>
+ <object class="GtkStackPage">
+ <property name="name">pathbar</property>
+ <property name="child">
+ <object class="GtkBox" id="path_bar_container">
+ <property name="valign">center</property>
+ </object>
+ </property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkStackPage">
+ <property name="name">location</property>
+ <property name="child">
+ <object class="GtkBox" id="location_entry_container">
+ <style>
+ <class name="linked"/>
+ </style>
+ </object>
+ </property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkStackPage">
+ <property name="name">search</property>
+ <property name="child">
+ <object class="GtkBox" id="search_container">
+ <property name="orientation">vertical</property>
+ </object>
+ </property>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child>
+ <object class="GtkToggleButton" id="search_button">
+ <property name="tooltip_text" translatable="yes">Search</property>
+ <property name="halign">center</property>
+ <property name="valign">center</property>
+ <property name="action_name">slot.search-visible</property>
+ <property name="icon_name">edit-find-symbolic</property>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child type="start">
+ <object class="GtkBox">
+ <child>
+ <object class="GtkRevealer">
+ <property name="reveal-child" bind-source="NautilusToolbar" bind-property="show-sidebar-button" bind-flags="bidirectional|sync-create"/>
+ <property name="transition-type">slide-right</property>
+ <property name="child">
+ <object class="GtkToggleButton" id="show_sidebar_button">
+ <property name="active" bind-source="NautilusToolbar" bind-property="sidebar-button-active" bind-flags="bidirectional|sync-create"/>
+ <property name="tooltip-text" translatable="yes">Show sidebar</property>
+ <property name="icon-name">sidebar-show-symbolic</property>
+ </object>
+ </property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkRevealer">
+ <property name="reveal-child" bind-source="NautilusToolbar" bind-property="show-toolbar-children" bind-flags="sync-create"/>
+ <property name="transition-type">slide-right</property>
+ <property name="child">
+ <object class="GtkBox">
+ <property name="spacing">6</property>
+ <child>
+ <object class="NautilusHistoryControls">
+ <property name="window-slot" bind-source="NautilusToolbar" bind-property="window-slot" bind-flags="sync-create"/>
+ </object>
+ </child>
+ <child>
+ <object class="GtkSeparator">
+ <style>
+ <class name="spacer"/>
+ </style>
+ </object>
+ </child>
+ </object>
+ </property>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child type="end">
+ <object class="GtkBox">
+ <child>
+ <object class="GtkRevealer">
+ <property name="reveal-child" bind-source="NautilusToolbar" bind-property="show-toolbar-children" bind-flags="sync-create"/>
+ <property name="transition-type">slide-left</property>
+ <property name="child">
+ <object class="GtkBox">
+ <property name="spacing">6</property>
+ <property name="margin-end">6</property>
+ <child>
+ <object class="GtkSeparator">
+ <style>
+ <class name="spacer"/>
+ </style>
+ </object>
+ </child>
+ <child>
+ <object class="NautilusProgressIndicator"/>
+ </child>
+ <child>
+ <object class="NautilusViewControls">
+ <property name="window-slot" bind-source="NautilusToolbar" bind-property="window-slot" bind-flags="sync-create"/>
+ </object>
+ </child>
+ </object>
+ </property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkMenuButton" id="app_button">
+ <property name="tooltip-text" translatable="yes">Main Menu</property>
+ <property name="halign">center</property>
+ <property name="valign">center</property>
+ <property name="icon_name">open-menu-symbolic</property>
+ <property name="popover">
+ <object class="GtkPopoverMenu">
+ <property name="menu-model">app_menu</property>
+ <child type="zoom-out">
+ <object class="GtkButton">
+ <property name="icon-name">zoom-out-symbolic</property>
+ <property name="action-name">view.zoom-out</property>
+ <property name="tooltip-text" translatable="yes">Zoom out</property>
+ <style>
+ <class name="flat"/>
+ </style>
+ </object>
+ </child>
+ <child type="zoom-in">
+ <object class="GtkButton">
+ <property name="icon-name">zoom-in-symbolic</property>
+ <property name="action-name">view.zoom-in</property>
+ <property name="tooltip-text" translatable="yes">Zoom in</property>
+ <style>
+ <class name="flat"/>
+ </style>
+ </object>
+ </child>
+ </object>
+ </property>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ </template>
+</interface>
diff --git a/src/resources/ui/nautilus-view-controls.ui b/src/resources/ui/nautilus-view-controls.ui
new file mode 100644
index 0000000..2231d66
--- /dev/null
+++ b/src/resources/ui/nautilus-view-controls.ui
@@ -0,0 +1,42 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+ <menu id="view_menu">
+ <section>
+ <attribute name="label" translatable="yes" context="menu item" comments="Translators: a menu item in a group of sorting options in a toolbar menu, with criterions such as &quot;A-Z&quot; or &quot;Last Modified&quot;.">Sort</attribute>
+ <!--
+ Sort section.
+
+ The toolbar code assumes this is the second item of this menu model.
+ Its contents is provided by the view.
+ -->
+ </section>
+ <section>
+ <item>
+ <attribute name="label" translatable="yes">_Visible Columns…</attribute>
+ <attribute name="action">view.visible-columns</attribute>
+ <attribute name="hidden-when">action-missing</attribute>
+ </item>
+ </section>
+ </menu>
+ <template class="NautilusViewControls" parent="AdwBin">
+ <property name="child">
+ <object class="AdwSplitButton" id="view_split_button">
+ <property name="halign">center</property>
+ <property name="valign">center</property>
+ <property name="dropdown-tooltip" translatable="yes" comments="Translators: This is a noun, meaning the options pertaining to the view.">View Options</property>
+ <property name="action_name">slot.files-view-mode-toggle</property>
+ <property name="menu-model">view_menu</property>
+ <binding name="icon-name">
+ <lookup name="icon-name">
+ <lookup name="window-slot">NautilusViewControls</lookup>
+ </lookup>
+ </binding>
+ <binding name="tooltip-text">
+ <lookup name="tooltip">
+ <lookup name="window-slot">NautilusViewControls</lookup>
+ </lookup>
+ </binding>
+ </object>
+ </property>
+ </template>
+</interface>
diff --git a/src/resources/ui/nautilus-window.ui b/src/resources/ui/nautilus-window.ui
new file mode 100644
index 0000000..d8db7f7
--- /dev/null
+++ b/src/resources/ui/nautilus-window.ui
@@ -0,0 +1,107 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+ <requires lib="gtk" version="4.0"/>
+ <menu id="tab_menu_model">
+ <section>
+ <item>
+ <attribute name="label" translatable="yes">_New Tab</attribute>
+ <attribute name="action">win.new-tab</attribute>
+ </item>
+ </section>
+ <section>
+ <item>
+ <attribute name="label" translatable="yes">Move Tab _Left</attribute>
+ <attribute name="action">win.tab-move-left</attribute>
+ </item>
+ <item>
+ <attribute name="label" translatable="yes">Move Tab _Right</attribute>
+ <attribute name="action">win.tab-move-right</attribute>
+ </item>
+ </section>
+ <section>
+ <item>
+ <attribute name="label" translatable="yes">_Close Tab</attribute>
+ <attribute name="action">win.close-current-view</attribute>
+ </item>
+ </section>
+ </menu>
+ <template class="NautilusWindow" parent="AdwApplicationWindow">
+ <property name="show-menubar">False</property>
+ <property name="title" translatable="yes">_Files</property>
+ <child>
+ <object class="AdwToastOverlay" id="toast_overlay">
+ <property name="child">
+ <object class="GtkBox">
+ <property name="orientation">vertical</property>
+ <child>
+ <object class="NautilusToolbar" id="toolbar">
+ <property name="show-sidebar-button" bind-source="content_flap" bind-property="folded" bind-flags="sync-create"/>
+ <property name="sidebar-button-active" bind-source="content_flap" bind-property="reveal-flap" bind-flags="bidirectional|sync-create"/>
+ <property name="show-toolbar-children" bind-source="content_flap" bind-property="folded" bind-flags="sync-create|invert-boolean"/>
+ <property name="window-slot" bind-source="NautilusWindow" bind-property="active-slot" bind-flags="sync-create"/>
+ </object>
+ </child>
+ <child>
+ <object class="AdwFlap" id="content_flap">
+ <property name="swipe-to-close" bind-source="content_flap" bind-property="folded" bind-flags="sync-create"/>
+ <child type="flap">
+ <object class="NautilusGtkPlacesSidebar" id="places_sidebar">
+ <property name="vexpand">True</property>
+ <property name="show-other-locations">True</property>
+ <property name="show-starred-location">True</property>
+ <property name="width-request">240</property>
+ <style>
+ <class name="background"/>
+ </style>
+ </object>
+ </child>
+ <child type="separator">
+ <object class="GtkSeparator"/>
+ </child>
+ <child>
+ <object class="GtkBox">
+ <property name="orientation">vertical</property>
+ <property name="width-request">360</property>
+ <child>
+ <object class="AdwTabBar">
+ <property name="view">tab_view</property>
+ </object>
+ </child>
+ <child>
+ <object class="AdwTabView" id="tab_view">
+ <property name="menu-model">tab_menu_model</property>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child>
+ <object class="GtkActionBar">
+ <property name="revealed" bind-source="content_flap" bind-property="folded" bind-flags="sync-create"/>
+ <child type="start">
+ <object class="NautilusHistoryControls">
+ <property name="window-slot" bind-source="NautilusWindow" bind-property="active-slot" bind-flags="sync-create"/>
+ </object>
+ </child>
+ <child type="end">
+ <object class="GtkBox">
+ <property name="spacing">6</property>
+ <child>
+ <object class="NautilusProgressIndicator"/>
+ </child>
+ <child>
+ <object class="NautilusViewControls">
+ <property name="window-slot" bind-source="NautilusWindow" bind-property="active-slot" bind-flags="sync-create"/>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ </property>
+ </object>
+ </child>
+ </template>
+</interface>